diff --git a/nostr/commit-signatures.jsonl b/nostr/commit-signatures.jsonl index a43ff10..5123260 100644 --- a/nostr/commit-signatures.jsonl +++ b/nostr/commit-signatures.jsonl @@ -109,3 +109,4 @@ {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772142558,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","remove redundancy"]],"content":"Signed commit: remove redundancy","id":"11ac91151bebd4dd49b91bcdef7b0b7157f0afd8ce710f7231be4860fb073d08","sig":"a7efcafa5ea83a0c37eae4562a84a7581c3d5c5dd1416f8f3e2bd2633d8523ae0eb7cc56dc4292c127ea16fb2dd5bc639483cb096263a850956b47312ed7ff6f"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772182112,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor 12"]],"content":"Signed commit: refactor 12","id":"73671ae6535309f9eae164f7a3ec403b1bc818ef811b9692fd0122d0b72c2774","sig":"0df56b009f5afb77de334225ab30cff55586ac0cf48f5ee435428201a1e72dc357a0fb5e80ef196f5bd76d6d448056d25f0feab0b1bcbe45f9af1a2a0d5453ca"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772188835,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor 13"]],"content":"Signed commit: refactor 13","id":"f41c8662dcbf1be408c560d11eda0890c40582a8ea8bb3220116e645cc6a2bb5","sig":"2b7b70089cecfa4652fe236fa586a6fe1b05c1c95434a160717cbf5ee2f37382cdd8e8f31d7b3a7576ee5264e9e70c7a8651591caaea0cd311d1be4c561d282f"} +{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772193104,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"02dcdcda1083cffd91dbf8906716c2ae09f06f77ef8590802afecd85f0b3108a","sig":"13d2b30ed37af03fd47dc09536058babb4dc63d1cfc55b8f38651ffd6342abcddc840b543c085b047721e9102b2d07e3dae78ff31d5990c92c04410ef1efcd5b"} diff --git a/src/lib/components/CodeEditor.svelte b/src/lib/components/CodeEditor.svelte index 9cef1a7..9e7c43a 100644 --- a/src/lib/components/CodeEditor.svelte +++ b/src/lib/components/CodeEditor.svelte @@ -79,6 +79,8 @@ autocompletion(), highlightSelectionMatches(), highlightField, + // Enable line wrapping to prevent horizontal overflow + EditorView.lineWrapping, keymap.of([ ...closeBracketsKeymap, ...defaultKeymap, @@ -230,15 +232,54 @@ .code-editor { height: 100%; width: 100%; - overflow: auto; + max-width: 100%; + overflow-x: hidden; + overflow-y: auto; + min-width: 0; + box-sizing: border-box; } :global(.code-editor .cm-editor) { height: 100%; + width: 100%; + max-width: 100%; + min-width: 0; + box-sizing: border-box; + overflow: hidden; } :global(.code-editor .cm-scroller) { - overflow: auto; + overflow-x: hidden !important; + overflow-y: auto; + width: 100%; + max-width: 100%; + min-width: 0; + box-sizing: border-box; + } + + :global(.code-editor .cm-content) { + max-width: 100% !important; + min-width: 0; + box-sizing: border-box; + word-wrap: break-word; + overflow-wrap: break-word; + overflow-x: hidden !important; + width: 100%; + } + + :global(.code-editor .cm-line) { + max-width: 100% !important; + min-width: 0; + box-sizing: border-box; + word-wrap: break-word; + overflow-wrap: break-word; + overflow-x: hidden !important; + } + + :global(.code-editor .cm-line > *) { + max-width: 100% !important; + word-wrap: break-word !important; + overflow-wrap: break-word !important; } :global(.code-editor .cm-highlight-marker) { @@ -246,4 +287,24 @@ padding: 2px 0; border-radius: 2px; } + + /* Prevent any CodeMirror element from causing horizontal overflow */ + :global(.code-editor .cm-gutters), + :global(.code-editor .cm-gutter), + :global(.code-editor .cm-panels), + :global(.code-editor .cm-panel), + :global(.code-editor .cm-focused) { + max-width: 100%; + box-sizing: border-box; + overflow-x: hidden !important; + } + + /* Ensure all text content in CodeMirror wraps */ + :global(.code-editor .cm-content), + :global(.code-editor .cm-line), + :global(.code-editor .cm-lineContent) { + overflow-x: hidden !important; + word-break: break-word; + overflow-wrap: break-word; + } diff --git a/src/lib/components/CommentRenderer.svelte b/src/lib/components/CommentRenderer.svelte index 84c9d9a..023b3b8 100644 --- a/src/lib/components/CommentRenderer.svelte +++ b/src/lib/components/CommentRenderer.svelte @@ -1,3 +1,15 @@ + + {#snippet leftPane()} + {#if isMaintainer && onCreateFile} + + + + New File + + + {/if} ● Unsaved changes {/if} - {#if currentFile && supportsPreview((currentFile.split('.').pop() || '').toLowerCase()) && !isMaintainer} + {#if currentFile && supportsPreview((currentFile.split('.').pop() || '').toLowerCase())} {#if isMaintainer} - { - editedContent = value; - hasChanges = value !== fileContent; - onContentChange(value); - }} - /> + {#if currentFile && showFilePreview && fileHtml && supportsPreview((currentFile.split('.').pop() || '').toLowerCase())} + + + + + + {:else} + { + editedContent = value; + hasChanges = value !== fileContent; + onContentChange(value); + }} + /> + {/if} {:else} {#if isImageFile && imageUrl} @@ -205,13 +260,13 @@ - {:else if highlightedFileContent} - - {@html highlightedFileContent} - - {:else} + {:else if fileContent} - {fileContent} + {#if highlightedFileContent && highlightedFileContent.trim() !== ''} + {@html highlightedFileContent} + {:else} + {fileContent} + {/if} {/if} @@ -244,6 +299,10 @@ flex-direction: column; min-height: 0; overflow: hidden; + width: 100%; + max-width: 100%; + min-width: 0; + box-sizing: border-box; } .file-editor .editor-header { @@ -296,11 +355,13 @@ .raw-content { width: 100%; max-width: 100%; - overflow-x: auto; + overflow-x: hidden; overflow-y: auto; box-sizing: border-box; contain: layout; min-width: 0; + word-wrap: break-word; + overflow-wrap: break-word; } .raw-content pre { @@ -308,8 +369,10 @@ padding: 1rem; background: var(--bg-secondary); border-radius: 4px; - overflow-x: auto; + overflow-x: hidden; + overflow-y: visible; word-wrap: break-word; + overflow-wrap: break-word; white-space: pre-wrap; max-width: 100%; box-sizing: border-box; @@ -319,23 +382,26 @@ .raw-content code { display: block; - overflow-x: auto; + overflow-x: hidden; + overflow-y: visible; max-width: 100%; box-sizing: border-box; width: 100%; min-width: 0; word-break: break-word; overflow-wrap: break-word; + white-space: pre-wrap; } .raw-content :global(code.hljs) { - overflow-x: auto; + overflow-x: hidden !important; + overflow-y: visible !important; display: block; max-width: 100% !important; min-width: 0; - word-break: break-word; - overflow-wrap: break-word; - white-space: pre-wrap; + word-break: break-word !important; + overflow-wrap: break-word !important; + white-space: pre-wrap !important; box-sizing: border-box; } @@ -357,6 +423,7 @@ white-space: pre-wrap !important; display: inline; box-sizing: border-box; + overflow-x: hidden !important; } .raw-content :global(pre code.hljs) { @@ -374,6 +441,20 @@ width: 100%; max-width: 100%; min-width: 0; + box-sizing: border-box; + } + + .editor-container :global(.code-editor) { + width: 100% !important; + max-width: 100% !important; + min-width: 0; + box-sizing: border-box; + } + + .editor-container :global(.code-editor), + .editor-container :global(.code-editor *) { + max-width: 100% !important; + box-sizing: border-box; } .read-only-editor { @@ -400,12 +481,22 @@ .read-only-editor > .raw-content > pre { max-width: 100%; min-width: 0; + overflow-x: hidden !important; + overflow-y: visible !important; + word-wrap: break-word !important; + overflow-wrap: break-word !important; + white-space: pre-wrap !important; } .read-only-editor > .raw-content > pre > code { max-width: 100%; min-width: 0; display: block; + overflow-x: hidden !important; + overflow-y: visible !important; + word-wrap: break-word !important; + overflow-wrap: break-word !important; + white-space: pre-wrap !important; } .file-editor .editor-actions { @@ -419,4 +510,42 @@ width: auto; min-width: 0; } + + .create-file-header { + padding: 0.75rem; + border-bottom: 1px solid var(--border-color); + margin-bottom: 0.5rem; + } + + .create-file-button { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: var(--button-primary); + color: var(--accent-text, #ffffff); + border: none; + border-radius: 4px; + cursor: pointer; + font-family: inherit; + font-size: 0.9rem; + transition: background 0.2s; + } + + .create-file-button:hover:not(:disabled) { + background: var(--button-primary-hover); + } + + .create-file-button:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .create-file-button .icon { + width: 16px; + height: 16px; + filter: brightness(0) invert(1); + } diff --git a/src/routes/repos/[npub]/[repo]/components/TabLayout.svelte b/src/routes/repos/[npub]/[repo]/components/TabLayout.svelte index cb55333..38911c5 100644 --- a/src/routes/repos/[npub]/[repo]/components/TabLayout.svelte +++ b/src/routes/repos/[npub]/[repo]/components/TabLayout.svelte @@ -25,8 +25,7 @@ activeTab = '', tabs = [], onTabChange = () => {}, - title = '', - headerActions + title = '' }: Props = $props(); @@ -112,18 +111,29 @@ } .left-pane { - flex: 0 0 300px; - min-width: 300px; - max-width: 300px; + flex: 0 0 400px; + min-width: 400px; + max-width: 400px; border-right: 1px solid var(--border-color); overflow-y: auto; padding: 1rem; box-sizing: border-box; + background: var(--bg-primary); + color: var(--text-primary); + display: flex; + flex-direction: column; + } + + .left-pane > * { + width: 100%; + min-width: 0; + max-width: 100%; + box-sizing: border-box; } .right-panel { flex: 1 1 auto; - min-width: 0; + min-width: 400px; width: auto; max-width: none; overflow-y: auto; @@ -132,6 +142,8 @@ display: flex; flex-direction: column; box-sizing: border-box; + background: var(--bg-primary); + color: var(--text-primary); } .right-panel > * { diff --git a/src/routes/repos/[npub]/[repo]/components/dialogs/CreateDocumentationDialog.svelte b/src/routes/repos/[npub]/[repo]/components/dialogs/CreateDocumentationDialog.svelte new file mode 100644 index 0000000..73ad73b --- /dev/null +++ b/src/routes/repos/[npub]/[repo]/components/dialogs/CreateDocumentationDialog.svelte @@ -0,0 +1,133 @@ + + + + {#snippet children()} + + Create a documentation event. This will be published to Nostr relays. + + + Event Kind: + + 30818 - Repository State (Asciidoc) + 30041 - Publication (Asciidoc) + 30817 - Repository Announcement + 30023 - Article + + Select the type of documentation event to create + + + Title: + + + + Identifier (d-tag): + + Unique identifier for this documentation event + + + Subject: + + + + Cancel + + {state.saving ? 'Creating...' : 'Create'} + + + {/snippet} + + + diff --git a/src/routes/repos/[npub]/[repo]/components/dialogs/CreateThreadDialog.svelte b/src/routes/repos/[npub]/[repo]/components/dialogs/CreateThreadDialog.svelte index adb742a..3518eeb 100644 --- a/src/routes/repos/[npub]/[repo]/components/dialogs/CreateThreadDialog.svelte +++ b/src/routes/repos/[npub]/[repo]/components/dialogs/CreateThreadDialog.svelte @@ -37,6 +37,7 @@ label { display: block; margin-bottom: 1rem; + color: var(--text-primary); } label input, @@ -46,6 +47,22 @@ margin-top: 0.25rem; border: 1px solid var(--border-color, #e0e0e0); border-radius: 4px; + background-color: var(--bg-primary); + color: var(--text-primary); + font-family: inherit; + } + + label textarea { + font-family: 'IBM Plex Mono', monospace; + font-size: 0.9rem; + resize: vertical; + } + + label input:focus, + label textarea:focus { + outline: none; + border-color: var(--button-primary); + box-shadow: 0 0 0 2px rgba(var(--button-primary-rgb, 220, 20, 60), 0.2); } .modal-actions { diff --git a/src/routes/repos/[npub]/[repo]/components/dialogs/Modal.svelte b/src/routes/repos/[npub]/[repo]/components/dialogs/Modal.svelte index fdddb72..790c1e3 100644 --- a/src/routes/repos/[npub]/[repo]/components/dialogs/Modal.svelte +++ b/src/routes/repos/[npub]/[repo]/components/dialogs/Modal.svelte @@ -7,9 +7,10 @@ ariaLabel?: string; onClose: () => void; children?: Snippet; + maxWidth?: string; } - let { open, title, ariaLabel, onClose, children }: Props = $props(); + let { open, title, ariaLabel, onClose, children, maxWidth = '500px' }: Props = $props(); {#if open} @@ -27,6 +28,7 @@ e.stopPropagation()} onkeydown={(e) => e.stopPropagation()} tabindex="-1" @@ -56,7 +58,6 @@ color: var(--text-primary, #e0e0e0); border-radius: 8px; padding: 1.5rem; - max-width: 500px; width: 90%; max-height: 90vh; overflow-y: auto; diff --git a/src/routes/repos/[npub]/[repo]/services/commit-operations.ts b/src/routes/repos/[npub]/[repo]/services/commit-operations.ts index e504423..320bbb9 100644 --- a/src/routes/repos/[npub]/[repo]/services/commit-operations.ts +++ b/src/routes/repos/[npub]/[repo]/services/commit-operations.ts @@ -20,6 +20,11 @@ export async function loadCommitHistory( state.loading.commits = true; state.error = null; try { + // Use currentBranch, fallback to defaultBranch, then 'master' + const branch = state.git.currentBranch || state.git.defaultBranch || 'master'; + const url = `/api/repos/${state.npub}/${state.repo}/commits?branch=${encodeURIComponent(branch)}&limit=50`; + console.log('[loadCommitHistory] Fetching commits:', { url, branch, currentBranch: state.git.currentBranch, defaultBranch: state.git.defaultBranch }); + const data = await apiRequest>(`/api/repos/${state.npub}/${state.repo}/commits?branch=${state.git.currentBranch}&limit=50`); + }>>(url); + + console.log('[loadCommitHistory] Received data:', { commitCount: data?.length || 0, data }); // Normalize commits: API-based commits use 'sha', local commits use 'hash' state.git.commits = data.map((commit: any) => ({ @@ -38,6 +45,8 @@ export async function loadCommitHistory( files: commit.files || [] })).filter((commit: any) => commit.hash); // Filter out commits without hash + console.log('[loadCommitHistory] Normalized commits:', { count: state.git.commits.length }); + // Verify commits in background (only for cloned repos) if (state.clone.isCloned === true) { state.git.commits.forEach(commit => { @@ -47,6 +56,7 @@ export async function loadCommitHistory( }); } } catch (err) { + console.error('[loadCommitHistory] Error loading commits:', err); state.error = err instanceof Error ? err.message : 'Failed to load commit history'; } finally { state.loading.commits = false; diff --git a/src/routes/repos/[npub]/[repo]/services/file-operations.ts b/src/routes/repos/[npub]/[repo]/services/file-operations.ts index b01ae06..76a9f6c 100644 --- a/src/routes/repos/[npub]/[repo]/services/file-operations.ts +++ b/src/routes/repos/[npub]/[repo]/services/file-operations.ts @@ -591,25 +591,16 @@ export async function loadFile( state.preview.file.showPreview = true; state.preview.file.html = ''; - // Render markdown/asciidoc/HTML/CSV files as HTML for preview - if (state.files.content && (ext === 'md' || ext === 'markdown' || ext === 'adoc' || ext === 'asciidoc' || ext === 'html' || ext === 'htm' || ext === 'csv')) { - await callbacks.renderFileAsHtml(state.files.content, ext || ''); + // ALWAYS apply syntax highlighting for ALL files - this ensures raw view always has highlighting + // We'll apply it regardless of preview mode so it's ready when user switches to raw view + if (state.files.content && state.files.content.trim().length > 0) { + await callbacks.applySyntaxHighlighting(state.files.content, ext || ''); } - // Apply syntax highlighting - // For files that support HTML preview (markdown, HTML, etc.), only show highlighting in raw mode - // For code files and other non-markup files, always show syntax highlighting - const hasHtmlPreview = supportsPreview(ext); - if (state.files.content) { - if (hasHtmlPreview) { - // Markup files: only show highlighting when not in preview mode (raw mode) - if (!state.preview.file.showPreview) { - await callbacks.applySyntaxHighlighting(state.files.content, ext || ''); - } - } else { - // Code files and other non-markup files: always show syntax highlighting - await callbacks.applySyntaxHighlighting(state.files.content, ext || ''); - } + // Render markdown/asciidoc/HTML/CSV files as HTML for preview (if in preview mode) + // This happens after highlighting so both are available + if (state.files.content && (ext === 'md' || ext === 'markdown' || ext === 'adoc' || ext === 'asciidoc' || ext === 'html' || ext === 'htm' || ext === 'csv')) { + await callbacks.renderFileAsHtml(state.files.content, ext || ''); } } } catch (err: any) { diff --git a/src/routes/repos/[npub]/[repo]/stores/repo-state.ts b/src/routes/repos/[npub]/[repo]/stores/repo-state.ts index c3c8943..ed12dc5 100644 --- a/src/routes/repos/[npub]/[repo]/stores/repo-state.ts +++ b/src/routes/repos/[npub]/[repo]/stores/repo-state.ts @@ -38,6 +38,7 @@ export type DialogType = | 'createPR' | 'createPatch' | 'createThread' + | 'createDocumentation' | 'reply' | 'commit' | 'verification' @@ -121,6 +122,13 @@ export interface PatchCommentFormData { replyingTo: string | null; } +export interface DocumentationFormData { + kind: number; // 30818, 30041, 30817, or 30023 + title: string; + identifier: string; + content: string; +} + // Status update tracking export interface StatusUpdates { issue: Record; @@ -220,6 +228,7 @@ export interface RepoState { discussion: DiscussionFormData; patchHighlight: PatchHighlightFormData; patchComment: PatchCommentFormData; + documentation: DocumentationFormData; commit: { message: string; }; @@ -545,6 +554,7 @@ export function createRepoState(): RepoState { discussion: { threadTitle: '', threadContent: '', replyContent: '' }, patchHighlight: { text: '', startLine: 0, endLine: 0, startPos: 0, endPos: 0, comment: '' }, patchComment: { content: '', replyingTo: null }, + documentation: { kind: 30818, title: '', identifier: '', content: '' }, commit: { message: '' } }, issues: [], diff --git a/src/routes/repos/[npub]/[repo]/utils/file-processing.ts b/src/routes/repos/[npub]/[repo]/utils/file-processing.ts index 49fb888..b6f78e4 100644 --- a/src/routes/repos/[npub]/[repo]/utils/file-processing.ts +++ b/src/routes/repos/[npub]/[repo]/utils/file-processing.ts @@ -264,9 +264,11 @@ export async function applySyntaxHighlighting( setHighlightedContent: (html: string) => void ): Promise { try { + console.log('[applySyntaxHighlighting] Starting:', { ext, contentLength: content.length }); const hljsModule = await import('highlight.js'); const hljs = hljsModule.default || hljsModule; const lang = getHighlightLanguage(ext); + console.log('[applySyntaxHighlighting] Language detected:', lang); // Register Markdown language if needed if (lang === 'markdown' && !hljs.getLanguage('markdown')) { @@ -314,15 +316,18 @@ export async function applySyntaxHighlighting( } // Apply highlighting + let highlighted: string; if (lang === 'plaintext') { - setHighlightedContent(`${hljs.highlight(content, { language: 'plaintext' }).value}`); + highlighted = `${hljs.highlight(content, { language: 'plaintext' }).value}`; } else if (hljs.getLanguage(lang)) { - setHighlightedContent(`${hljs.highlight(content, { language: lang }).value}`); + highlighted = `${hljs.highlight(content, { language: lang }).value}`; } else { - setHighlightedContent(`${hljs.highlightAuto(content).value}`); + highlighted = `${hljs.highlightAuto(content).value}`; } + console.log('[applySyntaxHighlighting] Highlighting complete, setting content:', { highlightedLength: highlighted.length }); + setHighlightedContent(highlighted); } catch (err) { - console.error('Error applying syntax highlighting:', err); + console.error('[applySyntaxHighlighting] Error applying syntax highlighting:', err); setHighlightedContent(`${escapeHtml(content)}`); } }
{fileContent}
+ Create a documentation event. This will be published to Nostr relays. +
${hljs.highlight(content, { language: 'plaintext' }).value}
${hljs.highlight(content, { language: lang }).value}
${hljs.highlightAuto(content).value}
${escapeHtml(content)}