Skip to content

Commit 9b4a34a

Browse files
authored
Merge pull request #251 from softnetics/benz/fix/new-richtext-design
feat: richtext-editor designs and add link-extension
2 parents b49fe5d + a4812ac commit 9b4a34a

21 files changed

Lines changed: 521 additions & 267 deletions

File tree

.changeset/swift-turtles-pump.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@example/erp": patch
3+
"@example/ui-playground": patch
4+
"@genseki/react": patch
5+
---
6+
7+
feat: richtext-editor designs and add link-extension
Lines changed: 135 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,173 @@
11
'use client'
2+
import type React from 'react'
3+
4+
import Color from '@tiptap/extension-color'
5+
import Link from '@tiptap/extension-link'
6+
import TextAlign from '@tiptap/extension-text-align'
7+
import TextStyle from '@tiptap/extension-text-style'
8+
import Underline from '@tiptap/extension-underline'
9+
import StarterKit from '@tiptap/starter-kit'
10+
211
import {
312
EditorBar,
4-
EditorBarGroup,
5-
EditorBgColorPicker,
613
EditorTextColorPicker,
714
MarkButton,
815
RedoButton,
916
SelectTextStyle,
1017
TextAlignButton,
1118
TextAlignButtonsGroup,
12-
ToggleGroup,
1319
ToolbarGroup,
1420
ToolbarSeparator,
1521
UndoButton,
1622
UploadImageButton,
1723
} from '@genseki/react'
24+
import {
25+
BackColorExtension,
26+
CustomImageExtension,
27+
ImageUploadNodeExtension,
28+
SelectionExtension,
29+
} from '@genseki/react'
1830

1931
export const EditorSlotBefore = () => {
2032
return (
2133
<EditorBar>
22-
<EditorBarGroup>
23-
<EditorBgColorPicker />
24-
<EditorTextColorPicker />
25-
<SelectTextStyle />
26-
</EditorBarGroup>
27-
<EditorBarGroup className="items-center">
28-
<UndoButton />
29-
<RedoButton />
30-
</EditorBarGroup>
31-
<ToolbarSeparator className="h-auto" />
34+
<SelectTextStyle />
3235
<ToolbarGroup className="items-center">
3336
<MarkButton type="bold" />
3437
<MarkButton type="italic" />
3538
<MarkButton type="underline" />
36-
<MarkButton type="strike" />
39+
<MarkButton type="link" />
3740
</ToolbarGroup>
3841
<ToolbarSeparator className="h-auto" />
39-
<ToggleGroup className="items-center">
40-
<MarkButton type="bulletList" />
41-
</ToggleGroup>
42+
<EditorTextColorPicker />
4243
<ToolbarSeparator className="h-auto" />
4344
<ToolbarGroup className="items-center">
4445
<TextAlignButtonsGroup>
4546
<TextAlignButton type="left" />
4647
<TextAlignButton type="center" />
4748
<TextAlignButton type="right" />
48-
<TextAlignButton type="justify" />
49+
<MarkButton type="bulletList" />
4950
</TextAlignButtonsGroup>
5051
</ToolbarGroup>
5152
<ToolbarSeparator />
5253
<ToolbarSeparator className="h-auto" />
5354
<UploadImageButton />
55+
<RedoButton />
56+
<UndoButton />
5457
</EditorBar>
5558
)
5659
}
60+
61+
export const postEditorProviderProps = {
62+
immediatelyRender: false,
63+
shouldRerenderOnTransaction: true,
64+
content: {
65+
type: 'doc',
66+
content: [
67+
{
68+
type: 'paragraph',
69+
content: [
70+
{
71+
type: 'text',
72+
text: '',
73+
},
74+
],
75+
},
76+
],
77+
},
78+
slotBefore: <EditorSlotBefore />,
79+
extensions: [
80+
Color,
81+
BackColorExtension,
82+
Underline.configure({ HTMLAttributes: { class: 'earth-underline' } }),
83+
SelectionExtension,
84+
TextStyle,
85+
TextAlign.configure({
86+
types: ['heading', 'paragraph'],
87+
alignments: ['left', 'center', 'right', 'justify'],
88+
defaultAlignment: 'left',
89+
}),
90+
StarterKit.configure({
91+
bold: { HTMLAttributes: { class: 'bold large-black' } },
92+
paragraph: { HTMLAttributes: { class: 'paragraph-custom' } },
93+
heading: { HTMLAttributes: { class: 'heading-custom' } },
94+
bulletList: { HTMLAttributes: { class: 'list-custom' } },
95+
orderedList: { HTMLAttributes: { class: 'ordered-list' } },
96+
code: { HTMLAttributes: { class: 'code' } },
97+
codeBlock: { HTMLAttributes: { class: 'code-block' } },
98+
horizontalRule: { HTMLAttributes: { class: 'hr-custom' } },
99+
italic: { HTMLAttributes: { class: 'italic-text' } },
100+
strike: { HTMLAttributes: { class: 'strikethrough' } },
101+
blockquote: { HTMLAttributes: { class: 'blockquote-custom' } },
102+
}),
103+
CustomImageExtension.configure({ HTMLAttributes: { className: 'image-displayer' } }),
104+
ImageUploadNodeExtension.configure({
105+
showProgress: false,
106+
accept: 'image/*',
107+
maxSize: 1024 * 1024 * 10, // 10MB
108+
limit: 3,
109+
pathName: 'posts/rich-text',
110+
}),
111+
Link.configure({
112+
openOnClick: false,
113+
autolink: true,
114+
defaultProtocol: 'https',
115+
protocols: ['http', 'https'],
116+
isAllowedUri: (url, ctx) => {
117+
try {
118+
// construct URL
119+
const parsedUrl = url.includes(':')
120+
? new URL(url)
121+
: new URL(`${ctx.defaultProtocol}://${url}`)
122+
123+
// use default validation
124+
if (!ctx.defaultValidate(parsedUrl.href)) {
125+
return false
126+
}
127+
128+
// disallowed protocols
129+
const disallowedProtocols = ['ftp', 'file', 'mailto']
130+
const protocol = parsedUrl.protocol.replace(':', '')
131+
132+
if (disallowedProtocols.includes(protocol)) {
133+
return false
134+
}
135+
136+
// only allow protocols specified in ctx.protocols
137+
const allowedProtocols = ctx.protocols.map((p) => (typeof p === 'string' ? p : p.scheme))
138+
139+
if (!allowedProtocols.includes(protocol)) {
140+
return false
141+
}
142+
143+
// disallowed domains
144+
const disallowedDomains = ['example-phishing.com', 'malicious-site.net']
145+
const domain = parsedUrl.hostname
146+
147+
if (disallowedDomains.includes(domain)) {
148+
return false
149+
}
150+
151+
// all checks have passed
152+
return true
153+
} catch {
154+
return false
155+
}
156+
},
157+
shouldAutoLink: (url) => {
158+
try {
159+
// construct URL
160+
const parsedUrl = url.includes(':') ? new URL(url) : new URL(`https://${url}`)
161+
162+
// only auto-link if the domain is not in the disallowed list
163+
const disallowedDomains = ['example-no-autolink.com', 'another-no-autolink.com']
164+
const domain = parsedUrl.hostname
165+
166+
return !disallowedDomains.includes(domain)
167+
} catch {
168+
return false
169+
}
170+
},
171+
}),
172+
],
173+
}

examples/erp/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@tanstack/react-table": "^8.21.3",
3131
"@tiptap/extension-color": "^2.26.3",
3232
"@tiptap/extension-image": "^2.26.3",
33+
"@tiptap/extension-link": "2.26.3",
3334
"@tiptap/extension-text-align": "^2.26.3",
3435
"@tiptap/extension-text-style": "^2.26.3",
3536
"@tiptap/extension-underline": "^2.26.3",

examples/ui-playground/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
"@phosphor-icons/react": "^2.1.8",
2121
"@tailwindcss/postcss": "^4.1.7",
2222
"@tanstack/react-query": "^5.71.5",
23+
"@tiptap/extension-color": "^2.26.3",
24+
"@tiptap/extension-link": "2.26.3",
25+
"@tiptap/extension-text-align": "^2.26.3",
26+
"@tiptap/extension-text-style": "^2.26.3",
27+
"@tiptap/extension-underline": "^2.26.3",
2328
"@tiptap/starter-kit": "^2.26.3",
2429
"date-fns": "^4.1.0",
2530
"next": "15.2.2",

0 commit comments

Comments
 (0)