Skip to content

Commit ba12aee

Browse files
committed
feat(text): support Link Mention format
- Fixed an issue where the newly introduced "Link Mention" feature in Notion was unsupported, causing an "unsupported text format" error. - Now, it's correctly rendered in a way similar to Notion's functionality.
1 parent b2682fe commit ba12aee

File tree

4 files changed

+188
-0
lines changed

4 files changed

+188
-0
lines changed

packages/notion-types/src/core.ts

+2
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export type InlineEquationFormat = ['e', string]
119119
export type DiscussionFormat = ['m', string]
120120
export type ExternalLinkFormat = ['‣', [string, string]]
121121
export type DateFormat = ['d', FormattedDate]
122+
export type LinkMentionFormat = ['lm', string]
122123

123124
export interface FormattedDate {
124125
type: 'date' | 'daterange' | 'datetime' | 'datetimerange'
@@ -145,6 +146,7 @@ export type SubDecoration =
145146
| ExternalLinkFormat
146147
| DiscussionFormat
147148
| ExternalObjectInstanceFormat
149+
| LinkMentionFormat
148150

149151
export type BaseDecoration = [string]
150152
export type AdditionalDecoration = [string, SubDecoration[]]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import * as React from 'react'
2+
3+
export type LinkMentionData = {
4+
href: string
5+
title: string
6+
icon_url: string
7+
description: string
8+
link_provider: string
9+
thumbnail_url: string
10+
}
11+
12+
export function LinkMention({ metadata }: { metadata: LinkMentionData }) {
13+
return (
14+
<span className='notion-link-mention'>
15+
<LinkMentionInline metadata={metadata} />
16+
<LinkMentionPreview metadata={metadata} />
17+
</span>
18+
)
19+
}
20+
21+
function LinkMentionInline({ metadata }: { metadata: LinkMentionData }) {
22+
return (
23+
<a
24+
href={metadata.href}
25+
target='_blank'
26+
rel='noopener noreferrer'
27+
className='notion-link-mention-link'
28+
>
29+
<img
30+
className='notion-link-mention-icon'
31+
src={metadata.icon_url}
32+
alt={metadata.link_provider}
33+
/>
34+
{metadata.link_provider && (
35+
<span className='notion-link-mention-provider'>
36+
{metadata.link_provider}
37+
</span>
38+
)}
39+
<span className='notion-link-mention-title'>{metadata.title}</span>
40+
</a>
41+
)
42+
}
43+
44+
function LinkMentionPreview({ metadata }: { metadata: LinkMentionData }) {
45+
return (
46+
<div className='notion-link-mention-preview'>
47+
<article className='notion-link-mention-card'>
48+
<img
49+
className='notion-link-mention-preview-thumbnail'
50+
src={metadata.thumbnail_url}
51+
alt={metadata.title}
52+
/>
53+
<div className='notion-link-mention-preview-content'>
54+
<p className='notion-link-mention-preview-title'>{metadata.title}</p>
55+
<p className='notion-link-mention-preview-description'>
56+
{metadata.description}
57+
</p>
58+
<div className='notion-link-mention-preview-footer'>
59+
<img
60+
className='notion-link-mention-preview-icon'
61+
src={metadata.icon_url}
62+
alt={metadata.link_provider}
63+
/>
64+
<span className='notion-link-mention-preview-provider'>
65+
{metadata.link_provider}
66+
</span>
67+
</div>
68+
</div>
69+
</article>
70+
</div>
71+
)
72+
}

packages/react-notion-x/src/components/text.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { formatDate, getHashFragmentValue } from '../utils'
1111
import { EOI } from './eoi'
1212
import { GracefulImage } from './graceful-image'
1313
import { PageTitle } from './page-title'
14+
import { LinkMention, type LinkMentionData } from './link-mention'
1415

1516
/**
1617
* Renders a single piece of Notion text, including basic rich text formatting.
@@ -20,6 +21,7 @@ import { PageTitle } from './page-title'
2021
* TODO: I think this implementation would be more correct if the reduce just added
2122
* attributes to the final element's style.
2223
*/
24+
2325
export function Text({
2426
value,
2527
block,
@@ -244,6 +246,11 @@ export function Text({
244246
)
245247
}
246248

249+
case 'lm': {
250+
const metadata = decorator[1] as unknown as LinkMentionData
251+
return <LinkMention metadata={metadata} />
252+
}
253+
247254
case 'eoi': {
248255
const blockId = decorator[1]
249256
const externalObjectInstance = recordMap.block[blockId]

packages/react-notion-x/src/styles.css

+107
Original file line numberDiff line numberDiff line change
@@ -2976,3 +2976,110 @@ svg.notion-page-icon {
29762976
text-decoration: underline;
29772977
cursor: pointer;
29782978
}
2979+
2980+
/* Link Mention Styles */
2981+
.notion-link-mention {
2982+
position: relative;
2983+
display: inline-flex;
2984+
align-items: center;
2985+
vertical-align: middle;
2986+
}
2987+
2988+
.notion-link-mention-link {
2989+
display: inline-flex;
2990+
align-items: center;
2991+
gap: 0.5rem;
2992+
}
2993+
2994+
.notion-link-mention-icon {
2995+
width: 1.5rem;
2996+
height: 1.5rem;
2997+
border-radius: 0.375rem;
2998+
}
2999+
3000+
.notion-link-mention-provider {
3001+
font-size: 0.875rem;
3002+
color: var(--fg-color);
3003+
}
3004+
3005+
.notion-link-mention-title {
3006+
font-size: 0.875rem;
3007+
font-weight: 600;
3008+
color: var(--fg-color);
3009+
}
3010+
3011+
/* Preview card (appears on hover) */
3012+
.notion-link-mention-preview {
3013+
display: none;
3014+
position: absolute;
3015+
top: 100%;
3016+
left: 0;
3017+
margin-top: 0.5rem;
3018+
z-index: 100000;
3019+
}
3020+
3021+
.notion-link-mention:hover .notion-link-mention-preview {
3022+
display: block;
3023+
}
3024+
3025+
.notion-link-mention-card {
3026+
width: 18rem;
3027+
height: 15rem;
3028+
background: var(--bg-color);
3029+
border-radius: 0.75rem;
3030+
border: 1px solid rgba(27, 31, 36, 0.15);
3031+
overflow: hidden;
3032+
box-shadow:
3033+
0 4px 6px -1px rgba(0, 0, 0, 0.1),
3034+
0 2px 4px -1px rgba(0, 0, 0, 0.06);
3035+
}
3036+
3037+
.notion-link-mention-preview-thumbnail {
3038+
width: 100%;
3039+
height: 55%;
3040+
object-fit: cover;
3041+
}
3042+
3043+
.notion-link-mention-preview-content {
3044+
display: flex;
3045+
flex-direction: column;
3046+
height: 45%;
3047+
padding: 0.5rem 1rem;
3048+
gap: 0.125rem;
3049+
}
3050+
3051+
.notion-link-mention-preview-title {
3052+
font-size: 0.875rem;
3053+
font-weight: 700;
3054+
color: var(--fg-color);
3055+
margin: 0;
3056+
}
3057+
3058+
.notion-link-mention-preview-description {
3059+
font-size: 0.875rem;
3060+
color: var(--fg-color);
3061+
display: -webkit-box;
3062+
-webkit-line-clamp: 2;
3063+
-webkit-box-orient: vertical;
3064+
overflow: hidden;
3065+
margin: 0;
3066+
}
3067+
3068+
.notion-link-mention-preview-footer {
3069+
display: flex;
3070+
align-items: center;
3071+
gap: 0.375rem;
3072+
margin-top: auto;
3073+
padding-bottom: 0.5rem;
3074+
}
3075+
3076+
.notion-link-mention-preview-icon {
3077+
width: 1rem;
3078+
height: 1rem;
3079+
border-radius: 0.25rem;
3080+
}
3081+
3082+
.notion-link-mention-preview-provider {
3083+
font-size: 0.875rem;
3084+
color: var(--fg-color-2);
3085+
}

0 commit comments

Comments
 (0)