You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

450 lines
16 KiB

/**
* Repository effects hooks
* Extracted $effect blocks from +page.svelte for better organization
*
* Note: These hooks must be called from within a Svelte component context
* where $page and $userStore runes are available
*/
import { settingsStore } from '$lib/services/settings-store.js';
import type { RepoState } from '../stores/repo-state.js';
/**
* Sync pageData from $page store
* Returns effect callback that should be called within $effect in component
*/
export function usePageDataEffect(state: RepoState, getPageData: () => any): () => void {
return () => {
if (typeof window === 'undefined' || !state.isMounted) return;
try {
const data = getPageData();
if (data && state.isMounted) {
state.pageData = data || {};
}
} catch (err) {
if (state.isMounted) {
console.warn('Failed to update pageData:', err);
}
}
};
}
/**
* Sync params from $page store
* Returns effect callback that should be called within $effect in component
*/
export function usePageParamsEffect(state: RepoState, getPageParams: () => { npub?: string; repo?: string }): () => void {
return () => {
if (typeof window === 'undefined' || !state.isMounted) return;
try {
const params = getPageParams();
if (params && state.isMounted) {
if (params.npub && params.npub !== state.npub) state.npub = params.npub;
if (params.repo && params.repo !== state.repo) state.repo = params.repo;
}
} catch {
if (!state.isMounted) return;
try {
if (typeof window !== 'undefined') {
const pathParts = window.location.pathname.split('/').filter(Boolean);
if (pathParts[0] === 'repos' && pathParts[1] && pathParts[2] && state.isMounted) {
state.npub = pathParts[1];
state.repo = pathParts[2];
}
}
} catch {
// Ignore errors - params will be set eventually
}
}
};
}
/**
* Load maintainers when repo data is available
* Returns effect callback that should be called within $effect in component
*/
export function useMaintainersEffect(
state: RepoState,
getRepoOwnerPubkey: () => string,
getRepoMaintainers: () => string[],
loadAllMaintainers: () => Promise<void>,
getPageData: () => any
): () => void {
return () => {
if (typeof window === 'undefined' || !state.isMounted) return;
try {
const data = getPageData();
if (!data || !state.isMounted) return;
const currentRepoKey = `${state.npub}/${state.repo}`;
if (currentRepoKey !== state.maintainers.lastRepoKey && state.isMounted) {
state.maintainers.loaded = false;
state.maintainers.effectRan = false;
state.maintainers.lastRepoKey = currentRepoKey;
}
const repoOwnerPubkeyDerived = getRepoOwnerPubkey();
const repoMaintainers = getRepoMaintainers();
if (state.isMounted &&
(repoOwnerPubkeyDerived || (repoMaintainers && repoMaintainers.length > 0)) &&
!state.maintainers.effectRan &&
!state.loading.maintainers) {
state.maintainers.effectRan = true;
state.maintainers.loaded = true;
loadAllMaintainers().catch(err => {
if (!state.isMounted) return;
state.maintainers.loaded = false;
state.maintainers.effectRan = false;
console.warn('Failed to load maintainers:', err);
});
}
} catch (err) {
if (state.isMounted) {
console.warn('Maintainers effect error:', err);
}
}
};
}
/**
* Watch auto-save settings and manage auto-save interval
* Returns effect callback that should be called within $effect in component
*/
export function useAutoSaveEffect(
state: RepoState,
autoSaveInterval: { value: ReturnType<typeof setInterval> | null },
setupAutoSave: () => void
): () => void {
return () => {
if (!state.isMounted) return;
settingsStore.getSettings().then(settings => {
if (!state.isMounted) return;
if (settings.autoSave && !autoSaveInterval.value) {
setupAutoSave();
} else if (!settings.autoSave && autoSaveInterval.value) {
if (autoSaveInterval.value) {
clearInterval(autoSaveInterval.value);
autoSaveInterval.value = null;
}
}
}).catch(err => {
if (state.isMounted) {
console.warn('Failed to check auto-save setting:', err);
}
});
};
}
/**
* Sync user state from userStore and reload data on login/logout
* Returns effect callback that should be called within $effect in component
*/
export function useUserStoreEffect(
state: RepoState,
cachedUserData: { email: string | null; name: string | null },
getUserStore: () => any,
callbacks: {
checkMaintainerStatus: () => Promise<void>;
loadBookmarkStatus: () => Promise<void>;
loadAllMaintainers: () => Promise<void>;
checkCloneStatus: (force: boolean) => Promise<void>;
loadBranches: () => Promise<void>;
loadFiles: (path?: string) => Promise<void>;
loadReadme: () => Promise<void>;
loadTags: () => Promise<void>;
loadDiscussions: () => Promise<void>;
}
): () => void {
return () => {
if (!state.isMounted) return;
try {
const currentUser = getUserStore();
if (!currentUser || !state.isMounted) return;
const wasLoggedIn = state.user.pubkey !== null || state.user.pubkeyHex !== null;
if (currentUser.userPubkey && currentUser.userPubkeyHex && state.isMounted) {
const wasDifferent = state.user.pubkey !== currentUser.userPubkey || state.user.pubkeyHex !== currentUser.userPubkeyHex;
state.user.pubkey = currentUser.userPubkey;
state.user.pubkeyHex = currentUser.userPubkeyHex;
if (wasDifferent && state.isMounted) {
state.loading.repoNotFound = false;
cachedUserData.email = null;
cachedUserData.name = null;
if (!state.isMounted) return;
callbacks.checkMaintainerStatus().catch(err => {
if (state.isMounted) console.warn('Failed to reload maintainer status after login:', err);
});
callbacks.loadBookmarkStatus().catch(err => {
if (state.isMounted) console.warn('Failed to reload bookmark status after login:', err);
});
state.maintainers.loaded = false;
state.maintainers.effectRan = false;
state.maintainers.lastRepoKey = null;
callbacks.loadAllMaintainers().catch(err => {
if (state.isMounted) console.warn('Failed to reload maintainers after login:', err);
});
setTimeout(() => {
if (state.isMounted) {
callbacks.checkCloneStatus(true).catch(err => {
if (state.isMounted) console.warn('Failed to recheck clone status after login:', err);
});
}
}, 100);
if (!state.loading.main && state.isMounted) {
callbacks.loadBranches().catch(err => {
if (state.isMounted) console.warn('Failed to reload branches after login:', err);
});
callbacks.loadFiles().catch(err => {
if (state.isMounted) console.warn('Failed to reload files after login:', err);
});
callbacks.loadReadme().catch(err => {
if (state.isMounted) console.warn('Failed to reload readme after login:', err);
});
callbacks.loadTags().catch(err => {
if (state.isMounted) console.warn('Failed to reload tags after login:', err);
});
callbacks.loadDiscussions().catch(err => {
if (state.isMounted) console.warn('Failed to reload discussions after login:', err);
});
}
}
} else if (state.isMounted) {
state.user.pubkey = null;
state.user.pubkeyHex = null;
cachedUserData.email = null;
cachedUserData.name = null;
if (wasLoggedIn && state.isMounted) {
callbacks.checkMaintainerStatus().catch(err => {
if (state.isMounted) console.warn('Failed to reload maintainer status after logout:', err);
});
callbacks.loadBookmarkStatus().catch(err => {
if (state.isMounted) console.warn('Failed to reload bookmark status after logout:', err);
});
state.maintainers.loaded = false;
state.maintainers.effectRan = false;
state.maintainers.lastRepoKey = null;
callbacks.loadAllMaintainers().catch(err => {
if (state.isMounted) console.warn('Failed to reload maintainers after logout:', err);
});
if (!state.loading.main && state.ui.activeTab === 'files' && state.isMounted) {
callbacks.loadFiles().catch(err => {
if (state.isMounted) console.warn('Failed to reload files after logout:', err);
});
}
}
}
} catch (err) {
if (state.isMounted) {
console.warn('User store sync error:', err);
}
}
};
}
/**
* Handle tab switching when clone status changes
* Returns effect callback that should be called within $effect in component
*/
export function useTabSwitchEffect(
state: RepoState,
tabs: Array<{ id: string }>,
canUseApiFallback: boolean
): () => void {
return () => {
if (!state.isMounted) return;
if (state.clone.isCloned === false && !canUseApiFallback && tabs.length > 0) {
const currentTab = tabs.find(t => t.id === state.ui.activeTab);
if (!currentTab && state.isMounted) {
state.ui.activeTab = tabs[0].id as typeof state.ui.activeTab;
}
}
};
}
/**
* Update repo images from pageData
* Returns effect callback that should be called within $effect in component
*/
export function useRepoImagesEffect(state: RepoState, getPageData: () => any): () => void {
return () => {
if (typeof window === 'undefined' || !state.isMounted) return;
try {
const data = getPageData();
if (!data || !state.isMounted) return;
if (data.image && data.image !== state.metadata.image && state.isMounted) {
state.metadata.image = data.image;
console.log('[Repo Images] Updated image from pageData (reactive):', state.metadata.image);
}
if (data.banner && data.banner !== state.metadata.banner && state.isMounted) {
state.metadata.banner = data.banner;
console.log('[Repo Images] Updated banner from pageData (reactive):', state.metadata.banner);
}
} catch (err) {
if (state.isMounted) {
console.warn('Image update effect error:', err);
}
}
};
}
/**
* Load patch highlights when patch is selected
* Returns effect callback that should be called within $effect in component
*/
export function usePatchHighlightsEffect(
state: RepoState,
loadPatchHighlights: (patchId: string, author: string) => Promise<void>
): () => void {
return () => {
if (!state.isMounted || !state.selected.patch) return;
const patch = state.patches.find(p => p.id === state.selected.patch);
if (patch) {
loadPatchHighlights(patch.id, patch.author).catch(err => {
if (state.isMounted) console.warn('Failed to load patch highlights:', err);
});
}
};
}
/**
* Load tab content when tab changes
* Returns effect callback that should be called within $effect in component
*/
export function useTabChangeEffect(
state: RepoState,
lastTab: { value: string | null },
findReadmeFile: (files: Array<{ name: string; path: string; type: 'file' | 'directory' }>) => { name: string; path: string; type: 'file' | 'directory' } | null,
callbacks: {
loadFiles: (path?: string) => Promise<void>;
loadFile: (path: string) => Promise<void>;
loadCommitHistory: () => Promise<void>;
loadTags: () => Promise<void>;
loadReleases: () => Promise<void>;
loadIssues: () => Promise<void>;
loadPRs: () => Promise<void>;
loadDocumentation: () => Promise<void>;
loadDiscussions: () => Promise<void>;
loadPatches: () => Promise<void>;
}
): () => void {
return () => {
if (!state.isMounted) return;
if (state.ui.activeTab !== lastTab.value) {
lastTab.value = state.ui.activeTab;
if (!state.isMounted) return;
if (state.ui.activeTab === 'files') {
if (state.files.list.length === 0 || state.files.currentPath !== '') {
callbacks.loadFiles('').catch(err => {
if (state.isMounted) console.warn('Failed to load files:', err);
});
} else if (state.files.list.length > 0 && !state.files.currentFile && state.isMounted) {
const readmeFile = findReadmeFile(state.files.list);
if (readmeFile) {
setTimeout(() => {
if (state.isMounted) {
callbacks.loadFile(readmeFile.path).catch(err => {
if (state.isMounted) console.warn('Failed to load README file:', err);
});
}
}, 100);
}
}
} else if (state.ui.activeTab === 'history' && state.isMounted) {
callbacks.loadCommitHistory().catch(err => {
if (state.isMounted) console.warn('Failed to load commit history:', err);
});
} else if (state.ui.activeTab === 'tags' && state.isMounted) {
callbacks.loadTags().catch(err => {
if (state.isMounted) console.warn('Failed to load tags:', err);
});
callbacks.loadReleases().catch(err => {
if (state.isMounted) console.warn('Failed to load releases:', err);
});
} else if (state.ui.activeTab === 'code-search') {
// Code search is performed on demand, not auto-loaded
} else if (state.ui.activeTab === 'issues' && state.isMounted) {
callbacks.loadIssues().catch(err => {
if (state.isMounted) console.warn('Failed to load issues:', err);
});
} else if (state.ui.activeTab === 'prs' && state.isMounted) {
callbacks.loadPRs().catch(err => {
if (state.isMounted) console.warn('Failed to load PRs:', err);
});
} else if (state.ui.activeTab === 'docs' && state.isMounted) {
callbacks.loadDocumentation().catch(err => {
if (state.isMounted) console.warn('Failed to load documentation:', err);
});
} else if (state.ui.activeTab === 'discussions' && state.isMounted) {
callbacks.loadDiscussions().catch(err => {
if (state.isMounted) console.warn('Failed to load discussions:', err);
});
} else if (state.ui.activeTab === 'patches' && state.isMounted) {
callbacks.loadPatches().catch(err => {
if (state.isMounted) console.warn('Failed to load patches:', err);
});
}
}
};
}
/**
* Reload branch-dependent data when branch changes
* Returns effect callback that should be called within $effect in component
*/
export function useBranchChangeEffect(
state: RepoState,
lastBranch: { value: string | null },
callbacks: {
loadReadme: () => Promise<void>;
loadFile: (path: string) => Promise<void>;
loadFiles: (path: string) => Promise<void>;
loadCommitHistory: () => Promise<void>;
loadDocumentation: () => Promise<void>;
}
): () => void {
return () => {
if (!state.isMounted) return;
if (state.git.currentBranch && state.git.currentBranch !== lastBranch.value) {
lastBranch.value = state.git.currentBranch;
if (!state.isMounted) return;
callbacks.loadReadme().catch(err => {
if (state.isMounted) console.warn('Failed to reload README after branch change:', err);
});
if (state.ui.activeTab === 'files' && state.isMounted) {
if (state.files.currentFile) {
callbacks.loadFile(state.files.currentFile).catch(err => {
if (state.isMounted) console.warn('Failed to reload file after branch change:', err);
});
} else {
callbacks.loadFiles(state.files.currentPath).catch(err => {
if (state.isMounted) console.warn('Failed to reload files after branch change:', err);
});
}
}
if (state.ui.activeTab === 'history' && state.isMounted) {
callbacks.loadCommitHistory().catch(err => {
if (state.isMounted) console.warn('Failed to reload commit history after branch change:', err);
});
}
if (state.ui.activeTab === 'docs' && state.isMounted) {
state.docs.html = null;
state.docs.content = null;
state.docs.kind = null;
callbacks.loadDocumentation().catch(err => {
if (state.isMounted) console.warn('Failed to reload documentation after branch change:', err);
});
}
}
};
}