@astro-community/astro-embed-link-preview 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LinkPreview.astro +107 -0
- package/README.md +5 -0
- package/index.ts +1 -0
- package/lib.ts +45 -0
- package/matcher.ts +7 -0
- package/package.json +35 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { parseOpenGraph } from './lib';
|
|
3
|
+
|
|
4
|
+
export interface Props {
|
|
5
|
+
/** URL to fetch Open Graph data. */
|
|
6
|
+
id: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { id } = Astro.props;
|
|
10
|
+
|
|
11
|
+
const meta = await parseOpenGraph(id);
|
|
12
|
+
const domain = meta?.url ? new URL(meta.url).hostname.replace('www.', '') : '';
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
meta && meta.title ? (
|
|
17
|
+
<article
|
|
18
|
+
class:list={[
|
|
19
|
+
'link-preview',
|
|
20
|
+
{
|
|
21
|
+
'link-preview--has-video': meta.video && meta.videoType,
|
|
22
|
+
'link-preview--no-media': !(
|
|
23
|
+
(meta.video && meta.videoType) ||
|
|
24
|
+
meta.image
|
|
25
|
+
),
|
|
26
|
+
},
|
|
27
|
+
]}
|
|
28
|
+
>
|
|
29
|
+
<div class="link-preview__content">
|
|
30
|
+
<header>
|
|
31
|
+
<a class="link-preview__title" href={id}>
|
|
32
|
+
{meta.title}
|
|
33
|
+
</a>{' '}
|
|
34
|
+
{domain && <small class="link-preview__domain">{domain}</small>}
|
|
35
|
+
</header>
|
|
36
|
+
<small class="link-preview__description">{meta.description}</small>
|
|
37
|
+
</div>
|
|
38
|
+
{meta.video && meta.videoType ? (
|
|
39
|
+
<video controls preload="metadata" width="1200" height="630">
|
|
40
|
+
<source src={meta.video} type={meta.videoType} />
|
|
41
|
+
</video>
|
|
42
|
+
) : (
|
|
43
|
+
meta.image && (
|
|
44
|
+
<img
|
|
45
|
+
src={meta.image}
|
|
46
|
+
alt={meta.imageAlt || ''}
|
|
47
|
+
width="1200"
|
|
48
|
+
height="630"
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
)}
|
|
52
|
+
</article>
|
|
53
|
+
) : (
|
|
54
|
+
<div class="link-preview link-preview--no-metadata">
|
|
55
|
+
<a href={id}>{id}</a>
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
<style>
|
|
61
|
+
/* Default styles */
|
|
62
|
+
.link-preview {
|
|
63
|
+
--lp-width: var(--link-preview-width, 30em);
|
|
64
|
+
--lp-pad-x: var(--link-preview-padding-inline, 0);
|
|
65
|
+
--lp-pad-y: var(--link-preview-padding-block, 0.5em);
|
|
66
|
+
--lp-corners: var(--link-preview-corners, 0);
|
|
67
|
+
|
|
68
|
+
position: relative;
|
|
69
|
+
width: var(--lp-width);
|
|
70
|
+
max-width: 100%;
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column-reverse;
|
|
73
|
+
border-radius: var(--lp-corners);
|
|
74
|
+
}
|
|
75
|
+
.link-preview * {
|
|
76
|
+
margin: 0 !important;
|
|
77
|
+
}
|
|
78
|
+
.link-preview__content {
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
padding: var(--lp-pad-y) var(--lp-pad-x);
|
|
82
|
+
}
|
|
83
|
+
.link-preview header {
|
|
84
|
+
display: flex;
|
|
85
|
+
flex-direction: column-reverse;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.link-preview__description {
|
|
89
|
+
white-space: nowrap;
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
text-overflow: ellipsis;
|
|
92
|
+
}
|
|
93
|
+
.link-preview:not(.link-preview--has-video) a::after {
|
|
94
|
+
content: '';
|
|
95
|
+
position: absolute;
|
|
96
|
+
inset: 0;
|
|
97
|
+
}
|
|
98
|
+
.link-preview img,
|
|
99
|
+
.link-preview video {
|
|
100
|
+
aspect-ratio: 1200 / 630;
|
|
101
|
+
width: 100%;
|
|
102
|
+
height: auto;
|
|
103
|
+
object-fit: cover;
|
|
104
|
+
border-top-left-radius: var(--lp-corners);
|
|
105
|
+
border-top-right-radius: var(--lp-corners);
|
|
106
|
+
}
|
|
107
|
+
</style>
|
package/README.md
ADDED
package/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as LinkPreview } from './LinkPreview.astro';
|
package/lib.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { safeGetDOM } from '@astro-community/astro-embed-utils';
|
|
2
|
+
|
|
3
|
+
/** Helper to get the `content` attribute of an element. */
|
|
4
|
+
const getContent = (el: Element | null) => el?.getAttribute('content');
|
|
5
|
+
/** Helper to filter out insecure or non-absolute URLs. */
|
|
6
|
+
const urlOrNull = (url: string | null | undefined) =>
|
|
7
|
+
url?.slice(0, 8) === 'https://' ? url : null;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Loads and parses an HTML page to return Open Graph metadata.
|
|
11
|
+
* @param pageUrl URL to parse
|
|
12
|
+
*/
|
|
13
|
+
export async function parseOpenGraph(pageUrl: string) {
|
|
14
|
+
const html = await safeGetDOM(pageUrl);
|
|
15
|
+
if (!html) return;
|
|
16
|
+
|
|
17
|
+
const getMetaProperty = (prop: string) =>
|
|
18
|
+
getContent(html.querySelector(`meta[property=${JSON.stringify(prop)}]`));
|
|
19
|
+
const getMetaName = (name: string) =>
|
|
20
|
+
getContent(html.querySelector(`meta[name=${JSON.stringify(name)}]`));
|
|
21
|
+
|
|
22
|
+
const title =
|
|
23
|
+
getMetaProperty('og:title') || html.querySelector('title')?.textContent;
|
|
24
|
+
const description =
|
|
25
|
+
getMetaProperty('og:description') || getMetaName('description');
|
|
26
|
+
const image = urlOrNull(
|
|
27
|
+
getMetaProperty('og:image:secure_url') ||
|
|
28
|
+
getMetaProperty('og:image:url') ||
|
|
29
|
+
getMetaProperty('og:image')
|
|
30
|
+
);
|
|
31
|
+
const imageAlt = getMetaProperty('og:image:alt');
|
|
32
|
+
const video = urlOrNull(
|
|
33
|
+
getMetaProperty('og:video:secure_url') ||
|
|
34
|
+
getMetaProperty('og:video:url') ||
|
|
35
|
+
getMetaProperty('og:video')
|
|
36
|
+
);
|
|
37
|
+
const videoType = getMetaProperty('og:video:type');
|
|
38
|
+
const url =
|
|
39
|
+
urlOrNull(
|
|
40
|
+
getMetaProperty('og:url') ||
|
|
41
|
+
html.querySelector("link[rel='canonical']")?.getAttribute('href')
|
|
42
|
+
) || pageUrl;
|
|
43
|
+
|
|
44
|
+
return { title, description, image, imageAlt, url, video, videoType };
|
|
45
|
+
}
|
package/matcher.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@astro-community/astro-embed-link-preview",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Component to embed a website’s OpenGraph image and metadata on your Astro site",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./index.ts",
|
|
8
|
+
"./matcher": "./matcher.ts"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"lib.ts",
|
|
12
|
+
"index.ts",
|
|
13
|
+
"matcher.ts",
|
|
14
|
+
"LinkPreview.astro"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/delucis/astro-embed",
|
|
19
|
+
"directory": "packages/astro-embed-link-preview"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"astro",
|
|
23
|
+
"astro-component",
|
|
24
|
+
"embeds",
|
|
25
|
+
"open-graph"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/delucis/astro-embed/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://astro-embed.netlify.app/components/link-preview/",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@astro-community/astro-embed-utils": "^0.1.1"
|
|
34
|
+
}
|
|
35
|
+
}
|