Browse Source

refactor 9

Nostr-Signature: 0d92496bc69fe5a2005be0eba26653a729d358f9d9f227e1af01c330eb0c4387 573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc 9203dcc9cfb44804957d37d3b47079ac324bffb42e445a3a79130622e5e20fd10513d987f48edf195514ebf6cb136ba6c5992b39de21b8b47a086194e22cbaeb
main
Silberengel 2 weeks ago
parent
commit
227033d316
  1. 1
      nostr/commit-signatures.jsonl
  2. 27
      src/routes/repos/[npub]/[repo]/+page.svelte
  3. 152
      src/routes/repos/[npub]/[repo]/components/DocsTab.svelte
  4. 16
      src/routes/repos/[npub]/[repo]/components/HistoryTab.svelte
  5. 8
      src/routes/repos/[npub]/[repo]/components/IssuesTab.svelte
  6. 8
      src/routes/repos/[npub]/[repo]/components/PRsTab.svelte
  7. 8
      src/routes/repos/[npub]/[repo]/components/PatchesTab.svelte
  8. 53
      src/routes/repos/[npub]/[repo]/components/StatusTabLayout.svelte
  9. 19
      src/routes/repos/[npub]/[repo]/components/TabLayout.svelte
  10. 27
      src/routes/repos/[npub]/[repo]/components/dialogs/CreateIssueDialog.svelte
  11. 27
      src/routes/repos/[npub]/[repo]/components/dialogs/CreatePRDialog.svelte
  12. 26
      src/routes/repos/[npub]/[repo]/components/dialogs/CreatePatchDialog.svelte
  13. 5
      src/routes/repos/[npub]/[repo]/components/dialogs/Modal.svelte

1
nostr/commit-signatures.jsonl

@ -101,3 +101,4 @@ @@ -101,3 +101,4 @@
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772111536,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor 5"]],"content":"Signed commit: refactor 5","id":"47651ed0aee8072f356fbac30b6168f2c985bcca392f9ed7d7c38d9670d90f16","sig":"2ca5d04d4a619dc3e02962249f7c650e3a561315897b329f4493e87148c5dd89fbcb6694515a72d0d17e64c9930e57bd7761e27b353275bb1ada9449330f4e1c"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772112054,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor 6"]],"content":"Signed commit: refactor 6","id":"cd9b7e015ee3bd6a4c4ab7d54d90ab411a08c29f249158c4cdea2b12996b6b44","sig":"2a8fd0a3718169df1517c0b939ec9ea9793da4dc07b20d9366f09fe70b5268e94451916f975e2cea1a3741419440189d31f844e79863e84de00c0be7c449e92a"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772112920,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor 7"]],"content":"Signed commit: refactor 7","id":"80f54ac61390cfbc8f2496a162d7065c447033a2e085ab5886c8138e337e93f9","sig":"f64bd2c965eff3534ad68e245651c189dc925d9613dd85557d88af8c692361ade13ccdd9deb88ae07eb227aa002af99d525a7fdf6f29eca854b5a02882ef226f"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772130529,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor 8"]],"content":"Signed commit: refactor 8","id":"716cfe7b5d8b788e6e24092a6ad7e92de0b3d383c43a343f3c5bec4d2bbdd4b9","sig":"e80ed3d9d471bd6907e212edfd7cf3f6039fa80e4434c35f0591729515eaa98c7a8ac54f2ac6f7a2fefb7846de0e2f0a120543a0dbe862c47c7710a653189b0c"}

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

@ -294,26 +294,19 @@ @@ -294,26 +294,19 @@
const lastBranch = { value: null as string | null };
// Initialize activeTab from URL query parameter
// Sync activeTab to match URL state
$effect(() => {
if (typeof window === 'undefined' || !state.isMounted) return;
const tabFromQuery = $page.url.searchParams.get('tab');
const validTabs = ['docs', 'files', 'issues', 'prs', 'patches', 'discussions', 'history', 'tags', 'code-search'];
if (tabFromQuery && validTabs.includes(tabFromQuery)) {
// Update from URL query parameter
if (tabFromQuery !== state.ui.activeTab) {
state.ui.activeTab = tabFromQuery as typeof state.ui.activeTab;
}
} else if (!tabFromQuery) {
// No tab in query params - 'files' is the default (no param = files tab)
// Only set default if activeTab is not already a valid tab
if (!validTabs.includes(state.ui.activeTab)) {
state.ui.activeTab = 'files';
}
// If activeTab is already 'files', do nothing (user is on files tab)
// If activeTab is another valid tab but URL has no param, don't override
// This allows onTabChange handlers to manage state without interference
// Determine what tab the URL represents
const urlTab = tabFromQuery && validTabs.includes(tabFromQuery) ? tabFromQuery : 'files';
// Only update if activeTab doesn't match URL state
if (state.ui.activeTab !== urlTab) {
state.ui.activeTab = urlTab as typeof state.ui.activeTab;
}
});
@ -2325,6 +2318,8 @@ @@ -2325,6 +2318,8 @@
}
goto(url.pathname + url.search, { replaceState: true, noScroll: true });
}}
onCreate={() => { state.openDialog = 'createIssue'; }}
userPubkey={state.user.pubkey || null}
/>
{/if}
@ -2385,6 +2380,8 @@ @@ -2385,6 +2380,8 @@
}
goto(url.pathname + url.search, { replaceState: true, noScroll: true });
}}
onCreate={() => { state.openDialog = 'createPR'; }}
userPubkey={state.user.pubkey || null}
/>
{/if}
@ -2466,6 +2463,8 @@ @@ -2466,6 +2463,8 @@
}
goto(url.pathname + url.search, { replaceState: true, noScroll: true });
}}
onCreate={() => { state.openDialog = 'createPatch'; }}
userPubkey={state.user.pubkey || null}
/>
{/if}

152
src/routes/repos/[npub]/[repo]/components/DocsTab.svelte

@ -36,9 +36,11 @@ @@ -36,9 +36,11 @@
let documentationKind = $state<'markdown' | 'asciidoc' | 'text' | '30040' | null>(null);
let indexEvent = $state<NostrEvent | null>(null);
let loading = $state(false);
let loadingDocs = $state(false);
let error = $state<string | null>(null);
let docFiles: Array<{ name: string; path: string }> = $state([]);
let selectedDoc: string | null = $state(null);
let hasReadme = $state(false);
$effect(() => {
if (npub && repo && currentBranch) {
@ -48,83 +50,67 @@ @@ -48,83 +50,67 @@
async function loadDocumentation() {
loading = true;
loadingDocs = true;
error = null;
documentationContent = null;
documentationKind = null;
indexEvent = null;
hasReadme = false;
try {
logger.operation('Loading documentation', { npub, repo, branch: currentBranch });
// Try README first (faster, always available if repo has content)
const readmePromise = (async () => {
try {
const readmeResponse = await fetch(`/api/repos/${npub}/${repo}/readme?ref=${currentBranch || 'HEAD'}`);
if (readmeResponse.ok) {
const readmeData = await readmeResponse.json();
if (readmeData.content) {
return {
content: readmeData.content,
kind: readmeData.type || 'markdown',
path: 'README.md'
};
}
// Load README FIRST and display immediately
try {
const readmeResponse = await fetch(`/api/repos/${npub}/${repo}/readme?ref=${currentBranch || 'HEAD'}`);
if (readmeResponse.ok) {
const readmeData = await readmeResponse.json();
if (readmeData.content) {
documentationContent = readmeData.content;
documentationKind = readmeData.type || 'markdown';
selectedDoc = 'README.md';
hasReadme = true;
loading = false; // Stop showing loading once README is loaded
logger.debug({ npub, repo }, 'README loaded and displayed');
}
} catch (readmeErr) {
logger.debug({ error: readmeErr, npub, repo }, 'No README found');
}
return null;
})();
} catch (readmeErr) {
logger.debug({ error: readmeErr, npub, repo }, 'No README found');
}
// Try docs folder in parallel
const docsPromise = (async () => {
try {
const response = await fetch(`/api/repos/${npub}/${repo}/tree?ref=${currentBranch || 'HEAD'}&path=docs`);
if (response.ok) {
const data = await response.json();
return Array.isArray(data) ? data : (data.files || []);
// Now check for docs folder in the background
try {
const response = await fetch(`/api/repos/${npub}/${repo}/tree?ref=${currentBranch || 'HEAD'}&path=docs`);
if (response.ok) {
const data = await response.json();
const docsFiles = Array.isArray(data) ? data : (data.files || []);
if (docsFiles.length > 0) {
docFiles = docsFiles;
logger.debug({ npub, repo, fileCount: docsFiles.length }, 'Docs folder found');
}
} catch (err) {
logger.debug({ error: err, npub, repo }, 'Docs folder not found');
}
return [];
})();
// Wait for both, prefer docs folder if it has files
const [readmeResult, docsFiles] = await Promise.all([readmePromise, docsPromise]);
docFiles = docsFiles;
if (docsFiles.length > 0) {
// Look for README or index files first in docs folder
const readmeFile = docsFiles.find((f: { name: string; path: string }) =>
f.name.toLowerCase() === 'readme.md' ||
f.name.toLowerCase() === 'readme.adoc' ||
f.name.toLowerCase() === 'index.md'
);
if (readmeFile) {
await loadDocFile(readmeFile.path);
} else {
// Load first file from docs folder
await loadDocFile(docsFiles[0].path);
}
} else if (readmeResult) {
// No docs folder, use README from root
documentationContent = readmeResult.content;
documentationKind = readmeResult.kind as 'markdown' | 'asciidoc';
selectedDoc = readmeResult.path;
} catch (err) {
logger.debug({ error: err, npub, repo }, 'Docs folder not found');
}
// Check for kind 30040 publication index (only if no content found yet)
if (!documentationContent && !indexEvent) {
// Check for kind 30040 publication index (only if no README found)
if (!hasReadme && !indexEvent) {
await checkForPublicationIndex();
if (indexEvent) {
loading = false;
}
}
} catch (err) {
error = err instanceof Error ? err.message : 'Failed to load documentation';
logger.error({ error: err, npub, repo }, 'Error loading documentation');
} finally {
loading = false;
} finally {
loadingDocs = false;
if (!hasReadme && !indexEvent) {
loading = false;
}
}
}
@ -198,31 +184,47 @@ @@ -198,31 +184,47 @@
{#snippet leftPane()}
<div class="docs-sidebar">
<h3>Documentation</h3>
{#if loading}
<div class="loading">Loading documentation...</div>
{#if loadingDocs}
<div class="loading">Loading...</div>
{:else if error}
<div class="error">{error}</div>
{:else if docFiles.length > 0}
{:else}
<ul class="doc-list">
{#each docFiles as file}
{#if hasReadme}
<li>
<button
class="doc-item {selectedDoc === file.path ? 'selected' : ''}"
onclick={() => loadDocFile(file.path)}
class="doc-item {selectedDoc === 'README.md' ? 'selected' : ''}"
onclick={() => {
// Reload README if needed
if (!documentationContent) {
loadDocumentation();
} else {
selectedDoc = 'README.md';
}
}}
>
{file.name}
README.md
</button>
</li>
{/each}
{/if}
{#if docFiles.length > 0}
{#each docFiles as file}
<li>
<button
class="doc-item {selectedDoc === file.path ? 'selected' : ''}"
onclick={() => loadDocFile(file.path)}
>
{file.name}
</button>
</li>
{/each}
{/if}
{#if !hasReadme && docFiles.length === 0}
<div class="empty-sidebar">
<p>No documentation files found</p>
</div>
{/if}
</ul>
{:else if documentationContent}
<div class="empty-sidebar">
<p>No custom documentation found. Displaying the ReadMe, instead.</p>
</div>
{:else}
<div class="empty-sidebar">
<p>No documentation files found</p>
</div>
{/if}
</div>
{/snippet}
@ -260,12 +262,14 @@ @@ -260,12 +262,14 @@
<style>
.docs-sidebar {
padding: 1rem;
color: var(--text-primary);
}
.docs-sidebar h3 {
margin: 0 0 1rem 0;
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
}
.doc-list {
@ -284,16 +288,22 @@ @@ -284,16 +288,22 @@
margin-bottom: 0.5rem;
cursor: pointer;
transition: all 0.2s;
color: var(--text-primary);
font-family: inherit;
font-size: inherit;
}
.doc-item:hover {
background: var(--bg-hover);
border-color: var(--accent-color);
color: var(--text-primary);
}
.doc-item.selected {
background: var(--bg-selected);
border-color: var(--accent-color);
color: var(--text-primary);
font-weight: 500;
}
.empty-docs {

16
src/routes/repos/[npub]/[repo]/components/HistoryTab.svelte

@ -91,15 +91,19 @@ @@ -91,15 +91,19 @@
{#if selectedCommit}
{@const commit = commits.find(c => c.hash === selectedCommit)}
{#if commit}
{@const hasNostrSignature = commit.message && commit.message.includes('Nostr-Signature:')}
{@const hasVerification = commit.verification && commit.verification.hasSignature !== false}
<div class="commit-detail">
<div class="commit-detail-header">
<h2>Commit {commit.hash.slice(0, 7)}</h2>
<button
onclick={() => onVerify(commit.hash)}
disabled={verifyingCommits.has(commit.hash)}
>
{verifyingCommits.has(commit.hash) ? 'Verifying...' : 'Verify Signature'}
</button>
{#if hasNostrSignature || hasVerification}
<button
onclick={() => onVerify(commit.hash)}
disabled={verifyingCommits.has(commit.hash)}
>
{verifyingCommits.has(commit.hash) ? 'Verifying...' : 'Verify Signature'}
</button>
{/if}
</div>
<div class="commit-info">

8
src/routes/repos/[npub]/[repo]/components/IssuesTab.svelte

@ -27,6 +27,8 @@ @@ -27,6 +27,8 @@
activeTab?: string;
tabs?: Array<{ id: string; label: string; icon?: string }>;
onTabChange?: (tab: string) => void;
onCreate?: () => void;
userPubkey?: string | null;
}
let {
@ -40,7 +42,9 @@ @@ -40,7 +42,9 @@
loadingReplies = false,
activeTab = '',
tabs = [],
onTabChange = () => {}
onTabChange = () => {},
onCreate,
userPubkey = null
}: Props = $props();
const items = $derived(issues.map(issue => ({
@ -148,6 +152,8 @@ @@ -148,6 +152,8 @@
{tabs}
{onTabChange}
title="Issues"
{onCreate}
showCreateButton={!!userPubkey && !!onCreate}
/>
<style>

8
src/routes/repos/[npub]/[repo]/components/PRsTab.svelte

@ -26,6 +26,8 @@ @@ -26,6 +26,8 @@
activeTab?: string;
tabs?: Array<{ id: string; label: string; icon?: string }>;
onTabChange?: (tab: string) => void;
onCreate?: () => void;
userPubkey?: string | null;
}
let {
@ -37,7 +39,9 @@ @@ -37,7 +39,9 @@
onStatusUpdate = () => {},
activeTab = '',
tabs = [],
onTabChange = () => {}
onTabChange = () => {},
onCreate,
userPubkey = null
}: Props = $props();
const items = $derived(prs.map(pr => ({
@ -130,6 +134,8 @@ @@ -130,6 +134,8 @@
{tabs}
{onTabChange}
title="Pull Requests"
{onCreate}
showCreateButton={!!userPubkey && !!onCreate}
/>
<style>

8
src/routes/repos/[npub]/[repo]/components/PatchesTab.svelte

@ -27,6 +27,8 @@ @@ -27,6 +27,8 @@
activeTab?: string;
tabs?: Array<{ id: string; label: string; icon?: string }>;
onTabChange?: (tab: string) => void;
onCreate?: () => void;
userPubkey?: string | null;
}
let {
@ -40,7 +42,9 @@ @@ -40,7 +42,9 @@
applying = {},
activeTab = '',
tabs = [],
onTabChange = () => {}
onTabChange = () => {},
onCreate,
userPubkey = null
}: Props = $props();
const items = $derived(patches.map(patch => ({
@ -134,6 +138,8 @@ @@ -134,6 +138,8 @@
{tabs}
{onTabChange}
title="Patches"
{onCreate}
showCreateButton={!!userPubkey && !!onCreate}
/>
<style>

53
src/routes/repos/[npub]/[repo]/components/StatusTabLayout.svelte

@ -24,6 +24,8 @@ @@ -24,6 +24,8 @@
tabs?: Array<{ id: string; label: string; icon?: string }>;
onTabChange?: (tab: string) => void;
title?: string;
onCreate?: () => void;
showCreateButton?: boolean;
}
let {
@ -41,7 +43,9 @@ @@ -41,7 +43,9 @@
activeTab = '',
tabs = [],
onTabChange = () => {},
title = ''
title = '',
onCreate,
showCreateButton = false
}: Props = $props();
let selectedItem = $derived(items.find(item => item.id === selectedId) || null);
@ -76,6 +80,18 @@ @@ -76,6 +80,18 @@
>
{#snippet leftPane()}
<div class="status-groups">
{#if showCreateButton && onCreate}
<div class="status-header-wrapper">
<h2 class="status-title">{title || 'Items'}</h2>
<button
onclick={onCreate}
class="create-button"
title="Create New"
>
<img src="/icons/plus.svg" alt="New" class="icon" />
</button>
</div>
{/if}
{#each statusGroups as { label, value }}
{#if grouped[value] && grouped[value].length > 0}
<div class="status-group">
@ -134,6 +150,41 @@ @@ -134,6 +150,41 @@
gap: 1.5rem;
}
.status-header-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border-color);
}
.status-title {
margin: 0;
font-size: 1.25rem;
color: var(--text-primary);
}
.create-button {
background: none;
border: none;
cursor: pointer;
padding: 0.25rem;
display: flex;
align-items: center;
transition: opacity 0.2s;
}
.create-button:hover {
opacity: 0.7;
}
.create-button .icon {
width: 20px;
height: 20px;
filter: var(--icon-filter, none);
}
.status-group {
display: flex;
flex-direction: column;

19
src/routes/repos/[npub]/[repo]/components/TabLayout.svelte

@ -73,6 +73,10 @@ @@ -73,6 +73,10 @@
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
max-width: 100%;
min-width: 0;
box-sizing: border-box;
}
.tab-header {
@ -83,6 +87,8 @@ @@ -83,6 +87,8 @@
gap: 0.5rem;
background: var(--bg-primary);
flex-shrink: 0;
width: 100%;
box-sizing: border-box;
}
.tab-title {
@ -90,26 +96,35 @@ @@ -90,26 +96,35 @@
margin: 0;
font-size: 1.2rem;
color: var(--text-primary);
min-width: 0;
}
.tab-layout {
display: flex;
flex: 1;
min-height: 0;
min-width: 0;
width: 100%;
max-width: 100%;
gap: 1rem;
box-sizing: border-box;
}
.left-pane {
flex: 0 0 300px;
min-width: 300px;
max-width: 300px;
border-right: 1px solid var(--border-color);
overflow-y: auto;
padding: 1rem;
box-sizing: border-box;
}
.right-panel {
flex: 1;
flex: 1 1 auto;
min-width: 0;
width: 100%;
width: auto;
max-width: none;
overflow-y: auto;
overflow-x: hidden;
padding: 1rem;

27
src/routes/repos/[npub]/[repo]/components/dialogs/CreateIssueDialog.svelte

@ -39,13 +39,24 @@ @@ -39,13 +39,24 @@
margin-bottom: 1rem;
}
label {
color: var(--text-primary, #e0e0e0);
}
label input,
label textarea {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border: 1px solid var(--border-color, #333);
border-radius: 4px;
background: var(--bg-secondary, #2a2a2a);
color: var(--text-primary, #e0e0e0);
}
label input::placeholder,
label textarea::placeholder {
color: var(--text-secondary, #888);
}
.modal-actions {
@ -64,14 +75,24 @@ @@ -64,14 +75,24 @@
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
background: var(--cancel-bg, var(--bg-secondary, #2a2a2a));
color: var(--text-primary, #e0e0e0);
border: 1px solid var(--border-color, #333);
}
.cancel-button:hover {
background: var(--bg-hover, #3a3a3a);
}
.save-button {
background: var(--primary-color, #2196f3);
background: var(--primary-color, var(--accent-color, #2196f3));
color: white;
}
.save-button:hover {
opacity: 0.9;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;

27
src/routes/repos/[npub]/[repo]/components/dialogs/CreatePRDialog.svelte

@ -47,13 +47,24 @@ @@ -47,13 +47,24 @@
margin-bottom: 1rem;
}
label {
color: var(--text-primary, #e0e0e0);
}
label input,
label textarea {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border: 1px solid var(--border-color, #333);
border-radius: 4px;
background: var(--bg-secondary, #2a2a2a);
color: var(--text-primary, #e0e0e0);
}
label input::placeholder,
label textarea::placeholder {
color: var(--text-secondary, #888);
}
.modal-actions {
@ -72,14 +83,24 @@ @@ -72,14 +83,24 @@
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
background: var(--cancel-bg, var(--bg-secondary, #2a2a2a));
color: var(--text-primary, #e0e0e0);
border: 1px solid var(--border-color, #333);
}
.cancel-button:hover {
background: var(--bg-hover, #3a3a3a);
}
.save-button {
background: var(--primary-color, #2196f3);
background: var(--primary-color, var(--accent-color, #2196f3));
color: white;
}
.save-button:hover {
opacity: 0.9;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;

26
src/routes/repos/[npub]/[repo]/components/dialogs/CreatePatchDialog.svelte

@ -37,13 +37,14 @@ @@ -37,13 +37,14 @@
<style>
.help-text {
margin-bottom: 1rem;
color: var(--text-secondary, #666);
color: var(--text-secondary, #888);
font-size: 0.9rem;
}
label {
display: block;
margin-bottom: 1rem;
color: var(--text-primary, #e0e0e0);
}
label input,
@ -51,8 +52,15 @@ @@ -51,8 +52,15 @@
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid var(--border-color, #e0e0e0);
border: 1px solid var(--border-color, #333);
border-radius: 4px;
background: var(--bg-secondary, #2a2a2a);
color: var(--text-primary, #e0e0e0);
}
label input::placeholder,
label textarea::placeholder {
color: var(--text-secondary, #888);
}
.modal-actions {
@ -71,14 +79,24 @@ @@ -71,14 +79,24 @@
}
.cancel-button {
background: var(--cancel-bg, #e0e0e0);
background: var(--cancel-bg, var(--bg-secondary, #2a2a2a));
color: var(--text-primary, #e0e0e0);
border: 1px solid var(--border-color, #333);
}
.cancel-button:hover {
background: var(--bg-hover, #3a3a3a);
}
.save-button {
background: var(--primary-color, #2196f3);
background: var(--primary-color, var(--accent-color, #2196f3));
color: white;
}
.save-button:hover {
opacity: 0.9;
}
.save-button:disabled {
opacity: 0.5;
cursor: not-allowed;

5
src/routes/repos/[npub]/[repo]/components/dialogs/Modal.svelte

@ -52,16 +52,19 @@ @@ -52,16 +52,19 @@
}
.modal {
background: var(--modal-bg, #fff);
background: var(--modal-bg, var(--bg-primary, #1a1a1a));
color: var(--text-primary, #e0e0e0);
border-radius: 8px;
padding: 1.5rem;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
border: 1px solid var(--border-color, #333);
}
.modal h3 {
margin: 0 0 1rem 0;
color: var(--text-primary, #e0e0e0);
}
</style>

Loading…
Cancel
Save