Browse Source

bug-fixes

Nostr-Signature: 6fd5146b5f5980987adc5e6b93a1bcb31cfbb6a2e4fce6f1dbe5c7fdf54b717a 573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc 41422b07b63fc494c0aba22b85f1b56a6a5527f9df48d49816f7be417ad74608f8d1dff8302f562b2f47690720c089aab76db948f5bf1ecdda68741b256d7a2b
main
Silberengel 3 weeks ago
parent
commit
c8e7d34509
  1. 1
      nostr/commit-signatures.jsonl
  2. 237
      src/app.css
  3. 9
      src/lib/components/RepoHeaderEnhanced.svelte
  4. 12
      src/lib/styles/components.css
  5. 2176
      src/lib/styles/repo.css
  6. 2
      src/routes/api/repos/[npub]/[repo]/download/+server.ts
  7. 169
      src/routes/repos/[npub]/[repo]/+page.svelte

1
nostr/commit-signatures.jsonl

@ -46,3 +46,4 @@
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771690183,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","get rid of tabs on repo page"]],"content":"Signed commit: get rid of tabs on repo page","id":"d34fb23385a23f479c683e76f5676356a11d63bcd0ecf71d25f1b85dbb0cfe57","sig":"1f6454f9961b9245d1e32f4a903ee9636201670491145d0185e95e7b7d33bf1027ac5b8e370070640e103740ab19e9915baa7755c6008fd32fe41e9cb86d33b8"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771690183,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","get rid of tabs on repo page"]],"content":"Signed commit: get rid of tabs on repo page","id":"d34fb23385a23f479c683e76f5676356a11d63bcd0ecf71d25f1b85dbb0cfe57","sig":"1f6454f9961b9245d1e32f4a903ee9636201670491145d0185e95e7b7d33bf1027ac5b8e370070640e103740ab19e9915baa7755c6008fd32fe41e9cb86d33b8"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771691277,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","fix docs"]],"content":"Signed commit: fix docs","id":"4671648712f19537cbf0fd00cf19e254eae4a1ac9c1274ea396e62dac193b88c","sig":"49a3e89e312ec4caebfeacdaade3e4cc6d027ab9c50d8e6aa1998f120a81d8d51235ae397df6e42b9efca4147497b8881731dda6d58fee7d28d2ac07cec295ec"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771691277,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","fix docs"]],"content":"Signed commit: fix docs","id":"4671648712f19537cbf0fd00cf19e254eae4a1ac9c1274ea396e62dac193b88c","sig":"49a3e89e312ec4caebfeacdaade3e4cc6d027ab9c50d8e6aa1998f120a81d8d51235ae397df6e42b9efca4147497b8881731dda6d58fee7d28d2ac07cec295ec"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771705699,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"59d0c409196dccb8109a29829002df69dbca43c5e95c1fdc1e7baa0b88ee5927","sig":"af8726a86e30c64b098ad13946d5bc84cb08d5ea8b75f08641c03fbdd8b9c91683e8091b206159dde2239ea8964cb3589bcb4ec2892541d2980f186a0fb09af9"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771705699,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"59d0c409196dccb8109a29829002df69dbca43c5e95c1fdc1e7baa0b88ee5927","sig":"af8726a86e30c64b098ad13946d5bc84cb08d5ea8b75f08641c03fbdd8b9c91683e8091b206159dde2239ea8964cb3589bcb4ec2892541d2980f186a0fb09af9"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771708933,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"6d8832125b76095b2e7ed57b71e26a6c05d9b19a14dfa76724c71f392147fe95","sig":"6ddebfa995b5b3f469db5f3cdbd7d13fa2307d7988c2667479015d6bc2ff442be357ee97e51340a944eb34fed73522db3930016d343810927486bdbcabddae5c"}

237
src/app.css

@ -639,42 +639,80 @@ textarea:disabled::placeholder {
} }
/* Make icon images theme-aware using filters */ /* Make icon images theme-aware using filters */
/* Target both img inside .icon and img with icon classes */
img.icon,
.icon img,
img.icon-small,
.icon-small img, .icon-small img,
img.icon-inline,
.icon-inline img, .icon-inline img,
.hamburger-icon img, .hamburger-icon img,
.platform-icon img, .platform-icon img,
.repo-badge-icon img, .repo-badge-icon img,
.btn-icon img { .btn-icon img {
filter: var(--icon-filter, brightness(0) saturate(100%) invert(var(--icon-invert, 0))); filter: brightness(0) saturate(100%) invert(1) !important; /* Default white for dark themes */
transition: filter 0.3s ease; opacity: 1 !important;
opacity: 0.9; transition: filter 0.3s ease, opacity 0.3s ease;
}
/* Theme icons - handled separately for better control */
.theme-icon img:not(.theme-toggle .theme-icon img),
.theme-icon-option img:not(.theme-option .theme-icon-option img) {
filter: var(--icon-filter, brightness(0) saturate(100%) invert(var(--icon-invert, 0)));
transition: filter 0.3s ease;
opacity: 0.9;
} }
/* Light theme: icons should be dark (black) */ /* Light theme: icons should be dark (black) */
:root, [data-theme="light"] img.icon,
[data-theme="light"] { [data-theme="light"] .icon img,
--icon-filter: brightness(0) saturate(100%); [data-theme="light"] img.icon-small,
--icon-invert: 0; [data-theme="light"] .icon-small img,
[data-theme="light"] img.icon-inline,
[data-theme="light"] .icon-inline img,
[data-theme="light"] .hamburger-icon img,
[data-theme="light"] .platform-icon img,
[data-theme="light"] .repo-badge-icon img,
[data-theme="light"] .btn-icon img {
filter: brightness(0) saturate(100%) !important; /* Black in light theme */
opacity: 1 !important;
} }
/* Dark theme: icons should be light (white/light gray) */ /* Dark theme (Purple): icons should be light (white/light gray) */
[data-theme="dark"] { [data-theme="dark"] img.icon,
--icon-filter: brightness(0) saturate(100%) invert(1); [data-theme="dark"] .icon img,
--icon-invert: 1; [data-theme="dark"] img.icon-small,
[data-theme="dark"] .icon-small img,
[data-theme="dark"] img.icon-inline,
[data-theme="dark"] .icon-inline img,
[data-theme="dark"] .hamburger-icon img,
[data-theme="dark"] .platform-icon img,
[data-theme="dark"] .repo-badge-icon img,
[data-theme="dark"] .btn-icon img {
filter: brightness(0) saturate(100%) invert(1) !important; /* White in dark/purple theme */
opacity: 1 !important;
} }
/* Black theme: icons should be light (white/light gray) */ /* Black theme: icons should be light (white/light gray) */
[data-theme="black"] { [data-theme="black"] img.icon,
--icon-filter: brightness(0) saturate(100%) invert(1); [data-theme="black"] .icon img,
--icon-invert: 1; [data-theme="black"] img.icon-small,
[data-theme="black"] .icon-small img,
[data-theme="black"] img.icon-inline,
[data-theme="black"] .icon-inline img,
[data-theme="black"] .hamburger-icon img,
[data-theme="black"] .platform-icon img,
[data-theme="black"] .repo-badge-icon img,
[data-theme="black"] .btn-icon img {
filter: brightness(0) saturate(100%) invert(1) !important; /* White in black theme */
opacity: 1 !important;
}
/* Theme icons - handled separately for better control */
.theme-icon img:not(.theme-toggle .theme-icon img),
.theme-icon-option img:not(.theme-option .theme-icon-option img) {
filter: brightness(0) saturate(100%) invert(1) !important; /* Default white for dark backgrounds */
opacity: 1 !important;
transition: filter 0.3s ease, opacity 0.3s ease;
}
/* Light theme: theme icons should be dark */
[data-theme="light"] .theme-icon img:not(.theme-toggle .theme-icon img),
[data-theme="light"] .theme-icon-option img:not(.theme-option .theme-icon-option img) {
filter: brightness(0) saturate(100%) !important; /* Black in light theme */
opacity: 1 !important;
} }
.icon-inline { .icon-inline {
@ -692,11 +730,71 @@ textarea:disabled::placeholder {
} }
/* Ensure icons in buttons and interactive elements have proper contrast */ /* Ensure icons in buttons and interactive elements have proper contrast */
button img.icon,
button .icon img,
button img.icon-inline,
button .icon-inline img, button .icon-inline img,
button img.icon-small,
button .icon-small img, button .icon-small img,
.button img.icon,
.button .icon img,
.button img.icon-inline,
.button .icon-inline img, .button .icon-inline img,
.button img.icon-small,
.button .icon-small img { .button .icon-small img {
filter: var(--icon-filter, brightness(0) saturate(100%) invert(var(--icon-invert, 0))); filter: brightness(0) saturate(100%) invert(1) !important; /* Default white for dark themes */
opacity: 1 !important;
}
/* Light theme: button icons should be dark */
[data-theme="light"] button img.icon,
[data-theme="light"] button .icon img,
[data-theme="light"] button img.icon-inline,
[data-theme="light"] button .icon-inline img,
[data-theme="light"] button img.icon-small,
[data-theme="light"] button .icon-small img,
[data-theme="light"] .button img.icon,
[data-theme="light"] .button .icon img,
[data-theme="light"] .button img.icon-inline,
[data-theme="light"] .button .icon-inline img,
[data-theme="light"] .button img.icon-small,
[data-theme="light"] .button .icon-small img {
filter: brightness(0) saturate(100%) !important; /* Black in light theme */
opacity: 1 !important;
}
/* Dark theme (Purple): button icons should be light */
[data-theme="dark"] button img.icon,
[data-theme="dark"] button .icon img,
[data-theme="dark"] button img.icon-inline,
[data-theme="dark"] button .icon-inline img,
[data-theme="dark"] button img.icon-small,
[data-theme="dark"] button .icon-small img,
[data-theme="dark"] .button img.icon,
[data-theme="dark"] .button .icon img,
[data-theme="dark"] .button img.icon-inline,
[data-theme="dark"] .button .icon-inline img,
[data-theme="dark"] .button img.icon-small,
[data-theme="dark"] .button .icon-small img {
filter: brightness(0) saturate(100%) invert(1) !important; /* White in dark/purple theme */
opacity: 1 !important;
}
/* Black theme: button icons should be light */
[data-theme="black"] button img.icon,
[data-theme="black"] button .icon img,
[data-theme="black"] button img.icon-inline,
[data-theme="black"] button .icon-inline img,
[data-theme="black"] button img.icon-small,
[data-theme="black"] button .icon-small img,
[data-theme="black"] .button img.icon,
[data-theme="black"] .button .icon img,
[data-theme="black"] .button img.icon-inline,
[data-theme="black"] .button .icon-inline img,
[data-theme="black"] .button img.icon-small,
[data-theme="black"] .button .icon-small img {
filter: brightness(0) saturate(100%) invert(1) !important; /* White in black theme */
opacity: 1 !important;
} }
/* Icons on accent backgrounds should be white */ /* Icons on accent backgrounds should be white */
@ -705,7 +803,8 @@ button[class*="primary"] .icon-small img,
.button[class*="primary"] .icon-inline img, .button[class*="primary"] .icon-inline img,
.button[class*="primary"] .icon-small img, .button[class*="primary"] .icon-small img,
.theme-toggle:hover .theme-icon img { .theme-toggle:hover .theme-icon img {
filter: brightness(0) saturate(100%) invert(1); filter: brightness(0) saturate(100%) invert(1) !important;
opacity: 1 !important;
} }
/* Theme toggle icons - ensure high contrast with maximum specificity */ /* Theme toggle icons - ensure high contrast with maximum specificity */
@ -1330,30 +1429,33 @@ button.theme-option.active img.theme-icon-option,
color: var(--error-text); color: var(--error-text);
} }
/* Code blocks - consistent dark-gray background in both themes */ /* Unified code styling - all code uses highlight.js standard theme with dark-gray background */
code {
background: var(--bg-secondary); /* Inline code - matches highlight.js theme */
code:not(pre code) {
background: #1e1e1e !important;
color: #d4d4d4 !important;
padding: 0.125rem 0.25rem; padding: 0.125rem 0.25rem;
border-radius: 0.25rem; border-radius: 0.25rem;
font-family: 'IBM Plex Mono', monospace; font-family: 'IBM Plex Mono', monospace;
font-size: 0.875em; font-size: 0.875em;
color: var(--text-primary); border: 1px solid #3a3a3a;
} }
/* Pre wrappers are transparent - only code.hljs has styling */
pre { pre {
background: #1e1e1e; /* Consistent dark-gray background */ margin: 0;
color: #d4d4d4; /* Light gray text for good contrast */ padding: 0;
padding: 1rem; background: transparent;
border-radius: 0.5rem; border: none;
overflow-x: auto; overflow: visible;
margin: 1rem 0;
border: 1px solid #3a3a3a;
} }
pre code { /* Code blocks - highlight.js handles everything */
background: transparent; pre code.hljs {
padding: 0; display: block;
color: inherit; width: 100%;
box-sizing: border-box;
} }
.clone-urls { .clone-urls {
@ -1365,14 +1467,14 @@ pre code {
.clone-urls code { .clone-urls code {
display: block; display: block;
background: var(--bg-secondary); background: #1e1e1e !important; /* Dark-gray background - theme independent */
color: #d4d4d4 !important; /* Light gray text - theme independent */
padding: 0.5rem; padding: 0.5rem;
border-radius: 0.25rem; border-radius: 0.25rem;
margin: 0.25rem 0; margin: 0.25rem 0;
font-family: 'IBM Plex Mono', monospace; font-family: 'IBM Plex Mono', monospace;
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-primary); border: 1px solid #3a3a3a;
border: 1px solid var(--border-light);
word-break: break-all; word-break: break-all;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
@ -1806,93 +1908,98 @@ label.filter-checkbox > span,
/* All pages use the same container width for consistency */ /* All pages use the same container width for consistency */
/* Highlight.js Syntax Highlighting - Consistent dark-gray background */ /* Highlight.js - unified code block styling - theme independent */
.hljs { .hljs {
background: #1e1e1e !important; /* Consistent dark-gray background in both themes */ display: block;
color: #d4d4d4 !important; /* Light gray text for good contrast */ background: #1e1e1e !important;
border: 1px solid #3a3a3a; color: #d4d4d4 !important;
border-radius: 4px; border: 1px solid #3a3a3a !important;
padding: 1rem; border-radius: 4px !important;
padding: 1rem !important;
overflow-x: auto; overflow-x: auto;
margin: 0;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.875rem;
line-height: 1.5;
} }
/* Syntax highlighting colors - high contrast for dark-gray background */ /* Syntax highlighting colors - high contrast for dark-gray background - theme independent */
.hljs-comment, .hljs-comment,
.hljs-quote { .hljs-quote {
color: #6a9955; /* Green comments - good contrast */ color: #6a9955 !important; /* Green comments - good contrast */
font-style: italic; font-style: italic;
} }
.hljs-keyword, .hljs-keyword,
.hljs-selector-tag, .hljs-selector-tag,
.hljs-subst { .hljs-subst {
color: #c586c0; /* Purple/magenta keywords - good contrast */ color: #c586c0 !important; /* Purple/magenta keywords - good contrast */
font-weight: 500; font-weight: 500;
} }
.hljs-number, .hljs-number,
.hljs-literal { .hljs-literal {
color: #b5cea8; /* Light green numbers - good contrast */ color: #b5cea8 !important; /* Light green numbers - good contrast */
font-weight: 500; font-weight: 500;
} }
.hljs-variable, .hljs-variable,
.hljs-template-variable, .hljs-template-variable,
.hljs-tag .hljs-attr { .hljs-tag .hljs-attr {
color: #9cdcfe; /* Light blue variables - good contrast */ color: #9cdcfe !important; /* Light blue variables - good contrast */
} }
.hljs-string, .hljs-string,
.hljs-doctag { .hljs-doctag {
color: #ce9178; /* Orange strings - good contrast */ color: #ce9178 !important; /* Orange strings - good contrast */
} }
.hljs-title, .hljs-title,
.hljs-section, .hljs-section,
.hljs-selector-id { .hljs-selector-id {
color: #dcdcaa; /* Yellow titles - good contrast */ color: #dcdcaa !important; /* Yellow titles - good contrast */
font-weight: 600; font-weight: 600;
} }
.hljs-type, .hljs-type,
.hljs-class .hljs-title { .hljs-class .hljs-title {
color: #4ec9b0; /* Cyan types - good contrast */ color: #4ec9b0 !important; /* Cyan types - good contrast */
font-weight: 500; font-weight: 500;
} }
.hljs-tag, .hljs-tag,
.hljs-name, .hljs-name,
.hljs-attribute { .hljs-attribute {
color: #569cd6; /* Blue tags - good contrast */ color: #569cd6 !important; /* Blue tags - good contrast */
} }
.hljs-regexp, .hljs-regexp,
.hljs-link { .hljs-link {
color: #d16969; /* Red regexp - good contrast */ color: #d16969 !important; /* Red regexp - good contrast */
} }
.hljs-symbol, .hljs-symbol,
.hljs-bullet { .hljs-bullet {
color: #dcdcaa; /* Yellow symbols - good contrast */ color: #dcdcaa !important; /* Yellow symbols - good contrast */
} }
.hljs-built_in, .hljs-built_in,
.hljs-builtin-name { .hljs-builtin-name {
color: #4ec9b0; /* Cyan built-ins - good contrast */ color: #4ec9b0 !important; /* Cyan built-ins - good contrast */
} }
.hljs-meta { .hljs-meta {
color: #808080; /* Gray meta - good contrast */ color: #808080 !important; /* Gray meta - good contrast */
} }
.hljs-deletion { .hljs-deletion {
background: #4a1f1f; /* Dark red background */ background: #4a1f1f !important; /* Dark red background */
color: #ff8a8a; /* Light red text */ color: #ff8a8a !important; /* Light red text */
} }
.hljs-addition { .hljs-addition {
background: #1a3a2a; /* Dark green background */ background: #1a3a2a !important; /* Dark green background */
color: #6aff9a; /* Light green text */ color: #6aff9a !important; /* Light green text */
} }
.hljs-emphasis { .hljs-emphasis {

9
src/lib/components/RepoHeaderEnhanced.svelte

@ -41,6 +41,7 @@
hasUnlimitedAccess?: boolean; hasUnlimitedAccess?: boolean;
needsClone?: boolean; needsClone?: boolean;
allMaintainers?: Array<{ pubkey: string; isOwner: boolean }>; allMaintainers?: Array<{ pubkey: string; isOwner: boolean }>;
onCopyEventId?: () => void;
} }
let { let {
@ -80,7 +81,8 @@
deletingAnnouncement = false, deletingAnnouncement = false,
hasUnlimitedAccess = false, hasUnlimitedAccess = false,
needsClone = false, needsClone = false,
allMaintainers = [] allMaintainers = [],
onCopyEventId
}: Props = $props(); }: Props = $props();
let showCloneMenu = $state(false); let showCloneMenu = $state(false);
@ -184,6 +186,11 @@
Create New Branch Create New Branch
</button> </button>
{/if} {/if}
{#if onCopyEventId}
<button class="menu-item" onclick={() => { onCopyEventId(); showMoreMenu = false; }}>
Copy Event ID
</button>
{/if}
{#if onDeleteAnnouncement} {#if onDeleteAnnouncement}
<button <button
class="menu-item menu-item-danger" class="menu-item menu-item-danger"

12
src/lib/styles/components.css

@ -115,21 +115,21 @@
height: 18px; height: 18px;
flex-shrink: 0; flex-shrink: 0;
/* Theme-aware icon colors */ /* Theme-aware icon colors */
filter: brightness(0) saturate(100%) invert(1); /* Default white for dark themes */ filter: brightness(0) saturate(100%) invert(1) !important; /* Default white for dark themes */
opacity: 1; opacity: 1 !important;
} }
/* Light theme: black icon */ /* Light theme: black icon */
:global([data-theme="light"]) .icon { :global([data-theme="light"]) .icon {
filter: brightness(0) saturate(100%); /* Black in light theme */ filter: brightness(0) saturate(100%) !important; /* Black in light theme */
opacity: 1; opacity: 1 !important;
} }
/* Dark themes: white icon */ /* Dark themes: white icon */
:global([data-theme="dark"]) .icon, :global([data-theme="dark"]) .icon,
:global([data-theme="black"]) .icon { :global([data-theme="black"]) .icon {
filter: brightness(0) saturate(100%) invert(1); /* White in dark themes */ filter: brightness(0) saturate(100%) invert(1) !important; /* White in dark themes */
opacity: 1; opacity: 1 !important;
} }
.repo-description { .repo-description {

2176
src/lib/styles/repo.css

File diff suppressed because it is too large Load Diff

2
src/routes/api/repos/[npub]/[repo]/download/+server.ts

@ -17,6 +17,8 @@ import { handleApiError, handleNotFoundError } from '$lib/utils/error-handler.js
import { KIND } from '$lib/types/nostr.js'; import { KIND } from '$lib/types/nostr.js';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { repoCache, RepoCache } from '$lib/services/git/repo-cache.js'; import { repoCache, RepoCache } from '$lib/services/git/repo-cache.js';
import { eventCache } from '$lib/services/nostr/event-cache.js';
import { fetchRepoAnnouncementsWithCache, findRepoAnnouncement } from '$lib/utils/nostr-utils.js';
const repoRoot = typeof process !== 'undefined' && process.env?.GIT_REPO_ROOT const repoRoot = typeof process !== 'undefined' && process.env?.GIT_REPO_ROOT
? process.env.GIT_REPO_ROOT ? process.env.GIT_REPO_ROOT

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

@ -227,6 +227,26 @@
let isRepoCloned = $state<boolean | null>(null); // null = unknown, true = cloned, false = not cloned let isRepoCloned = $state<boolean | null>(null); // null = unknown, true = cloned, false = not cloned
let checkingCloneStatus = $state(false); let checkingCloneStatus = $state(false);
let cloning = $state(false); let cloning = $state(false);
// Word wrap toggle
let wordWrap = $state(false);
// Function to toggle word wrap and refresh highlighting
async function toggleWordWrap() {
wordWrap = !wordWrap;
console.log('Word wrap toggled:', wordWrap);
// Force DOM update by accessing the element
await new Promise(resolve => {
requestAnimationFrame(() => {
requestAnimationFrame(resolve);
});
});
// Re-apply syntax highlighting to refresh the display
if (currentFile && fileContent) {
const ext = currentFile.split('.').pop() || '';
await applySyntaxHighlighting(fileContent, ext);
}
}
let copyingCloneUrl = $state(false); let copyingCloneUrl = $state(false);
// Helper: Check if repo needs to be cloned for write operations // Helper: Check if repo needs to be cloned for write operations
@ -707,6 +727,10 @@
// Mobile view toggle for file list/file viewer // Mobile view toggle for file list/file viewer
let showFileListOnMobile = $state(true); let showFileListOnMobile = $state(true);
// Guard to prevent README auto-load loop
let readmeAutoLoadAttempted = $state(false);
let readmeAutoLoadTimeout: ReturnType<typeof setTimeout> | null = null;
async function loadReadme() { async function loadReadme() {
if (repoNotFound) return; if (repoNotFound) return;
loadingReadme = true; loadingReadme = true;
@ -1838,6 +1862,10 @@
clearInterval(autoSaveInterval); clearInterval(autoSaveInterval);
autoSaveInterval = null; autoSaveInterval = null;
} }
if (readmeAutoLoadTimeout) {
clearTimeout(readmeAutoLoadTimeout);
readmeAutoLoadTimeout = null;
}
}); });
async function checkAuth() { async function checkAuth() {
@ -2031,6 +2059,59 @@
} }
} }
async function copyEventId() {
if (!repoAddress || !repoOwnerPubkey) {
alert('Repository address not available');
return;
}
try {
// Parse the repo address: kind:pubkey:identifier
const parts = repoAddress.split(':');
if (parts.length < 3) {
throw new Error('Invalid repository address format');
}
const kind = parseInt(parts[0]);
const pubkey = parts[1];
const identifier = parts.slice(2).join(':'); // In case identifier contains ':'
// Generate naddr synchronously
const naddr = nip19.naddrEncode({
kind,
pubkey,
identifier,
relays: [] // Optional: could include relays if available
});
// Copy naddr to clipboard immediately (while we have user activation)
try {
await navigator.clipboard.writeText(naddr);
} catch (clipboardErr) {
// Fallback: use execCommand for older browsers or if clipboard API fails
const textArea = document.createElement('textarea');
textArea.value = naddr;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
textArea.remove();
} catch (execErr) {
textArea.remove();
throw new Error('Failed to copy to clipboard. Please copy manually: ' + naddr);
}
}
// Show message with naddr
alert(`Event ID copied to clipboard!\n\nnaddr (repository address):\n${naddr}`);
} catch (err) {
console.error('Failed to copy event ID:', err);
alert(`Failed to copy event ID: ${err instanceof Error ? err.message : String(err)}`);
}
}
async function checkMaintainerStatus() { async function checkMaintainerStatus() {
if (repoNotFound || !userPubkey) { if (repoNotFound || !userPubkey) {
isMaintainer = false; isMaintainer = false;
@ -2380,14 +2461,40 @@
currentPath = path; currentPath = path;
// Auto-load README if we're in the root directory and no file is currently selected // Auto-load README if we're in the root directory and no file is currently selected
if (path === '' && !currentFile) { // Only attempt once per path to prevent loops
if (path === '' && !currentFile && !readmeAutoLoadAttempted) {
const readmeFile = findReadmeFile(files); const readmeFile = findReadmeFile(files);
if (readmeFile) { if (readmeFile) {
readmeAutoLoadAttempted = true;
// Clear any existing timeout
if (readmeAutoLoadTimeout) {
clearTimeout(readmeAutoLoadTimeout);
}
// Small delay to ensure UI is ready // Small delay to ensure UI is ready
readmeAutoLoadTimeout = setTimeout(() => {
loadFile(readmeFile.path).catch(err => {
// If load fails (e.g., 429 rate limit), reset the flag after a delay
// so we can retry later, but not immediately
if (err instanceof Error && err.message.includes('Too Many Requests')) {
console.warn('[README] Rate limited, will retry later');
setTimeout(() => { setTimeout(() => {
loadFile(readmeFile.path); readmeAutoLoadAttempted = false;
}, 5000); // Retry after 5 seconds
} else {
// For other errors, reset immediately
readmeAutoLoadAttempted = false;
}
});
readmeAutoLoadTimeout = null;
}, 100); }, 100);
} }
} else if (path !== '' || currentFile) {
// Reset flag when navigating away from root or when a file is selected
readmeAutoLoadAttempted = false;
if (readmeAutoLoadTimeout) {
clearTimeout(readmeAutoLoadTimeout);
readmeAutoLoadTimeout = null;
}
} }
} catch (err) { } catch (err) {
error = err instanceof Error ? err.message : 'Failed to load files'; error = err instanceof Error ? err.message : 'Failed to load files';
@ -2458,6 +2565,12 @@
}); });
if (!response.ok) { if (!response.ok) {
// Handle rate limiting specifically to prevent loops
if (response.status === 429) {
const error = new Error(`Failed to load file: Too Many Requests`);
console.warn('[File Load] Rate limited, please wait before retrying');
throw error;
}
throw new Error(`Failed to load file: ${response.statusText}`); throw new Error(`Failed to load file: ${response.statusText}`);
} }
@ -2467,6 +2580,11 @@
currentFile = filePath; currentFile = filePath;
hasChanges = false; hasChanges = false;
// Reset README auto-load flag when a file is successfully loaded
if (filePath && filePath.toLowerCase().includes('readme')) {
readmeAutoLoadAttempted = false;
}
// Determine language from file extension // Determine language from file extension
const ext = filePath.split('.').pop()?.toLowerCase(); const ext = filePath.split('.').pop()?.toLowerCase();
if (ext === 'md' || ext === 'markdown') { if (ext === 'md' || ext === 'markdown') {
@ -3709,6 +3827,7 @@
hasUnlimitedAccess={hasUnlimitedAccess($userStore.userLevel)} hasUnlimitedAccess={hasUnlimitedAccess($userStore.userLevel)}
needsClone={needsClone} needsClone={needsClone}
allMaintainers={allMaintainers} allMaintainers={allMaintainers}
onCopyEventId={copyEventId}
/> />
{/if} {/if}
@ -3810,6 +3929,14 @@
onTabChange={(tab) => activeTab = tab as typeof activeTab} onTabChange={(tab) => activeTab = tab as typeof activeTab}
/> />
<h2>Files</h2> <h2>Files</h2>
<button
onclick={toggleWordWrap}
class="word-wrap-button"
title={wordWrap ? 'Disable word wrap' : 'Enable word wrap'}
aria-label={wordWrap ? 'Disable word wrap' : 'Enable word wrap'}
>
{wordWrap ? 'Wrap' : 'No Wrap'}
</button>
<div class="file-tree-actions"> <div class="file-tree-actions">
{#if pathStack.length > 0 || currentPath} {#if pathStack.length > 0 || currentPath}
<button onclick={handleBack} class="back-button">← Back</button> <button onclick={handleBack} class="back-button">← Back</button>
@ -4148,7 +4275,7 @@
readOnly={needsClone} readOnly={needsClone}
/> />
{:else} {:else}
<div class="read-only-editor"> <div class="read-only-editor" class:word-wrap={wordWrap}>
{#if highlightedFileContent} {#if highlightedFileContent}
{@html highlightedFileContent} {@html highlightedFileContent}
{:else} {:else}
@ -5078,3 +5205,39 @@
</div> </div>
{/if} {/if}
</div> </div>
<style>
/* Word wrap styles - ensure they apply with highest specificity */
:global(.read-only-editor.word-wrap) {
overflow-x: hidden !important;
}
:global(.read-only-editor.word-wrap pre) {
white-space: pre-wrap !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
overflow-x: hidden !important;
overflow-y: visible !important;
max-width: 100% !important;
}
:global(.read-only-editor.word-wrap pre code),
:global(.read-only-editor.word-wrap pre code.hljs),
:global(.read-only-editor.word-wrap code.hljs),
:global(.read-only-editor.word-wrap .hljs) {
white-space: pre-wrap !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
overflow-x: hidden !important;
overflow-y: visible !important;
display: block !important;
max-width: 100% !important;
}
:global(.read-only-editor.word-wrap pre code.hljs *),
:global(.read-only-editor.word-wrap pre code.hljs span),
:global(.read-only-editor.word-wrap code.hljs *),
:global(.read-only-editor.word-wrap .hljs *) {
white-space: pre-wrap !important;
}
</style>

Loading…
Cancel
Save