Skip to content

Commit 6aaa2c9

Browse files
authored
Merge pull request #7582 from schrodingersket/feature/7503
7503: Added social media share links to share server.
2 parents 59a7a95 + 265c8c8 commit 6aaa2c9

File tree

6 files changed

+133
-38
lines changed

6 files changed

+133
-38
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* This file is part of CoCalc: Copyright © 2024 Sagemath, Inc.
3+
* License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
4+
*/
5+
6+
import { Space } from "antd";
7+
8+
import { Icon } from "@cocalc/frontend/components/icon";
9+
import { COLORS } from "@cocalc/util/theme";
10+
import { CSS } from "components/misc";
11+
import A from "components/misc/A";
12+
13+
interface SocialMediaShareLinksProps {
14+
title: string;
15+
url: string,
16+
showText?: boolean;
17+
standalone?: boolean; // default false
18+
}
19+
20+
export function SocialMediaShareLinks(props: SocialMediaShareLinksProps) {
21+
const {
22+
title,
23+
url,
24+
standalone = false,
25+
showText = false,
26+
} = props;
27+
28+
const bottomLinkStyle: CSS = {
29+
color: COLORS.ANTD_LINK_BLUE,
30+
...(standalone ? { fontSize: "125%", fontWeight: "bold" } : {}),
31+
};
32+
33+
const srcLink = encodeURIComponent(url);
34+
35+
return (
36+
<Space size="middle" direction="horizontal">
37+
<A
38+
key="tweet"
39+
href={`https://twitter.com/intent/tweet?text=${encodeURIComponent(
40+
title,
41+
)}&url=${srcLink}&via=cocalc_com`}
42+
style={{ color: COLORS.ANTD_LINK_BLUE, ...bottomLinkStyle }}
43+
>
44+
<Icon name="twitter" />
45+
{showText ? " Tweet" : ""}
46+
</A>
47+
<A
48+
key="facebook"
49+
href={`https://www.facebook.com/sharer/sharer.php?u=${srcLink}`}
50+
style={{ ...bottomLinkStyle }}
51+
>
52+
<Icon name="facebook-filled" />
53+
{showText ? " Share" : ""}
54+
</A>
55+
<A
56+
key="linkedin"
57+
href={`https://www.linkedin.com/sharing/share-offsite/?url=${srcLink}`}
58+
style={{ ...bottomLinkStyle }}
59+
>
60+
<Icon name="linkedin-filled" />
61+
{showText ? " Share" : ""}
62+
</A>
63+
</Space>
64+
);
65+
}

src/packages/next/components/news/news.tsx

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import TimeAgo from "timeago-react";
2828
import { useDateStr } from "./useDateStr";
2929
import { useCustomize } from "lib/customize";
3030
import { KUCALC_COCALC_COM } from "@cocalc/util/db-schema/site-defaults";
31+
import { SocialMediaShareLinks } from "../landing/social-media-share-links";
3132

3233
const STYLE: CSS = {
3334
borderColor: COLORS.GRAY_M,
@@ -129,36 +130,13 @@ export function News(props: Props) {
129130
}
130131

131132
function shareLinks(text = false) {
132-
const newsLink = encodeURIComponent(`${siteURL}${permalink}`);
133133
return (
134-
<Space size="middle" direction="horizontal">
135-
<A
136-
key="tweet"
137-
href={`https://twitter.com/intent/tweet?text=${encodeURIComponent(
138-
title,
139-
)}&url=${newsLink}&via=cocalc_com`}
140-
style={{ color: COLORS.ANTD_LINK_BLUE, ...bottomLinkStyle }}
141-
>
142-
<Icon name="twitter" />
143-
{text ? " Tweet" : ""}
144-
</A>
145-
<A
146-
key="facebook"
147-
href={`https://www.facebook.com/sharer/sharer.php?u=${newsLink}`}
148-
style={{ ...bottomLinkStyle }}
149-
>
150-
<Icon name="facebook-filled" />
151-
{text ? " Share" : ""}
152-
</A>
153-
<A
154-
key="linkedin"
155-
href={`https://www.linkedin.com/sharing/share-offsite/?url=${newsLink}`}
156-
style={{ ...bottomLinkStyle }}
157-
>
158-
<Icon name="linkedin-filled" />
159-
{text ? " Share" : ""}
160-
</A>
161-
</Space>
134+
<SocialMediaShareLinks
135+
title={title}
136+
url={encodeURIComponent(`${siteURL}${permalink}`)}
137+
showText={text}
138+
standalone={standalone}
139+
/>
162140
);
163141
}
164142

src/packages/next/components/path/path.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ import Avatar from "components/share/proxy/avatar";
3939
import A from "components/misc/A";
4040
import { join } from "path";
4141

42-
interface Props {
42+
import {
43+
SocialMediaShareLinks
44+
} from "components/landing/social-media-share-links";
45+
46+
export interface PublicPathProps {
4347
id: string;
4448
path: string;
4549
url: string;
@@ -67,6 +71,9 @@ interface Props {
6771
// doesn't use the names. See https://github.com/sagemathinc/cocalc/issues/6115
6872
redirect?: string;
6973
jupyter_api: boolean;
74+
created?: string; // ISO 8601 string
75+
last_edited?: string; // ISO 8601 string
76+
ogUrl?: string; // Open Graph URL for social media sharing
7077
}
7178

7279
export default function PublicPath({
@@ -94,7 +101,8 @@ export default function PublicPath({
94101
projectAvatarImage,
95102
redirect,
96103
jupyter_api,
97-
}: Props) {
104+
ogUrl,
105+
}: PublicPathProps) {
98106
useCounter(id);
99107
const [numStars, setNumStars] = useState<number>(stars);
100108

@@ -335,8 +343,13 @@ export default function PublicPath({
335343
<AntdAvatar
336344
shape="square"
337345
size={160}
338-
icon={<img src={projectAvatarImage} />}
339-
style={{ float: "left", margin: "10px" }}
346+
icon={(
347+
<img
348+
src={projectAvatarImage}
349+
alt={`Avatar for ${projectTitle}.`}
350+
/>
351+
)}
352+
style={{ float: "left", margin: "20px" }}
340353
/>
341354
) : undefined
342355
}
@@ -439,6 +452,13 @@ export default function PublicPath({
439452
</>
440453
)}
441454
</div>
455+
{ogUrl && (
456+
<SocialMediaShareLinks
457+
title={getTitle({ path, relativePath })}
458+
url={ogUrl}
459+
showText
460+
/>
461+
)}
442462
<Divider />
443463
{error != null && (
444464
<Alert

src/packages/next/lib/share/get-public-path-info.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export default async function getPublicPathInfo({
3535
// Get the database entry that describes the public path
3636
const { rows } = await pool.query(
3737
`SELECT project_id, path, description, compute_image, license, disabled, unlisted,
38-
authenticated, url, jupyter_api, redirect,
38+
authenticated, url, jupyter_api, redirect, created, last_edited,
3939
counter::INT,
4040
(SELECT COUNT(*)::INT FROM public_path_stars WHERE public_path_id=id) AS stars,
4141
CASE WHEN site_license_id <> '' THEN TRUE ELSE FALSE END AS has_site_license
@@ -113,6 +113,8 @@ export default async function getPublicPathInfo({
113113
basePath,
114114
isStarred,
115115
...details,
116+
created: rows[0].created.toISOString(),
117+
last_edited: rows[0].last_edited.toISOString(),
116118
};
117119
} catch (error) {
118120
return { id, ...rows[0], relativePath, isStarred, error: error.toString() };

src/packages/next/pages/share/public_paths/[...id].tsx

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,36 @@
44
*/
55

66
import { join } from "path";
7+
import NextHead from "next/head";
8+
79
import basePath from "lib/base-path";
810
import getPublicPathInfo from "lib/share/get-public-path-info";
11+
import shareURL from "lib/share/share-url";
912
import withCustomize from "lib/with-customize";
1013
import { getPublicPathNames } from "lib/names/public-path";
11-
import PublicPath from "components/path/path";
14+
import PublicPath, { PublicPathProps } from "components/path/path";
15+
16+
import ogShareLogo from "public/logo/og-share-logo.png";
17+
18+
export default (props: PublicPathProps) => (
19+
<>
20+
<PublicPath {...props} />
21+
<NextHead>
22+
<meta property="og:type" content="article"/>
23+
24+
<meta property="og:title" content={props.path}/>
25+
<meta property="og:description" content={props.description}/>
26+
<meta property="og:url" content={props.ogUrl}/>
27+
<meta property="og:image" content={
28+
props.customize.logoSquareURL ||
29+
`${props.customize.siteURL}${ogShareLogo.src}`
30+
}/>
1231

13-
export default PublicPath;
32+
<meta property="article:published_time" content={props.created}/>
33+
<meta property="article:modified_time" content={props.last_edited}/>
34+
</NextHead>
35+
</>
36+
)
1437

1538
export async function getServerSideProps(context) {
1639
const id = context.params.id[0];
@@ -34,12 +57,19 @@ export async function getServerSideProps(context) {
3457
}
3558
return { props: { redirect: location } };
3659
}
37-
const props = await getPublicPathInfo({
60+
const props: PublicPathProps = await getPublicPathInfo({
3861
id,
3962
relativePath,
4063
req: context.req,
4164
});
42-
return await withCustomize({ context, props });
65+
66+
const customize = await withCustomize({ context, props });
67+
68+
// Add full URL for social media sharing
69+
//
70+
customize.props.ogUrl = `${customize.props.customize.siteURL}${shareURL(id, relativePath)}`;
71+
72+
return customize;
4373
} catch (_err) {
4474
console.log(_err);
4575
return { notFound: true };
10.2 KB
Loading

0 commit comments

Comments
 (0)