diff --git a/app/[slug]/page.tsx b/app/[slug]/page.tsx index 3dd074d..e469e6c 100644 --- a/app/[slug]/page.tsx +++ b/app/[slug]/page.tsx @@ -222,7 +222,7 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => { {/* Tools Panel - 20% width, fixed */} {development_tools_list?.length > 0 && (
-
+

Other Tools @@ -358,9 +358,8 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => { } // Handle regular description text - const descriptions = desc?.description || ''; - const splitDescriptions = - descriptions.split(/((['"]).*?\2)/); // Split quoted and unquoted text + const descriptions = desc?.description || ""; + const splitDescriptions = descriptions.split(/((['"]).*?\2)/); // Split quoted and unquoted text return (

{ {splitDescriptions.map( (text: any, subIndex: any) => { const isQuoted = - typeof text === 'string' && - /^(['"]).*\1$/.test(text); + typeof text === "string" && /^(['"]).*\1$/.test(text); const containsBetterBugs = text.includes('BetterBugs.io'); @@ -382,13 +380,7 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => { - + {parts[0]} { > BetterBugs.io - + {parts[1]} @@ -463,9 +449,7 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => { )} {development_tools_user_agent_info?.example_string_description && (

- { - development_tools_user_agent_info?.example_string_description - } + {development_tools_user_agent_info?.example_string_description}

)} {development_tools_user_agent_info?.info_items && ( @@ -501,16 +485,21 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => {

{development_tools_steps_guide?.guide_description - ?.split(/(".*?")/g) - ?.map((parts: any, i: any) => - parts?.startsWith('') && parts?.endsWith('') ? ( + ?.split(/((['"]).*?\2)/g) + ?.map((parts: any, i: any) => { + const isQuoted = + typeof parts === "string" && + /^(['"]).*\1$/.test(parts); + return isQuoted ? ( {parts} ) : ( - {parts} - ) - )} + + {parts} + + ); + })}

)} @@ -522,8 +511,8 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => { const description2 = guide?.step_description2; // Split quoted and unquoted text - const parts = description?.split(/(".*?")/); - const desParts = description2?.split(/(".*?")/); + const parts = description?.split(/((['"]).*?\2)/); + const desParts = description2?.split(/((['"]).*?\2)/); return (
@@ -541,174 +530,36 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => { {guide?.step_title} - {guide?.steps_points?.length > 0 && ( -
    - {guide?.steps_points?.map( - (p: any, index: number) => ( -
  • - {p?.steps_points_title && ( - - {p?.steps_points_title} - - )} - {p?.steps_points_description && ( -

    - {p?.steps_points_description - ?.split(/(".*?")/) - .map( - ( - part: string, - i: number - ) => - part.startsWith('') && - part.endsWith('') ? ( - - {part} - - ) : ( - - {part - .split( - /(\/\/.*?\/\/)/ - ) - .map( - ( - sub: string, - j: number - ) => - sub.startsWith( - '//' - ) && - sub.endsWith( - '//' - ) ? ( - - {sub.slice( - 2, - -2 - )} - - ) : ( - sub - ) - )} - - ) - )} -

    - )} - {Array.isArray(p?.steps_subpoint) && - p?.steps_subpoint?.length > 0 && ( -
      - {p?.steps_subpoint?.map( - ( - sub_p: any, - subIndex: number - ) => ( -
    • - {sub_p?.title && ( - - {sub_p?.title} - - )} - {sub_p?.description && ( - - {sub_p?.description - ?.split(/(".*?")/) - .map( - ( - part: string, - i: number - ) => - part.startsWith( - '' - ) && - part.endsWith( - '' - ) ? ( - - {part} - - ) : ( - - {part - .split( - /(\/\/.*?\/\/)/ - ) - .map( - ( - sub: string, - j: number - ) => - sub.startsWith( - '//' - ) && - sub.endsWith( - '//' - ) ? ( - - {sub.slice( - 2, - -2 - )} - - ) : ( - sub - ) - )} - - ) - )} - - )} -
    • - ) - )} -
    - )} -
  • - ) - )} -
- )} - - {parts?.map((part: any, i: any) => - part.startsWith('') && - part.endsWith('') ? ( - <> - - {part} - - - ) : ( - part - ) - )} -
+ + {/* Step descriptions (below title) */} + {description && ( +
+ {parts?.map((part: any, i: any) => { + const isQuoted = + typeof part === "string" && + /^(['"]).*\1$/.test(part); + return isQuoted ? ( + + {part} + + ) : ( + + {part} + + ); + })} +
+ )} + {/* Step Description2 on a New Line, Only If Present */} {description2 && desParts?.length > 0 && ( -
- {desParts?.map((part: any, i: any) => - part.startsWith('') && - part.endsWith('') ? ( +
+ {desParts?.map((part: any, i: any) => { + const isQuoted = + typeof part === "string" && + /^(['"]).*\1$/.test(part); + return isQuoted ? ( { {part} ) : ( - part - ) - )} + + {part} + + ); + })}
)} + + {guide?.steps_points?.length > 0 && ( +
    + {guide?.steps_points?.map((p: any, index: number) => ( +
  • + {p?.steps_points_title && ( + + {p?.steps_points_title} + + )} + {p?.steps_points_description && ( +

    + {p?.steps_points_description + ?.split(/((['"]).*?\2)/) + .map((part: string, i: number) => + typeof part === "string" && + /^(['"]).*\1$/.test(part) ? ( + + {part} + + ) : ( + + {part + .split(/(\/\/.*?\/\/)/) + .map((sub: string, j: number) => + sub.startsWith("//") && sub.endsWith("//") ? ( + + {sub.slice(2, -2)} + + ) : ( + sub + ) + )} + + ) + )} +

    + )} + {Array.isArray(p?.steps_subpoint) && p?.steps_subpoint?.length > 0 && ( +
      + {p?.steps_subpoint?.map((sub_p: any, subIndex: number) => ( +
    • + {sub_p?.title && ( + + {sub_p?.title} + + )} + {sub_p?.description && ( + + {sub_p?.description + ?.split(/((['"]).*?\2)/) + .map((part: string, i: number) => + typeof part === "string" && + /^(['"]).*\1$/.test(part) ? ( + + {part} + + ) : ( + + {part + .split(/(\/\/.*?\/\/)/) + .map((sub: string, j: number) => + sub.startsWith("//") && sub.endsWith("//") ? ( + + {sub.slice(2, -2)} + + ) : ( + sub + ) + )} + + ) + )} + + )} +
    • + ))} +
    + )} +
  • + ))} +
+ )}
); } @@ -913,7 +858,7 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => { (desc: any, index: number) => { const descriptions = desc?.descriptions; const splitDescriptions = - descriptions.split(/(".*?")/); // Split quoted and unquoted text + descriptions.split(/((['"]).*?\2)/); // Split quoted and unquoted text return (

{ {splitDescriptions.map( (text: any, subIndex: any) => { const isQuoted = - text.startsWith('') && text.endsWith(''); + typeof text === "string" && + /^(['"]).*\1$/.test(text); return ( { + const m = mime.toLowerCase(); + if (m.includes("jpeg")) return "jpg"; + if (m.includes("png")) return "png"; + if (m.includes("webp")) return "webp"; + if (m.includes("gif")) return "gif"; + if (m.includes("svg")) return "svg"; + if (m.includes("bmp")) return "bmp"; + if (m.includes("icon")) return "ico"; + return "img"; +}; + +const tryBuildDataUrl = ( + raw: string, + fallbackMime: string +): { dataUrl: string | null; parseError: string | null } => { + const trimmed = raw.trim(); + if (!trimmed) return { dataUrl: null, parseError: null }; + + let mime = fallbackMime.toLowerCase(); + let b64 = ""; + + const dataUri = trimmed.match( + /^data:(image\/[a-z0-9.+-]+);base64,([\s\S]*)$/i + ); + if (dataUri) { + mime = dataUri[1].toLowerCase(); + b64 = dataUri[2].replace(/\s/g, ""); + } else { + b64 = trimmed.replace(/\s/g, ""); + // URL-safe Base64 → standard + if (!b64.includes("+") && !b64.includes("/") && /[-_]/.test(b64)) { + b64 = b64.replace(/-/g, "+").replace(/_/g, "/"); + } + } + + if (!b64) return { dataUrl: null, parseError: "No Base64 payload found." }; + + const pad = b64.length % 4; + if (pad) b64 += "=".repeat(4 - pad); + + let binaryLength = 0; + try { + const bin = atob(b64); + binaryLength = bin.length; + } catch { + return { + dataUrl: null, + parseError: + "Could not decode Base64. Remove extra text, fix padding, or paste a full data:image/...;base64,... URI.", + }; + } + + if (binaryLength > MAX_DECODED_BYTES) { + return { + dataUrl: null, + parseError: `Decoded size would exceed ${MAX_DECODED_BYTES / (1024 * 1024)} MB.`, + }; + } + + return { dataUrl: `data:${mime};base64,${b64}`, parseError: null }; +}; + +const Base64ToImageConverter: React.FC = () => { + const [input, setInput] = useState(""); + const [mimeFallback, setMimeFallback] = useState("image/png"); + const [loadError, setLoadError] = useState(null); + const [validDataUrl, setValidDataUrl] = useState(null); + const [resolvedMime, setResolvedMime] = useState("image/png"); + const fileRef = useRef(null); + + const built = useMemo( + () => tryBuildDataUrl(input, mimeFallback), + [input, mimeFallback] + ); + + useEffect(() => { + if (built.parseError || !built.dataUrl) { + setValidDataUrl(null); + setLoadError(null); + return; + } + + let cancelled = false; + const dataUrl = built.dataUrl; + const img = new Image(); + img.onload = () => { + if (cancelled) return; + setValidDataUrl(dataUrl); + setLoadError(null); + const mimeMatch = dataUrl.match(/^data:(image\/[^;]+);/i); + setResolvedMime(mimeMatch?.[1]?.toLowerCase() ?? mimeFallback); + }; + img.onerror = () => { + if (cancelled) return; + setValidDataUrl(null); + setLoadError( + "Decoded bytes are not a displayable image. Try another MIME type if you pasted raw Base64 without a Data URI." + ); + }; + img.src = dataUrl; + + return () => { + cancelled = true; + img.onload = null; + img.onerror = null; + img.src = ""; + }; + }, [built.dataUrl, built.parseError, mimeFallback]); + + const clearAll = () => { + setInput(""); + setLoadError(null); + setValidDataUrl(null); + if (fileRef.current) fileRef.current.value = ""; + }; + + const loadSample = () => { + setMimeFallback("image/png"); + setInput(SAMPLE_RAW_BASE64); + }; + + const onPickFile = () => fileRef.current?.click(); + + const onFile: React.ChangeEventHandler = async (e) => { + const file = e.target.files?.[0]; + if (!file) return; + try { + const text = await file.text(); + setInput(text); + } finally { + e.target.value = ""; + } + }; + + const download = () => { + if (!validDataUrl) return; + const ext = mimeToExtension(resolvedMime); + const a = document.createElement("a"); + a.href = validDataUrl; + a.download = `decoded-image.${ext}`; + document.body.appendChild(a); + a.click(); + a.remove(); + }; + + const copyInput = async () => { + if (!input.trim()) return; + try { + await navigator.clipboard.writeText(input); + } catch { + // ignore + } + }; + + const displayError = built.parseError || loadError; + + return ( +

+
+
+
+
+
+ + +
+ +
+
+ +

+ Paste a full{" "} + data:image/...;base64,... string, + or raw Base64 and choose the image type below. +

+ +