Browse Source

get rid of tabs on repo page

Nostr-Signature: d34fb23385a23f479c683e76f5676356a11d63bcd0ecf71d25f1b85dbb0cfe57 573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc 1f6454f9961b9245d1e32f4a903ee9636201670491145d0185e95e7b7d33bf1027ac5b8e370070640e103740ab19e9915baa7755c6008fd32fe41e9cb86d33b8
main
Silberengel 3 weeks ago
parent
commit
9056fb92e3
  1. 1
      nostr/commit-signatures.jsonl
  2. 41
      src/lib/components/RepoTabs.svelte
  3. 59
      src/lib/components/TabsMenu.svelte
  4. 155
      src/lib/styles/components.css
  5. 26
      src/lib/styles/repo.css
  6. 66
      src/routes/repos/[npub]/[repo]/+page.svelte

1
nostr/commit-signatures.jsonl

@ -42,3 +42,4 @@
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771680916,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","fix profile feeds"]],"content":"Signed commit: fix profile feeds","id":"33f33d76f6c79e68fdab72c8fdfc7e1f0ecc53a879a7f5aef02481f17384a06f","sig":"8f9056eab081d66edb693eb35a2e400368aa897746b97ca40a216604dc14ee877eb7f4f16dd2eeac257025b3adfe82e23734c7c106b6cec5e8a1ca661c872cc5"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771680916,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","fix profile feeds"]],"content":"Signed commit: fix profile feeds","id":"33f33d76f6c79e68fdab72c8fdfc7e1f0ecc53a879a7f5aef02481f17384a06f","sig":"8f9056eab081d66edb693eb35a2e400368aa897746b97ca40a216604dc14ee877eb7f4f16dd2eeac257025b3adfe82e23734c7c106b6cec5e8a1ca661c872cc5"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771681068,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","remove landing page search bar"]],"content":"Signed commit: remove landing page search bar","id":"71087b100ce14a1f2eb975be23450c62143ee11a8fd0429ec7440bfea1751741","sig":"695c704503ed1397f6871770ad55822a17a503bcfb71a0db7b3f2477cacb0e767b9f122075b0216f884e41e175c0ac9f9e3d743086a2aa34db4aa1207c900703"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771681068,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","remove landing page search bar"]],"content":"Signed commit: remove landing page search bar","id":"71087b100ce14a1f2eb975be23450c62143ee11a8fd0429ec7440bfea1751741","sig":"695c704503ed1397f6871770ad55822a17a503bcfb71a0db7b3f2477cacb0e767b9f122075b0216f884e41e175c0ac9f9e3d743086a2aa34db4aa1207c900703"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771682804,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","repo page refactor"]],"content":"Signed commit: repo page refactor","id":"9ad7610ff7aa61d62d3772d6ae7c0589cda8ff95cd7a60b81c84ba879e0f9d8a","sig":"8918f36d426d352a6787543daaa044cf51855632e2257f29cc18bb87db31d61c877b525113e21045d3bc135376e1c0574454e28bd409d3135bcb80079bc11947"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771682804,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","repo page refactor"]],"content":"Signed commit: repo page refactor","id":"9ad7610ff7aa61d62d3772d6ae7c0589cda8ff95cd7a60b81c84ba879e0f9d8a","sig":"8918f36d426d352a6787543daaa044cf51855632e2257f29cc18bb87db31d61c877b525113e21045d3bc135376e1c0574454e28bd409d3135bcb80079bc11947"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771688902,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor"]],"content":"Signed commit: refactor","id":"62b813f817173c9e35eb05088240f7ec50ecab697c8c6d4a5c19d47664ef3837","sig":"ca9c70fc7bf8b1bb1726461bb843127d1bddc4de96652cfc7497698a3f5c4dc4a8c3f5a7a240710db77afabeee2a3b7d594f75f42a0a8b28aeeef50f66b506c9"}

41
src/lib/components/RepoTabs.svelte

@ -11,20 +11,7 @@
</script> </script>
<nav class="repo-tabs"> <nav class="repo-tabs">
<!-- Mobile tab menu button --> <!-- Desktop tabs -->
<button
class="mobile-tabs-menu-button"
onclick={() => showMobileMenu = !showMobileMenu}
aria-expanded={showMobileMenu}
aria-label="Tab menu"
title={tabs.find(t => t.id === activeTab)?.label || 'Menu'}
>
<img src="/icons/menu.svg" alt="" class="icon" />
<span class="current-tab-label">
{tabs.find(t => t.id === activeTab)?.label || 'Menu'}
</span>
</button>
<div class="tabs-container"> <div class="tabs-container">
{#each tabs as tab} {#each tabs as tab}
<button <button
@ -47,7 +34,31 @@
{/each} {/each}
</div> </div>
<!-- Mobile tab menu button -->
<div class="mobile-tabs-menu-wrapper">
<div class="menu-button-wrapper">
<button
class="menu-button mobile-tabs-menu-button"
onclick={() => showMobileMenu = !showMobileMenu}
aria-expanded={showMobileMenu}
aria-label="Tab menu"
title={tabs.find(t => t.id === activeTab)?.label || 'Menu'}
>
<img src="/icons/more-vertical.svg" alt="" class="icon" />
</button>
{#if showMobileMenu} {#if showMobileMenu}
<div
class="mobile-tabs-menu-overlay"
onclick={() => showMobileMenu = false}
onkeydown={(e) => {
if (e.key === 'Escape') {
showMobileMenu = false;
}
}}
role="button"
tabindex="0"
aria-label="Close menu"
></div>
<div class="mobile-tabs-menu"> <div class="mobile-tabs-menu">
{#each tabs as tab} {#each tabs as tab}
<button <button
@ -69,4 +80,6 @@
{/each} {/each}
</div> </div>
{/if} {/if}
</div>
</div>
</nav> </nav>

59
src/lib/components/TabsMenu.svelte

@ -0,0 +1,59 @@
<script lang="ts">
import '$lib/styles/components.css';
interface Props {
activeTab: string;
tabs: Array<{ id: string; label: string; icon?: string; count?: number }>;
onTabChange: (tab: string) => void;
}
let { activeTab, tabs, onTabChange }: Props = $props();
let showTabsMenu = $state(false);
</script>
<div class="tabs-menu-button-wrapper">
<div class="menu-button-wrapper">
<button
class="menu-button"
onclick={() => showTabsMenu = !showTabsMenu}
aria-label="Tabs menu"
title="View tabs"
>
<img src="/icons/more-vertical.svg" alt="" class="icon" />
</button>
{#if showTabsMenu}
<div
class="more-menu-overlay"
onclick={() => showTabsMenu = false}
onkeydown={(e) => {
if (e.key === 'Escape') {
showTabsMenu = false;
}
}}
role="button"
tabindex="0"
aria-label="Close menu"
></div>
<div class="more-menu tabs-menu">
{#each tabs as tab}
<button
class="menu-item"
class:active={activeTab === tab.id}
onclick={() => {
onTabChange(tab.id);
showTabsMenu = false;
}}
>
{#if tab.icon}
<img src={tab.icon} alt="" class="tab-icon-inline" />
{/if}
{tab.label}
{#if tab.count !== undefined}
<span class="tab-count-inline">{tab.count}</span>
{/if}
</button>
{/each}
</div>
{/if}
</div>
</div>

155
src/lib/styles/components.css

@ -509,31 +509,48 @@
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
.mobile-tabs-menu-button { .mobile-tabs-menu-wrapper {
display: none; display: none;
align-items: center; position: relative;
justify-content: flex-start; padding: 0.5rem 0.75rem;
gap: 0.5rem;
padding: 0.75rem 1rem;
width: 100%;
background: transparent;
border: none;
border-bottom: 1px solid var(--border-color, #e0e0e0); border-bottom: 1px solid var(--border-color, #e0e0e0);
}
.mobile-tabs-menu-wrapper .menu-button-wrapper {
position: relative;
display: inline-block;
}
.mobile-tabs-menu-button {
padding: 0.5rem;
background: transparent;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 0.375rem;
cursor: pointer; cursor: pointer;
font-size: 0.875rem; display: flex;
color: var(--text-primary, #1a1a1a); align-items: center;
font-weight: 500; justify-content: center;
transition: all 0.2s ease;
}
.mobile-tabs-menu-button:hover {
background: var(--bg-secondary, #f5f5f5);
border-color: var(--accent, #007bff);
} }
.mobile-tabs-menu-button .icon { .mobile-tabs-menu-button .icon {
width: 20px; width: 18px;
height: 20px; height: 18px;
flex-shrink: 0; flex-shrink: 0;
} }
.current-tab-label { .mobile-tabs-menu-overlay {
flex: 1; position: fixed;
text-align: left; top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 99;
} }
.tabs-container { .tabs-container {
@ -657,17 +674,17 @@
.mobile-tabs-menu { .mobile-tabs-menu {
position: absolute; position: absolute;
top: 100%; top: calc(100% + 0.25rem);
left: 0;
right: 0; right: 0;
background: var(--card-bg, #ffffff); background: var(--card-bg, #ffffff);
border-bottom: 1px solid var(--border-color, #e0e0e0); border: 1px solid var(--border-color, #e0e0e0);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); border-radius: 0.375rem;
z-index: 50; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 100;
min-width: 200px;
max-width: min(90vw, 300px);
max-height: 70vh; max-height: 70vh;
overflow-y: auto; overflow-y: auto;
min-width: 200px;
max-width: 100vw;
} }
.mobile-tab-item { .mobile-tab-item {
@ -726,12 +743,94 @@
} }
} }
@media (max-width: 767px) { .repo-tabs {
.tabs-container { display: none !important;
display: none; }
}
.mobile-tabs-menu-button { .tabs-menu-button-wrapper {
display: flex; display: flex;
flex-shrink: 0;
}
.tabs-menu-button-wrapper .menu-button-wrapper {
position: relative;
}
.tabs-menu {
right: auto;
left: 0;
transform: none;
min-width: 200px;
max-width: min(calc(100vw - 1rem), 320px);
width: auto;
position: absolute;
}
@media (max-width: 768px) {
.tabs-menu {
right: auto;
left: 0;
max-width: calc(100vw - 1rem);
min-width: 180px;
}
}
@media (max-width: 480px) {
.tabs-menu {
right: auto;
left: 0;
max-width: calc(100vw - 1rem);
min-width: 160px;
} }
} }
.tabs-menu .menu-item {
display: flex;
align-items: center;
gap: 0.5rem;
}
.tab-icon-inline {
width: 16px;
height: 16px;
flex-shrink: 0;
filter: brightness(0) saturate(100%) invert(1);
opacity: 1;
}
:global([data-theme="light"]) .tab-icon-inline {
filter: brightness(0) saturate(100%);
opacity: 1;
}
:global([data-theme="dark"]) .tab-icon-inline,
:global([data-theme="black"]) .tab-icon-inline {
filter: brightness(0) saturate(100%) invert(1);
opacity: 1;
}
.menu-item.active .tab-icon-inline {
filter: brightness(0) saturate(100%) invert(48%) sepia(79%) saturate(2476%) hue-rotate(200deg) brightness(118%) contrast(119%);
opacity: 1;
}
.menu-item.active {
background: var(--bg-secondary, #f5f5f5);
font-weight: 600;
color: var(--accent, #007bff);
}
.tab-count-inline {
margin-left: auto;
padding: 0.125rem 0.375rem;
background: var(--bg-secondary, #f5f5f5);
border-radius: 0.75rem;
font-size: 0.75rem;
font-weight: 500;
color: var(--text-secondary, #666);
}
.menu-item.active .tab-count-inline {
background: var(--accent, #007bff);
color: var(--accent-text, #ffffff);
}

26
src/lib/styles/repo.css

@ -568,6 +568,7 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: 0.75rem;
} }
.file-tree-header h2 { .file-tree-header h2 {
@ -980,6 +981,7 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: 0.75rem;
} }
.history-header h2, .tags-header h2 { .history-header h2, .tags-header h2 {
@ -1246,6 +1248,7 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
flex-shrink: 0; flex-shrink: 0;
gap: 0.75rem;
} }
@media (max-width: 767px) { @media (max-width: 767px) {
@ -1286,6 +1289,12 @@
color: var(--text-primary); color: var(--text-primary);
} }
@media (max-width: 767px) {
.discussions-header h2 {
display: none;
}
}
.discussion-item { .discussion-item {
padding: 1rem 1.5rem; padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
@ -1556,6 +1565,22 @@
} }
/* Documentation */ /* Documentation */
.docs-header {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
gap: 0.75rem;
}
.docs-header h2 {
margin: 0;
font-size: 1.25rem;
color: var(--text-primary);
}
.docs-content { .docs-content {
flex: 1; flex: 1;
display: flex; display: flex;
@ -1638,6 +1663,7 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: 0.75rem;
} }
.issues-header h2, .prs-header h2 { .issues-header h2, .prs-header h2 {

66
src/routes/repos/[npub]/[repo]/+page.svelte

@ -7,7 +7,7 @@
import UserBadge from '$lib/components/UserBadge.svelte'; import UserBadge from '$lib/components/UserBadge.svelte';
import EventCopyButton from '$lib/components/EventCopyButton.svelte'; import EventCopyButton from '$lib/components/EventCopyButton.svelte';
import RepoHeaderEnhanced from '$lib/components/RepoHeaderEnhanced.svelte'; import RepoHeaderEnhanced from '$lib/components/RepoHeaderEnhanced.svelte';
import RepoTabs from '$lib/components/RepoTabs.svelte'; import TabsMenu from '$lib/components/TabsMenu.svelte';
import NostrLinkRenderer from '$lib/components/NostrLinkRenderer.svelte'; import NostrLinkRenderer from '$lib/components/NostrLinkRenderer.svelte';
import '$lib/styles/repo.css'; import '$lib/styles/repo.css';
import { getPublicKeyWithNIP07, isNIP07Available, signEventWithNIP07 } from '$lib/services/nostr/nip07-signer.js'; import { getPublicKeyWithNIP07, isNIP07Available, signEventWithNIP07 } from '$lib/services/nostr/nip07-signer.js';
@ -67,6 +67,8 @@
let activeTab = $state<'files' | 'history' | 'tags' | 'issues' | 'prs' | 'docs' | 'discussions'>('discussions'); let activeTab = $state<'files' | 'history' | 'tags' | 'issues' | 'prs' | 'docs' | 'discussions'>('discussions');
let showRepoMenu = $state(false); let showRepoMenu = $state(false);
// Tabs will be defined as derived after issues and prs are declared
// Auto-save // Auto-save
let autoSaveInterval: ReturnType<typeof setInterval> | null = null; let autoSaveInterval: ReturnType<typeof setInterval> | null = null;
@ -306,6 +308,17 @@
let newPRLabels = $state<string[]>(['']); let newPRLabels = $state<string[]>(['']);
let selectedPR = $state<string | null>(null); let selectedPR = $state<string | null>(null);
// Tabs menu - defined after issues and prs
const tabs = $derived([
{ id: 'discussions', label: 'Discussions', icon: '/icons/message-circle.svg' },
{ id: 'files', label: 'Files', icon: '/icons/file-text.svg' },
{ id: 'history', label: 'History', icon: '/icons/git-commit.svg' },
{ id: 'tags', label: 'Tags', icon: '/icons/tag.svg' },
{ id: 'issues', label: 'Issues', icon: '/icons/alert-circle.svg', count: issues.length },
{ id: 'prs', label: 'Pull Requests', icon: '/icons/git-pull-request.svg', count: prs.length },
{ id: 'docs', label: 'Docs', icon: '/icons/book.svg' }
]);
// Patches // Patches
let showCreatePatchDialog = $state(false); let showCreatePatchDialog = $state(false);
let newPatchContent = $state(''); let newPatchContent = $state('');
@ -3754,25 +3767,17 @@
{/if} {/if}
<!-- Tabs --> <!-- Tabs -->
<RepoTabs
activeTab={activeTab}
tabs={[
{ id: 'discussions', label: 'Discussions', icon: '/icons/message-circle.svg' },
{ id: 'files', label: 'Files', icon: '/icons/file-text.svg' },
{ id: 'history', label: 'History', icon: '/icons/git-commit.svg' },
{ id: 'tags', label: 'Tags', icon: '/icons/tag.svg' },
{ id: 'issues', label: 'Issues', icon: '/icons/alert-circle.svg', count: issues.length },
{ id: 'prs', label: 'Pull Requests', icon: '/icons/git-pull-request.svg', count: prs.length },
{ id: 'docs', label: 'Docs', icon: '/icons/book.svg' }
]}
onTabChange={(tab) => activeTab = tab as typeof activeTab}
/>
<div class="repo-layout"> <div class="repo-layout">
<!-- File Tree Sidebar --> <!-- File Tree Sidebar -->
{#if activeTab === 'files'} {#if activeTab === 'files'}
<aside class="file-tree" class:hide-on-mobile={!showFileListOnMobile && activeTab === 'files'}> <aside class="file-tree" class:hide-on-mobile={!showFileListOnMobile && activeTab === 'files'}>
<div class="file-tree-header"> <div class="file-tree-header">
<TabsMenu
activeTab={activeTab}
{tabs}
onTabChange={(tab) => activeTab = tab as typeof activeTab}
/>
<h2>Files</h2> <h2>Files</h2>
<div class="file-tree-actions"> <div class="file-tree-actions">
{#if pathStack.length > 0 || currentPath} {#if pathStack.length > 0 || currentPath}
@ -3843,6 +3848,11 @@
{#if activeTab === 'history'} {#if activeTab === 'history'}
<aside class="history-sidebar"> <aside class="history-sidebar">
<div class="history-header"> <div class="history-header">
<TabsMenu
activeTab={activeTab}
{tabs}
onTabChange={(tab) => activeTab = tab as typeof activeTab}
/>
<h2>Commit History</h2> <h2>Commit History</h2>
<button onclick={loadCommitHistory} class="refresh-button"> <button onclick={loadCommitHistory} class="refresh-button">
<img src="/icons/refresh-cw.svg" alt="" class="icon-inline" /> <img src="/icons/refresh-cw.svg" alt="" class="icon-inline" />
@ -3879,6 +3889,11 @@
{#if activeTab === 'tags'} {#if activeTab === 'tags'}
<aside class="tags-sidebar"> <aside class="tags-sidebar">
<div class="tags-header"> <div class="tags-header">
<TabsMenu
activeTab={activeTab}
{tabs}
onTabChange={(tab) => activeTab = tab as typeof activeTab}
/>
<h2>Tags</h2> <h2>Tags</h2>
{#if userPubkey && isMaintainer} {#if userPubkey && isMaintainer}
<button <button
@ -3917,6 +3932,11 @@
{#if activeTab === 'issues'} {#if activeTab === 'issues'}
<aside class="issues-sidebar"> <aside class="issues-sidebar">
<div class="issues-header"> <div class="issues-header">
<TabsMenu
activeTab={activeTab}
{tabs}
onTabChange={(tab) => activeTab = tab as typeof activeTab}
/>
<h2>Issues</h2> <h2>Issues</h2>
{#if userPubkey} {#if userPubkey}
<button onclick={() => { <button onclick={() => {
@ -3971,6 +3991,11 @@
{#if activeTab === 'prs'} {#if activeTab === 'prs'}
<aside class="prs-sidebar"> <aside class="prs-sidebar">
<div class="prs-header"> <div class="prs-header">
<TabsMenu
activeTab={activeTab}
{tabs}
onTabChange={(tab) => activeTab = tab as typeof activeTab}
/>
<h2>Pull Requests</h2> <h2>Pull Requests</h2>
{#if userPubkey} {#if userPubkey}
<button onclick={() => { <button onclick={() => {
@ -4222,6 +4247,14 @@
{#if activeTab === 'docs'} {#if activeTab === 'docs'}
<div class="docs-content"> <div class="docs-content">
<div class="docs-header">
<TabsMenu
activeTab={activeTab}
{tabs}
onTabChange={(tab) => activeTab = tab as typeof activeTab}
/>
<h2>Docs</h2>
</div>
{#if loadingDocs} {#if loadingDocs}
<div class="loading">Loading documentation...</div> <div class="loading">Loading documentation...</div>
{:else if documentationHtml} {:else if documentationHtml}
@ -4243,6 +4276,11 @@
{#if activeTab === 'discussions'} {#if activeTab === 'discussions'}
<div class="discussions-content"> <div class="discussions-content">
<div class="discussions-header"> <div class="discussions-header">
<TabsMenu
activeTab={activeTab}
{tabs}
onTabChange={(tab) => activeTab = tab as typeof activeTab}
/>
<h2>Discussions</h2> <h2>Discussions</h2>
<div class="discussions-actions"> <div class="discussions-actions">
<button <button

Loading…
Cancel
Save