js • Lines: 130// findProp: local function that queries site data.
// Common config is bundled locally (baseConfig.js) so the page renders correctly.
// Only blog/article contents (swe, music, drafts) are fetched from GitHub Pages at runtime.
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import baseConfig from './baseConfig';
let mergedData = { ...baseConfig };
let contentsPromise = null;
let contentsLoadError = null;
const configListeners = new Set();
const ConfigContext = createContext({
config: mergedData,
findProp: () => undefined,
isContentsLoaded: false,
});
function notifyConfigListeners() {
configListeners.forEach((listener) => listener());
}
function resolveConfigValue(config, path) {
if (!config) return undefined;
const parts = path.split('.');
let obj = config;
for (const part of parts) {
if (obj == null || typeof obj !== 'object') return undefined;
obj = obj[part];
}
return obj !== undefined ? obj : undefined;
}
export function findProp(path) {
return resolveConfigValue(mergedData, path);
}
export function subscribeToConfig(listener) {
configListeners.add(listener);
return () => configListeners.delete(listener);
}
export function useConfig() {
return useContext(ConfigContext);
}
export function ConfigProvider({ children }) {
const [config, setConfig] = useState(mergedData);
const [isContentsLoaded, setIsContentsLoaded] = useState(false);
useEffect(() => {
const unsubscribe = subscribeToConfig(() => {
setConfig({ ...mergedData });
});
let cancelled = false;
loadContents()
.catch(() => undefined)
.finally(() => {
if (!cancelled) {
setIsContentsLoaded(true);
}
});
return () => {
cancelled = true;
unsubscribe();
};
}, []);
const value = useMemo(() => ({
config,
findProp: (path) => resolveConfigValue(config, path),
isContentsLoaded,
}), [config, isContentsLoaded]);
return <ConfigContext.Provider value={value}>{children}</ConfigContext.Provider>;
}
export async function loadContents() {
if (contentsPromise) return contentsPromise;
contentsPromise = (async () => {
const url = 'https://palash90.github.io/site-assets/data.json';
const fallbackUrl = 'https://raw.githubusercontent.com/palash90/site-assets/main/data.json';
async function tryFetch(fetchUrl) {
const res = await fetch(fetchUrl);
if (!res.ok) {
let detail = `HTTP ${res.status}`;
try {
await res.clone().text();
} catch (_) {
detail = `CORS blocked (${res.status})`;
}
throw new Error(detail);
}
return res.json();
}
try {
let remote;
try {
remote = await tryFetch(url);
} catch (primaryErr) {
console.warn('Primary content URL failed, trying fallback:', primaryErr.message);
try {
remote = await tryFetch(fallbackUrl);
} catch (fbErr) {
throw new Error(`Primary: ${primaryErr.message} | Fallback: ${fbErr.message}`);
}
}
if (remote.contents) {
mergedData = { ...baseConfig, ...remote, contents: remote.contents };
notifyConfigListeners();
}
return mergedData.contents;
} catch (err) {
contentsLoadError = err.message;
console.warn('Failed to load contents from GitHub Pages:', err.message);
return null;
}
})();
return contentsPromise;
}
export function getContentsLoadError() {
return contentsLoadError;
}
export default findProp;