Skip to content

Commit

Permalink
feat: add support for blogpost from generic platforms (#2555)
Browse files Browse the repository at this point in the history
Co-authored-by: BekahHW <34313413+BekahHW@users.noreply.github.com>
  • Loading branch information
babblebey and BekahHW committed Apr 17, 2024
1 parent 5e4c037 commit 8ca6f25
Show file tree
Hide file tree
Showing 10 changed files with 829 additions and 256 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ import useUserHighlightReactions from "lib/hooks/useUserHighlightReactions";
import Tooltip from "components/atoms/Tooltip/tooltip";
import useFollowUser from "lib/hooks/useFollowUser";
import { fetchGithubIssueInfo } from "lib/hooks/fetchGithubIssueInfo";
import { isValidBlogUrl } from "lib/utils/dev-to";
import { isValidUrl } from "lib/utils/urls";
import { isValidDevToBlogUrl } from "lib/utils/dev-to";
import { fetchDevToBlogInfo } from "lib/hooks/fetchDevToBlogInfo";
import Search from "components/atoms/Search/search";
import Title from "components/atoms/Typography/title";
import { shortenUrl } from "lib/utils/shorten-url";
import GhOpenGraphImg from "../GhOpenGraphImg/gh-open-graph-img";
import GenericBlogOpenGraphImg from "../GenericBlogOpenGraphImg/generic-blog-open-graph-img";
import {
Dialog,
DialogContent,
Expand Down Expand Up @@ -260,7 +262,7 @@ const ContributorHighlightCard = ({

let repos: RepoList[] = [];

if (!taggedRepos || taggedRepos.length === 0) {
if (!taggedRepos || (taggedRepos.length === 0 && highlightLink.includes("github.com"))) {
const { owner: repoOwner, repoName } = getOwnerAndRepoNameFromUrl(highlightLink);
const repoIcon = getAvatarByUsername(repoOwner, 60);

Expand All @@ -279,7 +281,7 @@ const ContributorHighlightCard = ({
return { text: "Pull request", icon: <BiGitPullRequest className="text-md md:text-lg" /> };
case "blog_post":
return {
text: "Blog post",
text: highlightLink.includes("dev.to") ? "DevTo post" : "Blog post",
icon: (
// Used svg as i could not find the exact icon in react-icons
<svg
Expand Down Expand Up @@ -318,7 +320,12 @@ const ContributorHighlightCard = ({
return;
}

if (isValidPullRequestUrl(highlightLink) || isValidIssueUrl(highlightLink) || isValidBlogUrl(highlightLink)) {
if (
isValidPullRequestUrl(highlightLink) ||
isValidIssueUrl(highlightLink) ||
isValidDevToBlogUrl(highlightLink) ||
isValidUrl(highlightLink)
) {
const { apiPaths } = generateRepoParts(highlight.highlightLink);
const { repoName, orgName, issueId } = apiPaths;
setLoading(true);
Expand All @@ -340,7 +347,7 @@ const ContributorHighlightCard = ({

if (res.isError) {
setLoading(false);
setError("A valid Pull request, Issue or dev.to Blog Link is required");
setError("A valid Pull request, Issue or Blogpost Link is required");
return;
} else {
const res = await updateHighlights(
Expand Down Expand Up @@ -616,8 +623,10 @@ const ContributorHighlightCard = ({
<a href={highlightLink} target="_blank" aria-hidden="true">
{type === "pull_request" || type === "issue" ? (
<GhOpenGraphImg githubLink={highlightLink} />
) : (
) : highlightLink.includes("dev.to") ? (
<DevToSocialImg blogLink={highlightLink} />
) : (
<GenericBlogOpenGraphImg blogLink={highlightLink} />
)}
</a>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useEffect, useState } from "react";
import InvalidImage from "img/icons/fallback-image-disabled-square.svg";

interface GenericBlogOpenGraphImgProps {
blogLink: string;
className?: string;
}

const GenericBlogOpenGraphImg = ({ blogLink, className }: GenericBlogOpenGraphImgProps): JSX.Element => {
const [socialImage, setSocialImage] = useState("");
const [isValid, setIsValid] = useState(false);

useEffect(() => {
const fetchBlogOGImage = async () => {
const response = await fetch(`/api/fetchOGData/?url=${blogLink}`);
const data = await response.json();
return data;
};

fetchBlogOGImage().then(({ response }) => {
if (response) {
setSocialImage(response?.image?.url || response?.image?.url[response?.image?.url.length - 1]);
setIsValid(true);
} else {
setSocialImage("");
setIsValid(false);
}
});
}, [blogLink]);

return (
<>
{socialImage && (
<picture className={className}>
<img
src={isValid ? socialImage : InvalidImage}
alt={isValid ? "blog og image" : "invalid url image"}
className={"border border-slate-100 rounded-lg"}
/>
</picture>
)}
</>
);
};

export default GenericBlogOpenGraphImg;
67 changes: 45 additions & 22 deletions components/molecules/HighlightInput/highlight-input-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,16 @@ import { TypeWriterTextArea } from "components/atoms/TypeWriterTextArea/type-wri
import { fetchGithubIssueInfo } from "lib/hooks/fetchGithubIssueInfo";
import generateIssueHighlightSummary from "lib/utils/generate-issue-highlight-summary";
import { fetchDevToBlogInfo } from "lib/hooks/fetchDevToBlogInfo";
import { getBlogDetails, isValidBlogUrl } from "lib/utils/dev-to";
import generateBlogHighlightSummary from "lib/utils/generate-blog-highlight-summary";
import { getDevToBlogDetails, isValidDevToBlogUrl } from "lib/utils/dev-to";
import generateDevToBlogHighlightSummary from "lib/utils/generate-dev-to-blog-highlight-summary";
import Search from "components/atoms/Search/search";
import useSupabaseAuth from "lib/hooks/useSupabaseAuth";
import { isValidUrl } from "lib/utils/urls";
import { Calendar } from "../Calendar/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "../Popover/popover";
import GhOpenGraphImg from "../GhOpenGraphImg/gh-open-graph-img";
import DevToSocialImg from "../DevToSocialImage/dev-to-social-img";
import GenericBlogOpenGraphImg from "../GenericBlogOpenGraphImg/generic-blog-open-graph-img";
import CardRepoList, { RepoList } from "../CardRepoList/card-repo-list";
import {
Dialog,
Expand Down Expand Up @@ -163,16 +165,6 @@ const HighlightInputForm = ({ refreshCallback }: HighlightInputFormProps): JSX.E
}
};

const checkIfHighlightLinkIsValid = (link: string) => {
if (!link) return setError("");
if (isValidPullRequestUrl(link) || isValidIssueUrl(link) || isValidBlogUrl(link)) {
setIsHighlightURLValid(true);
setError("");
} else {
setIsHighlightURLValid(false);
setError("Please provide a valid pull request, issue or dev.to blog link!");
}
};
useEffect(() => {
// disable scroll when form is open
if (isFormOpenMobile) {
Expand Down Expand Up @@ -342,6 +334,22 @@ const HighlightInputForm = ({ refreshCallback }: HighlightInputFormProps): JSX.E
const newTaggedRepoList = [...taggedRepoList, newRepo];
setTaggedRepoList(newTaggedRepoList);
}

(() => {
if (!highlightLink) return setError("");
if (
isValidPullRequestUrl(highlightLink) ||
isValidIssueUrl(highlightLink) ||
isValidDevToBlogUrl(highlightLink) ||
isValidUrl(highlightLink)
) {
setIsHighlightURLValid(true);
setError("");
} else {
setIsHighlightURLValid(false);
setError("Please provide a valid pull request, issue or dev.to blog link!");
}
})();
}, [highlightLink]);

const handleTaggedRepoAdd = async (repoFullName: string) => {
Expand Down Expand Up @@ -384,15 +392,21 @@ const HighlightInputForm = ({ refreshCallback }: HighlightInputFormProps): JSX.E
};

const handleGenerateHighlightSummary = async () => {
if (
!highlightLink ||
(!isValidPullRequestUrl(highlightLink) && !isValidIssueUrl(highlightLink) && !isValidBlogUrl(highlightLink))
) {
if (!highlightLink) {
setError("Please provide a valid pull request, issue or dev.to blog link!");
return;
}
setIsHighlightURLValid(true);

if (highlightLink && (!highlightLink.includes("github.com") || !highlightLink.includes("dev.to"))) {
toast({
description: "Auto-Summarize not supported for current link!",
title: "Oops!",
variant: "warning",
});
return;
}

setIsSummaryButtonDisabled(true);

let summary: string | null;
Expand All @@ -404,8 +418,8 @@ const HighlightInputForm = ({ refreshCallback }: HighlightInputFormProps): JSX.E
const issueComments = await getGithubIssueComments(highlightLink);
summary = await generateIssueHighlightSummary(issueTitle, issueBody, issueComments);
} else {
const { title: blogTitle, markdown: blogMarkdown } = await getBlogDetails(highlightLink);
summary = await generateBlogHighlightSummary(blogTitle, blogMarkdown);
const { title: blogTitle, markdown: blogMarkdown } = await getDevToBlogDetails(highlightLink);
summary = await generateDevToBlogHighlightSummary(blogTitle, blogMarkdown);
}

setIsSummaryButtonDisabled(false);
Expand Down Expand Up @@ -433,7 +447,12 @@ const HighlightInputForm = ({ refreshCallback }: HighlightInputFormProps): JSX.E

const highlight = bodyText;

if (isValidPullRequestUrl(highlightLink) || isValidIssueUrl(highlightLink) || isValidBlogUrl(highlightLink)) {
if (
isValidPullRequestUrl(highlightLink) ||
isValidIssueUrl(highlightLink) ||
isValidDevToBlogUrl(highlightLink) ||
isValidUrl(highlightLink)
) {
setIsHighlightURLValid(true);
// generateApiPrUrl will return an object with repoName, orgName and issueId
// it can work with both issue and pull request links
Expand Down Expand Up @@ -648,19 +667,24 @@ const HighlightInputForm = ({ refreshCallback }: HighlightInputFormProps): JSX.E
value={highlightLink}
handleChange={(value) => {
setHighlightLink(value);
checkIfHighlightLinkIsValid(value);
}}
placeholder="Paste the URL to your PR, Issue, or Dev.to blog post."
/>
</div>
</div>

{highlightLink && isDivFocused && highlightLink.includes("github") && (
{highlightLink && isDivFocused && highlightLink.includes("github.com") && (
<GhOpenGraphImg githubLink={highlightLink} />
)}
{highlightLink && isDivFocused && highlightLink.includes("dev.to") && (
<DevToSocialImg blogLink={highlightLink} />
)}
{highlightLink &&
isDivFocused &&
!highlightLink.includes("github.com") &&
!highlightLink.includes("dev.to") && (
<GenericBlogOpenGraphImg className="max-sm:hidden lg:w-[33vw] md:w-[50vw]" blogLink={highlightLink} />
)}

<Button
loading={loading}
Expand Down Expand Up @@ -897,7 +921,6 @@ const HighlightInputForm = ({ refreshCallback }: HighlightInputFormProps): JSX.E
value={highlightLink}
handleChange={(value) => {
setHighlightLink(value);
checkIfHighlightLinkIsValid(value);
}}
placeholder="Paste your PR URL and get it auto-summarized!"
/>
Expand Down
10 changes: 6 additions & 4 deletions lib/utils/dev-to.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { getBlogDetails, isValidBlogUrl } from "lib/utils/dev-to";
import { getDevToBlogDetails, isValidDevToBlogUrl } from "lib/utils/dev-to";

describe("[lib] dev-to methods", () => {
it("Should return the title and markdown", async () => {
const result = await getBlogDetails(
const result = await getDevToBlogDetails(
"https://dev.to/opensauced/how-open-source-helped-me-get-a-github-octernship-4f69"
);
expect(result).toEqual({
Expand All @@ -12,11 +12,13 @@ describe("[lib] dev-to methods", () => {
});

it("Should return false", () => {
const result = isValidBlogUrl("https://gitub.com/open-sauced/hot/pull/448");
const result = isValidDevToBlogUrl("https://gitub.com/open-sauced/hot/pull/448");
expect(result).toEqual(false);
});
it("Should return true", () => {
const result = isValidBlogUrl("https://dev.to/opensauced/how-open-source-helped-me-get-a-github-octernship-4f69");
const result = isValidDevToBlogUrl(
"https://dev.to/opensauced/how-open-source-helped-me-get-a-github-octernship-4f69"
);
expect(result).toEqual(true);
});
});
4 changes: 2 additions & 2 deletions lib/utils/dev-to.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const getBlogDetails = async (blogLink: string) => {
export const getDevToBlogDetails = async (blogLink: string) => {
const trimmedUrl = blogLink.trim();
const devToUrl = new URL(trimmedUrl.includes("https://") ? trimmedUrl : `https://${trimmedUrl}`);
const { pathname } = devToUrl;
Expand All @@ -12,6 +12,6 @@ export const getBlogDetails = async (blogLink: string) => {
};
};

export const isValidBlogUrl = (url: string): boolean => {
export const isValidDevToBlogUrl = (url: string): boolean => {
return url.match(/((https?:\/\/)?(www\.)?dev\.to\/[^\/]+\/[^\/]+)/) ? true : false;
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { supabase } from "./supabase";

const baseUrl = process.env.NEXT_PUBLIC_API_URL;
const generateBlogHighlightSummary = async (blogTitle: string, blogMarkdown: string) => {
const generateDevToBlogHighlightSummary = async (blogTitle: string, blogMarkdown: string) => {
const sessionResponse = await supabase.auth.getSession();
const sessionToken = sessionResponse?.data.session?.access_token;
const payload = {
Expand Down Expand Up @@ -37,4 +37,4 @@ const generateBlogHighlightSummary = async (blogTitle: string, blogMarkdown: str
}
};

export default generateBlogHighlightSummary;
export default generateDevToBlogHighlightSummary;
4 changes: 4 additions & 0 deletions lib/utils/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export const siteUrl = (path: string = "", params: { [key: string]: any } = {})
return url.toString();
};

export const isValidUrl = (url: string) => {
return /^(http|https):\/\/[A-Za-z0-9.-]+(\/[A-Za-z0-9\/_.-]+)*$/.test(url);
};

/**
* -------------------------------------------------------------------------------
* DevCard URLs
Expand Down
Loading

0 comments on commit 8ca6f25

Please sign in to comment.