Skip to content

Commit 0d05251

Browse files
committed
feat: add linkedin and facebook sharing buttons
1 parent eedbd52 commit 0d05251

3 files changed

Lines changed: 153 additions & 67 deletions

File tree

src/infrastructure/serialization/share-urls.js

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,34 @@
11
import { buildSchemeSearch } from './scheme-query';
22

3-
export const SHARE_TEXT = 'Check out my terminal color scheme generated by the 4bit Terminal Color Scheme Designer.';
3+
export const SHARE_TEXT = 'Terminal color scheme from 4bit:';
44
const SHARE_BASE_URL = 'https://ciembor.github.io/4bit/';
5+
const DEFAULT_SHARE_URL = new URL(SHARE_BASE_URL);
6+
const PRIVATE_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1']);
7+
8+
function resolveShareBaseLocation(location = null) {
9+
if (location?.origin && location?.pathname) {
10+
const currentUrl = new URL(`${location.origin}${location.pathname}`);
11+
12+
if (
13+
currentUrl.protocol === 'https:' &&
14+
!PRIVATE_HOSTNAMES.has(currentUrl.hostname) &&
15+
!currentUrl.hostname.endsWith('.local')
16+
) {
17+
return {
18+
origin: currentUrl.origin,
19+
pathname: currentUrl.pathname,
20+
};
21+
}
22+
}
23+
24+
return {
25+
origin: DEFAULT_SHARE_URL.origin,
26+
pathname: DEFAULT_SHARE_URL.pathname,
27+
};
28+
}
529

630
export function buildShareUrl(scheme, location = null) {
7-
const origin = location?.origin || 'https://ciembor.github.io';
8-
const pathname = location?.pathname || '/4bit/';
31+
const { origin, pathname } = resolveShareBaseLocation(location);
932

1033
return `${origin}${pathname}${buildSchemeSearch(scheme)}`;
1134
}
@@ -20,6 +43,22 @@ export function buildTwitterShareHref(scheme, location = null) {
2043
return `https://twitter.com/intent/tweet?${params.toString()}`;
2144
}
2245

46+
export function buildLinkedInShareHref(scheme, location = null) {
47+
const params = new URLSearchParams({
48+
url: buildShareUrl(scheme, location),
49+
});
50+
51+
return `https://www.linkedin.com/sharing/share-offsite/?${params.toString()}`;
52+
}
53+
54+
export function buildFacebookShareHref(scheme, location = null) {
55+
const params = new URLSearchParams({
56+
u: buildShareUrl(scheme, location),
57+
});
58+
59+
return `https://www.facebook.com/sharer/sharer.php?${params.toString()}`;
60+
}
61+
2362
export function defaultShareBaseUrl() {
2463
return SHARE_BASE_URL;
2564
}
Lines changed: 70 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
11
<template>
22
<div id="social-media" class="skew">
33
<div class="inner">
4-
<div ref="buttons" class="buttons">
4+
<div class="buttons">
55
<a
6-
:href="shareHref"
7-
aria-label="post on x"
8-
class="buttons__overlay"
6+
:href="xShareHref"
7+
aria-label="share on x"
8+
class="share-button share-button--x"
99
rel="noopener noreferrer"
1010
target="_blank"
11-
></a>
11+
>X</a>
12+
<a
13+
:href="linkedInShareHref"
14+
aria-label="share on linkedin"
15+
class="share-button share-button--linkedin"
16+
rel="noopener noreferrer"
17+
target="_blank"
18+
>in</a>
19+
<a
20+
:href="facebookShareHref"
21+
aria-label="share on facebook"
22+
class="share-button share-button--facebook"
23+
rel="noopener noreferrer"
24+
target="_blank"
25+
>f</a>
1226
</div>
1327
</div>
1428
</div>
@@ -17,66 +31,39 @@
1731
<script>
1832
import { useSchemeStore } from '../../stores/scheme';
1933
import {
20-
buildShareUrl,
34+
buildFacebookShareHref,
35+
buildLinkedInShareHref,
2136
buildTwitterShareHref,
22-
defaultShareBaseUrl,
2337
} from '../../../infrastructure/serialization/share-urls';
2438
25-
const WIDGET_RETRY_MS = 500;
26-
2739
export default {
2840
name: 'SocialMedia',
2941
setup() {
3042
const schemeStore = useSchemeStore();
3143
3244
return { schemeStore };
3345
},
34-
data() {
35-
return {
36-
retryTimerId: null,
37-
};
38-
},
3946
computed: {
40-
shareUrl() {
41-
return buildShareUrl(
47+
currentLocation() {
48+
return typeof window !== 'undefined' ? window.location : null;
49+
},
50+
xShareHref() {
51+
return buildTwitterShareHref(
4252
this.schemeStore.scheme,
43-
typeof window !== 'undefined' ? window.location : null
53+
this.currentLocation
4454
);
4555
},
46-
shareHref() {
47-
return buildTwitterShareHref(
56+
linkedInShareHref() {
57+
return buildLinkedInShareHref(
4858
this.schemeStore.scheme,
49-
typeof window !== 'undefined' ? window.location : null
59+
this.currentLocation
5060
);
5161
},
52-
},
53-
mounted() {
54-
this.renderShareButton();
55-
},
56-
beforeUnmount() {
57-
window.clearTimeout(this.retryTimerId);
58-
},
59-
methods: {
60-
renderShareButton() {
61-
const buttonContainer = this.$refs.buttons;
62-
63-
if (!buttonContainer) {
64-
return;
65-
}
66-
67-
if (!window.twttr?.widgets?.createShareButton) {
68-
window.clearTimeout(this.retryTimerId);
69-
this.retryTimerId = window.setTimeout(() => {
70-
this.renderShareButton();
71-
}, WIDGET_RETRY_MS);
72-
return;
73-
}
74-
75-
window.clearTimeout(this.retryTimerId);
76-
window.twttr.widgets.createShareButton(defaultShareBaseUrl(), buttonContainer, {
77-
count: 'none',
78-
via: 'ciembor',
79-
});
62+
facebookShareHref() {
63+
return buildFacebookShareHref(
64+
this.schemeStore.scheme,
65+
this.currentLocation
66+
);
8067
},
8168
},
8269
};
@@ -87,30 +74,50 @@ export default {
8774
border: 1px solid #AAA;
8875
background-color: #C9C9C9;
8976
position: relative;
90-
width: 104px;
77+
width: 180px;
9178
display: inline-block;
9279
white-space: nowrap;
9380
9481
.buttons {
95-
display: inline-block;
96-
min-width: 73px;
97-
min-height: 20px;
98-
position: relative;
99-
}
100-
101-
.buttons__overlay {
102-
position: absolute;
103-
inset: 0;
104-
z-index: 2;
82+
display: flex;
83+
gap: 6px;
84+
align-items: center;
10585
}
10686
10787
.inner {
10888
position: absolute;
10989
top: 7px;
110-
left: 15px;
111-
display: block;
112-
width: 73px;
113-
height: 20px;
90+
left: 14px;
11491
}
11592
}
93+
94+
.share-button {
95+
display: inline-flex;
96+
align-items: center;
97+
justify-content: center;
98+
width: 44px;
99+
height: 18px;
100+
border: 1px solid #999;
101+
border-radius: 999px;
102+
color: #fff;
103+
font-family: Arial, Verdana, sans-serif;
104+
font-size: 12px;
105+
font-style: normal;
106+
font-weight: bold;
107+
line-height: 1;
108+
text-decoration: none;
109+
text-transform: none;
110+
}
111+
112+
.share-button--x {
113+
background: #111;
114+
}
115+
116+
.share-button--linkedin {
117+
background: #0a66c2;
118+
}
119+
120+
.share-button--facebook {
121+
background: #1877f2;
122+
}
116123
</style>

tests/infrastructure/serialization/share-urls.test.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { describe, expect, it } from 'vitest';
22
import { createDefaultScheme } from '../../../src/domain/scheme/scheme-defaults';
33
import {
4+
buildFacebookShareHref,
5+
buildLinkedInShareHref,
46
buildShareUrl,
57
buildTwitterShareHref,
68
defaultShareBaseUrl,
@@ -23,7 +25,7 @@ describe('share-urls', () => {
2325
);
2426
});
2527

26-
it('builds a twitter intent link with the share text and encoded URL', () => {
28+
it('builds a twitter intent link with the encoded URL', () => {
2729
const scheme = createDefaultScheme();
2830
scheme.dyeScope = 'all';
2931

@@ -35,10 +37,48 @@ describe('share-urls', () => {
3537

3638
expect(`${url.origin}${url.pathname}`).toBe('https://twitter.com/intent/tweet');
3739
expect(url.searchParams.get('text')).toBe(SHARE_TEXT);
40+
expect(url.searchParams.get('url')).toBe('https://ciembor.github.io/4bit/?dyeScope=all');
3841
expect(url.searchParams.get('via')).toBe('ciembor');
42+
});
43+
44+
it('falls back to the public share URL when current location is local', () => {
45+
const scheme = createDefaultScheme();
46+
scheme.dyeScope = 'all';
47+
48+
expect(buildShareUrl(scheme, {
49+
origin: 'http://localhost:5173',
50+
pathname: '/',
51+
})).toBe('https://ciembor.github.io/4bit/?dyeScope=all');
52+
});
53+
54+
it('builds a linkedin share link with the encoded URL', () => {
55+
const scheme = createDefaultScheme();
56+
scheme.dyeScope = 'all';
57+
58+
const href = buildLinkedInShareHref(scheme, {
59+
origin: 'https://ciembor.github.io',
60+
pathname: '/4bit/',
61+
});
62+
const url = new URL(href);
63+
64+
expect(`${url.origin}${url.pathname}`).toBe('https://www.linkedin.com/sharing/share-offsite/');
3965
expect(url.searchParams.get('url')).toBe('https://ciembor.github.io/4bit/?dyeScope=all');
4066
});
4167

68+
it('builds a facebook share link with the encoded URL', () => {
69+
const scheme = createDefaultScheme();
70+
scheme.dyeScope = 'all';
71+
72+
const href = buildFacebookShareHref(scheme, {
73+
origin: 'https://ciembor.github.io',
74+
pathname: '/4bit/',
75+
});
76+
const url = new URL(href);
77+
78+
expect(`${url.origin}${url.pathname}`).toBe('https://www.facebook.com/sharer/sharer.php');
79+
expect(url.searchParams.get('u')).toBe('https://ciembor.github.io/4bit/?dyeScope=all');
80+
});
81+
4282
it('uses the production share URL defaults when location is unavailable', () => {
4383
expect(buildShareUrl(createDefaultScheme())).toBe('https://ciembor.github.io/4bit/');
4484
expect(defaultShareBaseUrl()).toBe('https://ciembor.github.io/4bit/');

0 commit comments

Comments
 (0)