vitepress-linkcard 2.0.0 → 2.0.1
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/README.md +14 -4
- package/dist/.cjs.min.js +2 -2
- package/dist/.esm.min.js +2 -2
- package/dist/api.js +1 -27
- package/dist/assemble/html.js +6 -59
- package/dist/assemble/local-file-cache.js +0 -126
- package/dist/assemble/metadata.js +1 -27
- package/dist/assemble/parser.js +3 -142
- package/dist/assemble/style.js +2 -123
- package/dist/assemble/url.js +0 -27
- package/dist/assemble/xhr.js +1 -65
- package/dist/link-to-card-plugin.js +1 -110
- package/package.json +1 -1
- package/types/api.d.ts +1 -38
- package/types/assemble/html.d.ts +3 -49
- package/types/assemble/local-file-cache.d.ts +0 -105
- package/types/assemble/metadata.d.ts +1 -22
- package/types/assemble/parser.d.ts +2 -30
- package/types/assemble/style.d.ts +0 -56
- package/types/assemble/url.d.ts +0 -27
- package/types/assemble/xhr.d.ts +1 -55
- package/types/link-to-card-plugin.d.ts +0 -23
package/dist/assemble/parser.js
CHANGED
|
@@ -1,76 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTML parser module for extracting metadata from web pages.
|
|
3
|
-
*
|
|
4
|
-
* This module provides functions to parse HTML strings and extract metadata such as
|
|
5
|
-
* title, description, and logo/icon URLs. It supports both standard HTML meta tags
|
|
6
|
-
* and Open Graph Protocol (OGP) tags.
|
|
7
|
-
*
|
|
8
|
-
* @module parser
|
|
9
|
-
* @todo Refactor to improve maintainability and add support for more meta tag formats
|
|
10
|
-
*/
|
|
11
1
|
import { isString } from '@luckrya/utility';
|
|
12
2
|
import { cleanPath, extractUrl } from './url';
|
|
13
|
-
/**
|
|
14
|
-
* Default logo URL used when no logo can be extracted from the page.
|
|
15
|
-
*/
|
|
16
3
|
const DEFAULT_LOGO = 'https://resources.whatwg.org/logo-url.svg';
|
|
17
|
-
/**
|
|
18
|
-
* Regular expression to match HTML tag content between opening and closing tags.
|
|
19
|
-
* @example Matches: `<title>Page Title</title>` → captures "Page Title"
|
|
20
|
-
*/
|
|
21
4
|
const HtmlTagContentReg = /(<[A-Za-z]+\s*[^>]*>(.*)<\/[A-Za-z]+>)/;
|
|
22
|
-
/**
|
|
23
|
-
* Regular expression to extract the `content` attribute value from HTML meta tags.
|
|
24
|
-
* @example Matches: `content="value"` or `content='value'`
|
|
25
|
-
*/
|
|
26
5
|
const ContentAttrValueHtmlMetaTagReg = /content=["|']([^>]*)["|']/;
|
|
27
|
-
/**
|
|
28
|
-
* Regular expression to extract the `href` attribute value from HTML link tags.
|
|
29
|
-
* @example Matches: `href="value"` or `href='value'`
|
|
30
|
-
*/
|
|
31
6
|
const HrefAttrValueHtmlLinkTagReg = /href=["|']([^>]*)["|']/;
|
|
32
|
-
/**
|
|
33
|
-
* Regular expression to match HTML title tags.
|
|
34
|
-
* @example Matches: `<title>Page Title</title>`
|
|
35
|
-
*/
|
|
36
7
|
const HtmlTitleTagReg = /(<title\s*[^>]*>(.*)<\/title>)/g;
|
|
37
|
-
/**
|
|
38
|
-
* Creates a regular expression to match self-closing or non-closing HTML tags with a specific attribute.
|
|
39
|
-
*
|
|
40
|
-
* This function generates patterns to find meta or link tags that contain a specific attribute,
|
|
41
|
-
* which is useful for finding OGP tags like `og:title`, `og:description`, etc.
|
|
42
|
-
*
|
|
43
|
-
* @param attr - The attribute to search for (e.g., 'title', 'description', 'image')
|
|
44
|
-
* @param tag - The HTML tag name to match (default: 'meta')
|
|
45
|
-
* @returns A RegExp that matches tags containing the specified attribute
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```typescript
|
|
49
|
-
* const reg = containArrSelfLosingHtmlTagReg('og:title')
|
|
50
|
-
* // Matches: <meta property="og:title" content="...">
|
|
51
|
-
* ```
|
|
52
|
-
*
|
|
53
|
-
* @internal
|
|
54
|
-
*/
|
|
55
8
|
const containArrSelfLosingHtmlTagReg = (attr, tag = 'meta') => new RegExp(`<${tag}\\s[^>]*\\w+=['|"]([a-zA-Z]|:|\\s)*${attr}['|"][^>]*\\/?>`);
|
|
56
|
-
/**
|
|
57
|
-
* Extracts the page title from HTML string.
|
|
58
|
-
*
|
|
59
|
-
* Attempts to find the title in the following order:
|
|
60
|
-
* 1. Meta tags with 'title' attribute (e.g., `<meta property="og:title" content="...">`)
|
|
61
|
-
* 2. Standard HTML `<title>` tag
|
|
62
|
-
*
|
|
63
|
-
* @param htmlString - The HTML content to parse
|
|
64
|
-
* @returns The extracted title, or undefined if not found
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* ```typescript
|
|
68
|
-
* const title = matchTitleByMetaTag('<title>Example Page</title>')
|
|
69
|
-
* // Returns: "Example Page"
|
|
70
|
-
* ```
|
|
71
|
-
*
|
|
72
|
-
* @internal
|
|
73
|
-
*/
|
|
74
9
|
function matchTitleByMetaTag(htmlString) {
|
|
75
10
|
let title;
|
|
76
11
|
const metas = htmlString.match(containArrSelfLosingHtmlTagReg('title'));
|
|
@@ -89,24 +24,6 @@ function matchTitleByMetaTag(htmlString) {
|
|
|
89
24
|
}
|
|
90
25
|
return title;
|
|
91
26
|
}
|
|
92
|
-
/**
|
|
93
|
-
* Extracts the page description from HTML string.
|
|
94
|
-
*
|
|
95
|
-
* Searches for description in meta tags, supporting both standard and OGP formats:
|
|
96
|
-
* - `<meta name="description" content="...">`
|
|
97
|
-
* - `<meta property="og:description" content="...">`
|
|
98
|
-
*
|
|
99
|
-
* @param htmlString - The HTML content to parse
|
|
100
|
-
* @returns The extracted description, or undefined if not found
|
|
101
|
-
*
|
|
102
|
-
* @example
|
|
103
|
-
* ```typescript
|
|
104
|
-
* const desc = matchDescriptionByMetaTag('<meta name="description" content="A great site">')
|
|
105
|
-
* // Returns: "A great site"
|
|
106
|
-
* ```
|
|
107
|
-
*
|
|
108
|
-
* @internal
|
|
109
|
-
*/
|
|
110
27
|
function matchDescriptionByMetaTag(htmlString) {
|
|
111
28
|
let description;
|
|
112
29
|
const metas = htmlString.match(containArrSelfLosingHtmlTagReg('description'));
|
|
@@ -117,24 +34,6 @@ function matchDescriptionByMetaTag(htmlString) {
|
|
|
117
34
|
}
|
|
118
35
|
return description;
|
|
119
36
|
}
|
|
120
|
-
/**
|
|
121
|
-
* Extracts the page logo/icon URL from HTML string.
|
|
122
|
-
*
|
|
123
|
-
* Attempts to find the logo in the following order:
|
|
124
|
-
* 1. OGP image tag: `<meta property="og:image" content="...">`
|
|
125
|
-
* 2. Icon link tag: `<link rel="icon" href="...">`
|
|
126
|
-
*
|
|
127
|
-
* @param htmlString - The HTML content to parse
|
|
128
|
-
* @returns The extracted logo URL (may be relative), or undefined if not found
|
|
129
|
-
*
|
|
130
|
-
* @example
|
|
131
|
-
* ```typescript
|
|
132
|
-
* const logo = matchLogoByLinkOrMetaTag('<link rel="icon" href="/favicon.ico">')
|
|
133
|
-
* // Returns: "/favicon.ico"
|
|
134
|
-
* ```
|
|
135
|
-
*
|
|
136
|
-
* @internal
|
|
137
|
-
*/
|
|
138
37
|
function matchLogoByLinkOrMetaTag(htmlString) {
|
|
139
38
|
let logo;
|
|
140
39
|
const metas = htmlString.match(containArrSelfLosingHtmlTagReg('image'));
|
|
@@ -147,7 +46,6 @@ function matchLogoByLinkOrMetaTag(htmlString) {
|
|
|
147
46
|
const linkHtmlTags = htmlString.match(containArrSelfLosingHtmlTagReg('icon', 'link'));
|
|
148
47
|
if (linkHtmlTags?.length) {
|
|
149
48
|
const content = linkHtmlTags[0].match(HrefAttrValueHtmlLinkTagReg);
|
|
150
|
-
// logo 判断是否是完整地址
|
|
151
49
|
if (content && isString(content[1]))
|
|
152
50
|
logo = content[1];
|
|
153
51
|
}
|
|
@@ -157,43 +55,17 @@ function matchLogoByLinkOrMetaTag(htmlString) {
|
|
|
157
55
|
/**
|
|
158
56
|
* Parses HTML string to extract structured metadata for link card generation.
|
|
159
57
|
*
|
|
160
|
-
* This is the main parsing function that extracts title, description, and logo
|
|
161
|
-
* from an HTML page. It handles both absolute and relative URLs, converting
|
|
162
|
-
* relative logo paths to absolute URLs when necessary.
|
|
163
|
-
*
|
|
164
58
|
* @param htmlString - The HTML content to parse
|
|
165
|
-
* @param url - The URL of the page
|
|
166
|
-
* @returns Parsed metadata object, or null if no valid metadata
|
|
167
|
-
*
|
|
168
|
-
* @example
|
|
169
|
-
* ```typescript
|
|
170
|
-
* const html = '<title>Example</title><meta name="description" content="Test">'
|
|
171
|
-
* const metadata = parserMetadata(html, 'https://example.com')
|
|
172
|
-
* // Returns: { title: 'Example', description: 'Test', logo: '...' }
|
|
173
|
-
* ```
|
|
174
|
-
*
|
|
175
|
-
* @remarks
|
|
176
|
-
* - Returns null if all metadata fields (title, description, logo) are empty
|
|
177
|
-
* - Relative logo URLs are converted to absolute URLs using the page's origin
|
|
178
|
-
* - Falls back to a default logo if no logo can be found
|
|
179
|
-
*
|
|
180
|
-
* @todo Handle protocol-relative URLs like `//img.example.com/logo.png`
|
|
59
|
+
* @param url - The URL of the page
|
|
60
|
+
* @returns Parsed metadata object, or null if no valid metadata found
|
|
181
61
|
*/
|
|
182
62
|
export function parserMetadata(htmlString, url) {
|
|
183
|
-
/**
|
|
184
|
-
* Converts a potentially relative logo URL to an absolute URL.
|
|
185
|
-
*
|
|
186
|
-
* @param logo - The logo URL (may be relative or absolute)
|
|
187
|
-
* @returns Absolute URL for the logo, or default logo if input is invalid
|
|
188
|
-
*
|
|
189
|
-
* @internal
|
|
190
|
-
*/
|
|
191
63
|
function absolute(logo) {
|
|
192
64
|
if (!logo)
|
|
193
65
|
return DEFAULT_LOGO;
|
|
194
66
|
return extractUrl(logo)
|
|
195
67
|
? logo
|
|
196
|
-
: `${extractUrl(url)?.origin}${cleanPath(`/${logo}`)}`;
|
|
68
|
+
: `${extractUrl(url)?.origin}${cleanPath(`/${logo}`)}`;
|
|
197
69
|
}
|
|
198
70
|
const metadata = {
|
|
199
71
|
title: matchTitleByMetaTag(htmlString),
|
|
@@ -205,17 +77,6 @@ export function parserMetadata(htmlString, url) {
|
|
|
205
77
|
else
|
|
206
78
|
return metadata;
|
|
207
79
|
}
|
|
208
|
-
/**
|
|
209
|
-
* Checks if an object contains only undefined or empty string values.
|
|
210
|
-
*
|
|
211
|
-
* This utility function is used to determine if any valid metadata was extracted
|
|
212
|
-
* from a page. If all fields are empty, the metadata is considered invalid.
|
|
213
|
-
*
|
|
214
|
-
* @param obj - Object to check (typically a metadata object)
|
|
215
|
-
* @returns true if the object has no non-empty string values, false otherwise
|
|
216
|
-
*
|
|
217
|
-
* @internal
|
|
218
|
-
*/
|
|
219
80
|
function isEmptyStringObject(obj) {
|
|
220
81
|
return !Object.values(obj).filter((v) => isString(v)).length;
|
|
221
82
|
}
|
package/dist/assemble/style.js
CHANGED
|
@@ -1,49 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Style generation utilities for link card rendering.
|
|
3
|
-
*
|
|
4
|
-
* This module provides functions to generate inline CSS styles and class names
|
|
5
|
-
* for the link card components. The styles support customizable colors and
|
|
6
|
-
* responsive design with text ellipsis for long content.
|
|
7
|
-
*
|
|
8
|
-
* @module style
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* Converts a camelCase string to hyphenated kebab-case.
|
|
12
|
-
*
|
|
13
|
-
* This is used to convert JavaScript style property names (e.g., `backgroundColor`)
|
|
14
|
-
* to CSS property names (e.g., `background-color`).
|
|
15
|
-
*
|
|
16
|
-
* @param str - The camelCase string to convert
|
|
17
|
-
* @returns The hyphenated kebab-case string
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```typescript
|
|
21
|
-
* hyphenate('backgroundColor') // Returns: 'background-color'
|
|
22
|
-
* hyphenate('fontSize') // Returns: 'font-size'
|
|
23
|
-
* ```
|
|
24
|
-
*
|
|
25
|
-
* @internal
|
|
26
|
-
*/
|
|
27
1
|
function hyphenate(str) {
|
|
28
2
|
return str.replace(/\B([A-Z])/g, '-$1').toLowerCase();
|
|
29
3
|
}
|
|
30
|
-
/**
|
|
31
|
-
* Joins style properties into a CSS string.
|
|
32
|
-
*
|
|
33
|
-
* Converts a JavaScript object of style properties into a semicolon-separated
|
|
34
|
-
* CSS string suitable for inline styles.
|
|
35
|
-
*
|
|
36
|
-
* @param style - Object containing CSS properties and values
|
|
37
|
-
* @returns A string of CSS declarations
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```typescript
|
|
41
|
-
* join({ fontSize: '16px', color: 'red' })
|
|
42
|
-
* // Returns: 'font-size: 16px; color: red;'
|
|
43
|
-
* ```
|
|
44
|
-
*
|
|
45
|
-
* @internal
|
|
46
|
-
*/
|
|
47
4
|
function join(style) {
|
|
48
5
|
return Object.entries(style)
|
|
49
6
|
.map(([k, v]) => {
|
|
@@ -53,40 +10,9 @@ function join(style) {
|
|
|
53
10
|
.filter(Boolean)
|
|
54
11
|
.join(' ');
|
|
55
12
|
}
|
|
56
|
-
/**
|
|
57
|
-
* Wraps CSS declarations in an inline style attribute.
|
|
58
|
-
*
|
|
59
|
-
* @param style - Object containing CSS properties and values
|
|
60
|
-
* @returns A complete HTML style attribute string
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* ```typescript
|
|
64
|
-
* inlineStyle({ color: 'red', fontSize: '16px' })
|
|
65
|
-
* // Returns: 'style="color: red; font-size: 16px;"'
|
|
66
|
-
* ```
|
|
67
|
-
*
|
|
68
|
-
* @internal
|
|
69
|
-
*/
|
|
70
13
|
function inlineStyle(style) {
|
|
71
14
|
return `style="${join(style)}"`;
|
|
72
15
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Generates CSS properties for text ellipsis with line clamping.
|
|
75
|
-
*
|
|
76
|
-
* Creates styles that truncate text after a specified number of lines with
|
|
77
|
-
* an ellipsis. Uses both WebKit-specific properties and standard line-clamp.
|
|
78
|
-
*
|
|
79
|
-
* @param line - Maximum number of lines to display before truncation
|
|
80
|
-
* @returns CSS properties object for ellipsis effect
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* ```typescript
|
|
84
|
-
* ellipsisStyle(2)
|
|
85
|
-
* // Returns styles that truncate text after 2 lines with "..."
|
|
86
|
-
* ```
|
|
87
|
-
*
|
|
88
|
-
* @internal
|
|
89
|
-
*/
|
|
90
16
|
const ellipsisStyle = (line) => ({
|
|
91
17
|
'-webkit-box-orient': 'vertical',
|
|
92
18
|
'-webkit-line-clamp': line,
|
|
@@ -100,30 +26,6 @@ const ellipsisStyle = (line) => ({
|
|
|
100
26
|
});
|
|
101
27
|
/**
|
|
102
28
|
* Generates complete inline styles for all link card components.
|
|
103
|
-
*
|
|
104
|
-
* Creates a set of inline style strings for each part of the link card:
|
|
105
|
-
* - Container: main card box with border and background
|
|
106
|
-
* - Image: logo/icon display
|
|
107
|
-
* - Texts: wrapper for text content
|
|
108
|
-
* - Title: card title (2-line ellipsis)
|
|
109
|
-
* - Domain: domain name with underline
|
|
110
|
-
* - Description: description text (2-line ellipsis)
|
|
111
|
-
*
|
|
112
|
-
* The styles are inspired by VitePress's VPFeature component design.
|
|
113
|
-
*
|
|
114
|
-
* The container uses CSS custom properties for theming:
|
|
115
|
-
* - `--vitepress-linkcard-border-color`: Border color (default: #7d7d7dff)
|
|
116
|
-
* - `--vitepress-linkcard-bg-color`: Background color (default: transparent)
|
|
117
|
-
*
|
|
118
|
-
* @returns Object containing style attribute strings for each card component
|
|
119
|
-
*
|
|
120
|
-
* @example
|
|
121
|
-
* ```typescript
|
|
122
|
-
* const styles = STYLE()
|
|
123
|
-
* // Use in HTML: <div ${styles.container}>...</div>
|
|
124
|
-
* ```
|
|
125
|
-
*
|
|
126
|
-
* @see {@link https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/components/VPFeature.vue | VPFeature component}
|
|
127
29
|
*/
|
|
128
30
|
export const STYLE = () => ({
|
|
129
31
|
a: inlineStyle({
|
|
@@ -148,14 +50,14 @@ export const STYLE = () => ({
|
|
|
148
50
|
img: inlineStyle({
|
|
149
51
|
borderRadius: '0px 12px 12px 0px',
|
|
150
52
|
maxWidth: '40%',
|
|
151
|
-
height: '
|
|
53
|
+
height: '129px',
|
|
152
54
|
flexShrink: 0,
|
|
153
55
|
objectFit: 'contain',
|
|
154
56
|
overflow: 'hidden'
|
|
155
57
|
}),
|
|
156
58
|
texts: inlineStyle({
|
|
157
59
|
flex: '1 1 0%',
|
|
158
|
-
minWidth: '0'
|
|
60
|
+
minWidth: '0'
|
|
159
61
|
}),
|
|
160
62
|
title: inlineStyle({
|
|
161
63
|
...ellipsisStyle(2),
|
|
@@ -181,29 +83,6 @@ export const STYLE = () => ({
|
|
|
181
83
|
margin: '8px 16px 0px 16px'
|
|
182
84
|
})
|
|
183
85
|
});
|
|
184
|
-
/**
|
|
185
|
-
* Generates CSS class names with a custom prefix.
|
|
186
|
-
*
|
|
187
|
-
* When using the `classPrefix` option, this function creates consistent
|
|
188
|
-
* class names for all card components following a BEM-like naming convention.
|
|
189
|
-
*
|
|
190
|
-
* @param prefix - The prefix to prepend to all class names
|
|
191
|
-
* @returns Object mapping component names to their class names
|
|
192
|
-
*
|
|
193
|
-
* @example
|
|
194
|
-
* ```typescript
|
|
195
|
-
* const classes = classNames('my-card')
|
|
196
|
-
* // Returns: {
|
|
197
|
-
* // container: 'my-card__container',
|
|
198
|
-
* // title: 'my-card__texts--title',
|
|
199
|
-
* // ...
|
|
200
|
-
* // }
|
|
201
|
-
* ```
|
|
202
|
-
*
|
|
203
|
-
* @remarks
|
|
204
|
-
* When class names are used instead of inline styles, you must provide
|
|
205
|
-
* your own CSS definitions for these classes.
|
|
206
|
-
*/
|
|
207
86
|
export const classNames = (prefix) => ({
|
|
208
87
|
container: `${prefix}__container`,
|
|
209
88
|
img: `${prefix}__img`,
|
package/dist/assemble/url.js
CHANGED
|
@@ -1,26 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* URL utility functions for parsing and manipulating URLs.
|
|
3
|
-
*
|
|
4
|
-
* @module url
|
|
5
|
-
*/
|
|
6
1
|
/**
|
|
7
2
|
* Parses a URL string and returns a URL object.
|
|
8
3
|
*
|
|
9
|
-
* This is a wrapper around the native URL constructor that provides
|
|
10
|
-
* a consistent interface for URL parsing throughout the codebase.
|
|
11
|
-
*
|
|
12
4
|
* @param url - The URL string to parse
|
|
13
5
|
* @returns A URL object containing the parsed components
|
|
14
|
-
* @throws {TypeError} If the URL string is invalid
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```typescript
|
|
18
|
-
* const urlObj = extractUrl('https://example.com/path')
|
|
19
|
-
* console.log(urlObj.origin) // "https://example.com"
|
|
20
|
-
* console.log(urlObj.pathname) // "/path"
|
|
21
|
-
* ```
|
|
22
|
-
*
|
|
23
|
-
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/URL | MDN - URL}
|
|
24
6
|
*/
|
|
25
7
|
export function extractUrl(url) {
|
|
26
8
|
return new URL(url);
|
|
@@ -28,17 +10,8 @@ export function extractUrl(url) {
|
|
|
28
10
|
/**
|
|
29
11
|
* Removes duplicate consecutive slashes from a path string.
|
|
30
12
|
*
|
|
31
|
-
* This function normalizes paths by replacing multiple consecutive slashes
|
|
32
|
-
* with a single slash. Useful for constructing clean URLs from path segments.
|
|
33
|
-
*
|
|
34
13
|
* @param path - The path string to clean
|
|
35
14
|
* @returns The cleaned path with no consecutive slashes
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```typescript
|
|
39
|
-
* cleanPath('/path//to///file') // Returns: '/path/to/file'
|
|
40
|
-
* cleanPath('//images//logo.png') // Returns: '/images/logo.png'
|
|
41
|
-
* ```
|
|
42
15
|
*/
|
|
43
16
|
export function cleanPath(path) {
|
|
44
17
|
return path.replace(/\/\//g, '/');
|
package/dist/assemble/xhr.js
CHANGED
|
@@ -1,52 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* XHR module for fetching remote URL content.
|
|
3
|
-
*
|
|
4
|
-
* This module provides both synchronous and asynchronous HTTP GET request functionality
|
|
5
|
-
* with built-in caching. It works in both browser and Node.js environments by using
|
|
6
|
-
* the appropriate XMLHttpRequest implementation.
|
|
7
|
-
*
|
|
8
|
-
* @module xhr
|
|
9
|
-
* @todo Replace xmlhttprequest with a modern alternative (e.g., node-fetch or axios)
|
|
10
|
-
*/
|
|
11
|
-
// Refactor: xmlhttprequest will be replaced later
|
|
12
1
|
// @ts-expect-error: xmlhttprequest has no types
|
|
13
2
|
import xhrForNode from 'xmlhttprequest';
|
|
14
3
|
import { inBrowser, isString } from '@luckrya/utility';
|
|
15
|
-
/**
|
|
16
|
-
* In-memory cache for storing fetched HTML content by URL.
|
|
17
|
-
* Prevents redundant network requests for the same URLs.
|
|
18
|
-
* @internal
|
|
19
|
-
*/
|
|
20
4
|
const cache = new Map();
|
|
21
|
-
/**
|
|
22
|
-
* XMLHttpRequest implementation that works in both browser and Node.js.
|
|
23
|
-
* @internal
|
|
24
|
-
*/
|
|
25
5
|
const XHR = inBrowser ? window.XMLHttpRequest : xhrForNode.XMLHttpRequest;
|
|
26
6
|
/**
|
|
27
7
|
* Performs a synchronous HTTP GET request to fetch HTML content from a URL.
|
|
28
8
|
*
|
|
29
|
-
* This function fetches the content synchronously, which means it blocks execution
|
|
30
|
-
* until the request completes. The response is cached to avoid redundant requests.
|
|
31
|
-
*
|
|
32
9
|
* @param url - The URL to fetch content from
|
|
33
10
|
* @returns The HTML content as a string, or undefined if the request fails
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* ```typescript
|
|
37
|
-
* const html = sync('https://example.com')
|
|
38
|
-
* if (html) {
|
|
39
|
-
* console.log('Fetched HTML content')
|
|
40
|
-
* }
|
|
41
|
-
* ```
|
|
42
|
-
*
|
|
43
|
-
* @remarks
|
|
44
|
-
* - Returns cached content if available
|
|
45
|
-
* - Only returns content if HTTP status is 200
|
|
46
|
-
* - Errors are logged to console but not thrown
|
|
47
|
-
* - Synchronous requests block the event loop - use with caution
|
|
48
|
-
*
|
|
49
|
-
* @see {@link async} for an asynchronous alternative
|
|
50
11
|
*/
|
|
51
12
|
export function sync(url) {
|
|
52
13
|
if (cache.has(url))
|
|
@@ -70,33 +31,8 @@ export function sync(url) {
|
|
|
70
31
|
/**
|
|
71
32
|
* Performs an asynchronous HTTP GET request to fetch HTML content from a URL.
|
|
72
33
|
*
|
|
73
|
-
* This function fetches content asynchronously using Promises. The response is
|
|
74
|
-
* cached to avoid redundant requests.
|
|
75
|
-
*
|
|
76
34
|
* @param url - The URL to fetch content from
|
|
77
|
-
* @returns A Promise that resolves to the HTML content string
|
|
78
|
-
*
|
|
79
|
-
* @example
|
|
80
|
-
* ```typescript
|
|
81
|
-
* async function fetchPage() {
|
|
82
|
-
* try {
|
|
83
|
-
* const html = await async('https://example.com')
|
|
84
|
-
* if (html) {
|
|
85
|
-
* console.log('Fetched HTML content')
|
|
86
|
-
* }
|
|
87
|
-
* } catch (error) {
|
|
88
|
-
* console.error('Failed to fetch:', error)
|
|
89
|
-
* }
|
|
90
|
-
* }
|
|
91
|
-
* ```
|
|
92
|
-
*
|
|
93
|
-
* @remarks
|
|
94
|
-
* - Returns cached content if available
|
|
95
|
-
* - Only resolves with content if HTTP status is 200 and readyState is 4
|
|
96
|
-
* - Promise is rejected if the request throws an error
|
|
97
|
-
* - Preferred over `sync()` for non-blocking operations
|
|
98
|
-
*
|
|
99
|
-
* @see {@link sync} for a synchronous alternative
|
|
35
|
+
* @returns A Promise that resolves to the HTML content string
|
|
100
36
|
*/
|
|
101
37
|
export function async(url) {
|
|
102
38
|
return new Promise((resolve, reject) => {
|
|
@@ -3,45 +3,10 @@ import { getUrlMetadata, generateCardDomFragment } from './assemble';
|
|
|
3
3
|
/**
|
|
4
4
|
* Markdown-it plugin that converts specially-formatted links into rich link preview cards.
|
|
5
5
|
*
|
|
6
|
-
* This plugin intercepts markdown links that start with the `@:` prefix and transforms them
|
|
7
|
-
* into interactive cards displaying metadata (title, description, logo) fetched from the target URL.
|
|
8
|
-
*
|
|
9
|
-
* ## Usage
|
|
10
|
-
*
|
|
11
|
-
* In your markdown file:
|
|
12
|
-
* ```md
|
|
13
|
-
* [@:https://example.com](Link Title)
|
|
14
|
-
* ```
|
|
15
|
-
*
|
|
16
|
-
* Plugin configuration:
|
|
17
|
-
* ```typescript
|
|
18
|
-
* import MarkdownIt from 'markdown-it'
|
|
19
|
-
* import { linkToCardPlugin } from 'vitepress-linkcard'
|
|
20
|
-
*
|
|
21
|
-
* const md = new MarkdownIt()
|
|
22
|
-
* md.use(linkToCardPlugin, {
|
|
23
|
-
* target: '_blank'
|
|
24
|
-
* })
|
|
25
|
-
* ```
|
|
26
|
-
*
|
|
27
6
|
* @param md - The markdown-it instance
|
|
28
7
|
* @param pluginOptions - Configuration options for the plugin
|
|
29
|
-
*
|
|
30
|
-
* @see {@link LinkToCardPluginOptions} for available options
|
|
31
8
|
*/
|
|
32
9
|
export const linkToCardPlugin = (md, pluginOptions = {}) => {
|
|
33
|
-
/**
|
|
34
|
-
* Parses a link href to determine if it's a card link and extracts the URL.
|
|
35
|
-
*
|
|
36
|
-
* Card links are identified by the `@:` prefix (e.g., `@:https://example.com`).
|
|
37
|
-
*
|
|
38
|
-
* @param href - The href attribute from a markdown link token
|
|
39
|
-
* @returns An object containing:
|
|
40
|
-
* - `isCardLink`: true if the href matches the card link pattern
|
|
41
|
-
* - `url`: the extracted URL (without the `@:` prefix)
|
|
42
|
-
*
|
|
43
|
-
* @internal
|
|
44
|
-
*/
|
|
45
10
|
function parseCardLinkHref(href) {
|
|
46
11
|
const tagRegexp = new RegExp(`^(${'@'}:)([a-zA-Z0-9]+.*)`);
|
|
47
12
|
const match = href?.match(tagRegexp);
|
|
@@ -50,27 +15,10 @@ export const linkToCardPlugin = (md, pluginOptions = {}) => {
|
|
|
50
15
|
url: match?.[2]
|
|
51
16
|
};
|
|
52
17
|
}
|
|
53
|
-
/**
|
|
54
|
-
* Assembles the HTML template for a link card by fetching metadata and rendering.
|
|
55
|
-
*
|
|
56
|
-
* This function:
|
|
57
|
-
* 1. Fetches URL metadata (title, description, logo)
|
|
58
|
-
* 2. Hides remaining tokens in the link to prevent duplicate content
|
|
59
|
-
* 3. Extracts the link title from tokens
|
|
60
|
-
* 4. Renders the card using either a custom renderer or the default one
|
|
61
|
-
*
|
|
62
|
-
* @param options - Contains the URL and token information
|
|
63
|
-
* @param options.url - The URL to create a card for
|
|
64
|
-
* @param options.tokens - Array of markdown-it tokens
|
|
65
|
-
* @param options.i - Current token index
|
|
66
|
-
* @returns HTML string of the rendered card, or undefined if metadata cannot be fetched
|
|
67
|
-
*
|
|
68
|
-
* @internal
|
|
69
|
-
*/
|
|
70
18
|
function assembleCardTpl(options) {
|
|
71
19
|
const urlMetadata = getUrlMetadata(options.url);
|
|
72
20
|
if (urlMetadata) {
|
|
73
|
-
ignoreRestToken(options.tokens, options.i);
|
|
21
|
+
ignoreRestToken(options.tokens, options.i);
|
|
74
22
|
const cardDomOptions = {
|
|
75
23
|
href: options.url,
|
|
76
24
|
linkTitle: joinLinkTitle(options.tokens),
|
|
@@ -82,19 +30,6 @@ export const linkToCardPlugin = (md, pluginOptions = {}) => {
|
|
|
82
30
|
: generateCardDomFragment(urlMetadata, cardDomOptions);
|
|
83
31
|
}
|
|
84
32
|
}
|
|
85
|
-
/**
|
|
86
|
-
* Custom inline renderer that preserves hidden token handling.
|
|
87
|
-
*
|
|
88
|
-
* This overrides the default markdown-it inline renderer to properly handle
|
|
89
|
-
* tokens marked as hidden, which is necessary for the card link processing.
|
|
90
|
-
*
|
|
91
|
-
* @param tokens - Array of tokens to render
|
|
92
|
-
* @param rootOptions - Markdown-it rendering options
|
|
93
|
-
* @param env - Markdown-it environment variables
|
|
94
|
-
* @returns The rendered HTML string
|
|
95
|
-
*
|
|
96
|
-
* @see {@link https://markdown-it.github.io/markdown-it/#MarkdownIt.renderInline | MarkdownIt.renderInline}
|
|
97
|
-
*/
|
|
98
33
|
md.renderer.renderInline = (tokens, rootOptions, env) => {
|
|
99
34
|
let result = '';
|
|
100
35
|
for (let i = 0; i < tokens.length; i++) {
|
|
@@ -112,24 +47,6 @@ export const linkToCardPlugin = (md, pluginOptions = {}) => {
|
|
|
112
47
|
}
|
|
113
48
|
return result;
|
|
114
49
|
};
|
|
115
|
-
/**
|
|
116
|
-
* Custom renderer for link_open tokens that intercepts card links.
|
|
117
|
-
*
|
|
118
|
-
* This function checks if a link is a card link (prefixed with `@:`) and if so,
|
|
119
|
-
* generates and returns the card HTML. Regular links are passed through to the
|
|
120
|
-
* default renderer.
|
|
121
|
-
*
|
|
122
|
-
* @param tokens - Array of tokens being rendered
|
|
123
|
-
* @param i - Current token index
|
|
124
|
-
* @param rootOptions - Markdown-it rendering options
|
|
125
|
-
* @param env - Markdown-it environment (must be present even if unused to maintain signature)
|
|
126
|
-
* @param self - The renderer instance
|
|
127
|
-
* @returns HTML string for the link (either a card or a regular link)
|
|
128
|
-
*
|
|
129
|
-
* @remarks
|
|
130
|
-
* The `env` parameter must not be removed even if unused, as it's part of the
|
|
131
|
-
* markdown-it renderer signature.
|
|
132
|
-
*/
|
|
133
50
|
md.renderer.rules.link_open = (tokens, i, rootOptions, env, self) => {
|
|
134
51
|
const token = tokens[i];
|
|
135
52
|
const isLinkOpenToken = token.tag === 'a' && token.type === 'link_open';
|
|
@@ -143,38 +60,12 @@ export const linkToCardPlugin = (md, pluginOptions = {}) => {
|
|
|
143
60
|
return self.renderToken(tokens, i, rootOptions);
|
|
144
61
|
};
|
|
145
62
|
};
|
|
146
|
-
/**
|
|
147
|
-
* Marks all tokens except the one at index `i` as hidden.
|
|
148
|
-
*
|
|
149
|
-
* This is used to prevent the link text and closing tag from being rendered
|
|
150
|
-
* when a card link is detected, as the card HTML replaces the entire link element.
|
|
151
|
-
*
|
|
152
|
-
* @param tokens - Array of tokens to modify
|
|
153
|
-
* @param i - Index of the token to keep visible
|
|
154
|
-
*
|
|
155
|
-
* @todo Handle softbreak tokens properly
|
|
156
|
-
* @see {@link https://markdown-it.github.io/ | markdown-it documentation}
|
|
157
|
-
*
|
|
158
|
-
* @internal
|
|
159
|
-
*/
|
|
160
63
|
function ignoreRestToken(tokens, i) {
|
|
161
64
|
tokens.forEach((token, index) => {
|
|
162
65
|
if (index !== i)
|
|
163
66
|
token.hidden = true;
|
|
164
67
|
});
|
|
165
68
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Extracts and joins the content from hidden tokens to form the link title.
|
|
168
|
-
*
|
|
169
|
-
* When processing a card link, the link text tokens are marked as hidden.
|
|
170
|
-
* This function collects the content from those hidden tokens to use as the
|
|
171
|
-
* card's title attribute.
|
|
172
|
-
*
|
|
173
|
-
* @param tokens - Array of tokens to extract content from
|
|
174
|
-
* @returns The concatenated content from all hidden tokens
|
|
175
|
-
*
|
|
176
|
-
* @internal
|
|
177
|
-
*/
|
|
178
69
|
function joinLinkTitle(tokens) {
|
|
179
70
|
return tokens
|
|
180
71
|
.map(({ hidden, content }) => {
|
package/package.json
CHANGED