Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 0 additions & 47 deletions components/PostHeaderAuthors.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React, { useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { FaLinkedin, FaTwitter, FaLink } from "react-icons/fa";
import { sanitizeAuthorSlug } from "../utils/sanitizeAuthorSlug";

const PostHeaderAuthors = ({ blogwriter, blogreviewer, timetoRead }) => {
Expand Down Expand Up @@ -33,22 +31,6 @@ const PostHeaderAuthors = ({ blogwriter, blogreviewer, timetoRead }) => {
setHoverStateBlogReviewer(false);
}, 400);
};
const router = useRouter();
const currentURL = encodeURIComponent(
`keploy.io/${router.basePath + router.asPath}`
);
const twitterShareUrl = `https://twitter.com/share?url=${currentURL}`;
const linkedinShareUrl = `https://www.linkedin.com/shareArticle?url=${currentURL}`;

const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(`https://keploy.io/blog${router.asPath}`);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error("Failed to copy!", err);
}
};

return (
<>
Expand Down Expand Up @@ -140,35 +122,6 @@ const PostHeaderAuthors = ({ blogwriter, blogreviewer, timetoRead }) => {
</div>
)}
</div>
<div className="flex flex-row gap-5 items-center gap-3 sm:gap-5 order-2 sm:order-none mt-2 md:mb-2">
<p className="text-gray-500 text-sm">Share this</p>
<Link
href={twitterShareUrl}
target="_blank"
rel="noopener noreferrer"
className="twitter-share-button text-xl text-black transition-colors duration-300 hover:text-blue-500"
>
<FaTwitter className="icon" />
</Link>
<Link
href={linkedinShareUrl}
target="_blank"
rel="noopener noreferrer"
className="linkedin-share-button text-xl text-black transition-colors duration-300 hover:text-blue-500"
>
<FaLinkedin className="icon" />
</Link>
<button
onClick={copyToClipboard}
className="link-share-button text-xl text-black transition-colors duration-300 hover:text-blue-500 focus:outline-none"
aria-label="Copy URL to clipboard"
>
<FaLink className="icon" />
</button>
{copied && (
<span className="ml-2 text-orange-500 text-sm">Copied!</span>
)}
</div>
</div>

<hr className="border-slate-300 mb-20 mt-5" />
Expand Down
188 changes: 129 additions & 59 deletions components/post-body.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from "react";
import TOC from "./TableContents";
import { IoCopyOutline, IoCheckmarkOutline } from "react-icons/io5";
import TOC from "./TableContents";
import { IoCopyOutline, IoCheckmarkOutline } from "react-icons/io5";
import styles from "./post-body.module.css";
import dynamic from "next/dynamic";
import CodeMirror from "@uiw/react-codemirror";
Expand All @@ -18,24 +18,33 @@ import { Post } from "../types/post";
import JsonDiffViewer from "./json-diff-viewer";
import { sanitizeStringForURL } from "../utils/sanitizeStringForUrl";
import AdSlot from "./Adslot";
import Link from "next/link";
import { FaLinkedin, FaTwitter, FaLink } from "react-icons/fa";
import { useRouter } from "next/router";
import React from "react";
export default function PostBody({
content,
authorName,
ReviewAuthorDetails,
slug
slug,
}: {
content: Post["content"];
authorName: Post["ppmaAuthorName"];
ReviewAuthorDetails: { edges: { node: { name: string; avatar: { url: string }; description: string } }[] };
ReviewAuthorDetails: {
edges: {
node: { name: string; avatar: { url: string }; description: string };
}[];
};
slug: string | string[] | undefined;
}) {
const [tocItems, setTocItems] = useState([]);
const [copySuccessList, setCopySuccessList] = useState([]);
const [headingCopySuccessList, setHeadingCopySuccessList] = useState([]);
const [isSmallScreen, setIsSmallScreen] = useState(false);
const [replacedContent, setReplacedContent] = useState(content);
const [replacedContent, setReplacedContent] = useState(content);
const [isList, setIsList] = useState(false);
const [isUserEnteredURL, setIsUserEnteredURL] = useState(false);
const [copied, setCopied] = useState(false);
const sameAuthor =
authorName.split(" ")[0].toLowerCase() ===
ReviewAuthorDetails.edges[0].node.name.split(" ")[0].toLowerCase();
Expand Down Expand Up @@ -69,11 +78,32 @@ export default function PostBody({
return () => {
window.removeEventListener("resize", checkScreenSize);
};
}, [content]);
}, [content]);
const router = useRouter();
const currentURL = encodeURIComponent(
`keploy.io/${router.basePath + router.asPath}`
);
const twitterShareUrl = `https://twitter.com/share?url=${currentURL}`;
const linkedinShareUrl = `https://www.linkedin.com/shareArticle?url=${currentURL}`;
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(
`https://keploy.io/blog${router.asPath}`
);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error("Failed to copy!", err);
}
};

useEffect(() => {
const timeout = setTimeout(() => {
const headings = Array.from(document.getElementById('post-body-check').querySelectorAll("h1, h2, h3, h4"));
const headings = Array.from(
document
.getElementById("post-body-check")
.querySelectorAll("h1, h2, h3, h4")
);

const tocItems = headings.map((heading) => {
const id = `${heading.textContent}`;
Expand All @@ -92,31 +122,32 @@ export default function PostBody({
}
setTocItems(tocItems);
setCopySuccessList(Array(tocItems.length).fill(false));
setHeadingCopySuccessList(Array(tocItems.length).fill(false));
}, 500);
setHeadingCopySuccessList(Array(tocItems.length).fill(false));
}, 500);

return () => clearTimeout(timeout);
return () => clearTimeout(timeout);
}, [content]);

useEffect(() => {
const timeout = setTimeout(() => {
const hash = window.location.hash;
if (hash) {
const targetId = decodeURIComponent(hash.slice(1));
const el = document.getElementById(targetId);
if (el) {
setIsUserEnteredURL(true);

const yOffset = -80;
const y = el.getBoundingClientRect().top + window.pageYOffset + yOffset;

window.scrollTo({ top: y, behavior: "smooth" });
const timeout = setTimeout(() => {
const hash = window.location.hash;
if (hash) {
const targetId = decodeURIComponent(hash.slice(1));
const el = document.getElementById(targetId);
if (el) {
setIsUserEnteredURL(true);

const yOffset = -80;
const y =
el.getBoundingClientRect().top + window.pageYOffset + yOffset;

window.scrollTo({ top: y, behavior: "smooth" });
}
}
}
}, 100);
}, 100);

return () => clearTimeout(timeout);
}, [tocItems]);
return () => clearTimeout(timeout);
}, [tocItems]);

useEffect(() => {
const scrollObserverOptions = {
Expand Down Expand Up @@ -156,7 +187,7 @@ export default function PostBody({
setTimeout(() => {
updatedList[index] = false;
setCopySuccessList(updatedList);
}, 2000);
}, 2000);
})
.catch(() => {
const updatedList = [...copySuccessList];
Expand All @@ -166,7 +197,7 @@ export default function PostBody({
};

const handleHeadingCopyClick = (id: string, index: number) => {
const url = sanitizeStringForURL(id,true);
const url = sanitizeStringForURL(id, true);
const copyUrl = `${window.location.origin}${window.location.pathname}#${url}`;
navigator.clipboard
.writeText(copyUrl)
Expand All @@ -177,33 +208,38 @@ export default function PostBody({
setTimeout(() => {
updatedList[index] = false;
setHeadingCopySuccessList([...updatedList]);
}, 2000);
}, 2000);
})
.catch(() => {
console.error("Failed to copy the URL.");
});
};

useEffect(() => {
const headings = Array.from(document.getElementById('post-body-check').querySelectorAll("h1, h2"));
const headings = Array.from(
document.getElementById("post-body-check").querySelectorAll("h1, h2")
);
headings.forEach((heading, index) => {
if (heading.querySelector(".copy-url-button")) return;

const button = document.createElement("button");
button.className = "copy-url-button";
button.style.marginLeft = "8px";
button.style.marginLeft = "8px";
button.style.cursor = "pointer";
button.style.border = "none";
button.style.background = "none";
button.style.padding = "0";
button.style.fontSize = "1rem";
button.style.color = "#555";
button.textContent = headingCopySuccessList[index] ? '✔️' : '#'; // // Copy Button
button.style.color = "#555";
button.textContent = headingCopySuccessList[index] ? "✔️" : "#"; // // Copy Button
button.addEventListener("click", () => {
handleHeadingCopyClick(heading.innerHTML, index);

const yOffset = -80;
const y = document.getElementById(heading.id).getBoundingClientRect().top + window.pageYOffset + yOffset;
const y =
document.getElementById(heading.id).getBoundingClientRect().top +
window.pageYOffset +
yOffset;

window.scrollTo({
top: y,
Expand All @@ -216,12 +252,18 @@ export default function PostBody({
}, [tocItems, headingCopySuccessList]);

useEffect(() => {
const headings = Array.from(document.getElementById('post-body-check').querySelectorAll("h1, h2, h3, h4"));
const headings = Array.from(
document
.getElementById("post-body-check")
.querySelectorAll("h1, h2, h3, h4")
);
headings.forEach((heading, index) => {
const button = heading.querySelector(".copy-url-button") as HTMLButtonElement;
const button = heading.querySelector(
".copy-url-button"
) as HTMLButtonElement;
if (button) {
// button.textContent = headingCopySuccessList[index] ? '✔️' : '🔗'; // // Copy Button
button.style.color = headingCopySuccessList[index] ? "#28a745" : "#555";
button.style.color = headingCopySuccessList[index] ? "#28a745" : "#555";
}
});
}, [headingCopySuccessList]);
Expand Down Expand Up @@ -250,7 +292,7 @@ export default function PostBody({
.map((part, index) => {
if (/<pre[\s\S]*?<\/pre>/.test(part)) {
const codeMatch = part.match(/<code[\s\S]*?>([\s\S]*?)<\/code>/);
const code = codeMatch ? decodeHtmlEntities(codeMatch[1]) : "";
const code = codeMatch ? decodeHtmlEntities(codeMatch[1]) : "";
const language =
codeMatch && codeMatch[0].includes("language-")
? codeMatch[0].split("language-")[1].split('"')[0]
Expand Down Expand Up @@ -338,13 +380,16 @@ export default function PostBody({
>
<TOC headings={tocItems} isList={isList} setIsList={setIsList} />
</div>
<div className={`w-full p-4 ${isList ? "ml-10" : ""} md:w-4/5 lg:w-3/5`} id="post-body-check">
<div
className={`w-full p-4 ${isList ? "ml-10" : ""} md:w-4/5 lg:w-3/5`}
id="post-body-check"
>
{slug === "how-to-compare-two-json-files" && <JsonDiffViewer />}
<div className="prose lg:prose-xl post-content-wrapper">{renderCodeBlocks()}</div>
<div className="prose lg:prose-xl post-content-wrapper">
{renderCodeBlocks()}
</div>
<hr className="border-gray-300 mt-10 mb-20" />
<div>

</div>
<div></div>

<h1 className="text-2xl font-medium">Authored By:</h1>
<div className="my-5">
Expand All @@ -368,22 +413,47 @@ export default function PostBody({
)}
</div>

<aside className="w-full lg:w-1/5 lg:ml-10 p-4 flex flex-col gap-6 sticky lg:top-20">

{/* 1. Waitlist banner (always shown) */}
<div className="flex justify-center">
<WaitlistBanner />
</div>

{/* 2. Ad slot (hidden on <lg) */}
<div className="hidden lg:flex justify-center rounded-xl p-4">
<AdSlot
slotId="3356716061"
className="w-full h-60"
/>
</div>
</aside>
<aside className="w-full lg:w-1/5 lg:ml-10 p-4 flex flex-col gap-6 sticky lg:top-20">
{/* 1. Waitlist banner (always shown) */}
<div className="flex justify-center">
<WaitlistBanner />
</div>

<div className="flex flex-row gap-5 items-center gap-3 sm:gap-5 order-2 sm:order-none mt-2 md:mb-2 p-4">
<p className="text-gray-500 text-sm">Share this</p>
<Link
href={twitterShareUrl}
target="_blank"
rel="noopener noreferrer"
className="twitter-share-button text-xl text-black transition-colors duration-300 hover:text-blue-500"
>
<FaTwitter className="icon" />
</Link>
<Link
href={linkedinShareUrl}
target="_blank"
rel="noopener noreferrer"
className="linkedin-share-button text-xl text-black transition-colors duration-300 hover:text-blue-500"
>
<FaLinkedin className="icon" />
</Link>
<button
onClick={copyToClipboard}
className="link-share-button text-xl text-black transition-colors duration-300 hover:text-blue-500 focus:outline-none"
aria-label="Copy URL to clipboard"
>
<FaLink className="icon" />
</button>
{copied && (
<span className="ml-2 text-orange-500 text-sm">Copied!</span>
)}
</div>

{/* 2. Ad slot (hidden on <lg) */}
<div className="hidden lg:flex justify-center rounded-xl p-4">
<AdSlot slotId="3356716061" className="w-full h-60" />
</div>
</aside>
</div>
);
}
}