Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/chatty-readers-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@example/ui-playground": patch
"@genseki/react": patch
---

feat: add richtext hyperlink extension
84 changes: 13 additions & 71 deletions examples/ui-playground/src/components/slot-before.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,32 @@
import type React from 'react'

import Color from '@tiptap/extension-color'
import Link from '@tiptap/extension-link'
import TextAlign from '@tiptap/extension-text-align'
import TextStyle from '@tiptap/extension-text-style'
import Underline from '@tiptap/extension-underline'
import StarterKit from '@tiptap/starter-kit'

import {
CustomImageExtension,
ImageUploadNodeExtension,
ToolbarGroup,
ToolbarSeparator,
} from '@genseki/react'
import {
BackColorExtension,
CustomLinkExtension,
EditorBar,
EditorBgColorPicker,
EditorTextColorPicker,
MarkButton,
RedoButton,
SelectionExtension,
SelectTextStyle,
TextAlignButton,
TextAlignButtonsGroup,
ToolbarGroup,
ToolbarSeparator,
UndoButton,
UploadImageButton,
} from '@genseki/react'
import {
BackColorExtension,
CustomImageExtension,
ImageUploadNodeExtension,
SelectionExtension,
} from '@genseki/react'
} from '@genseki/react/v2'

export const EditorSlotBefore = () => {
return (
Expand All @@ -40,6 +41,7 @@ export const EditorSlotBefore = () => {
</ToolbarGroup>
<ToolbarSeparator className="h-auto" />
<EditorTextColorPicker />
<EditorBgColorPicker />
<ToolbarSeparator className="h-auto" />
<ToolbarGroup className="items-center">
<TextAlignButtonsGroup>
Expand Down Expand Up @@ -108,66 +110,6 @@ export const editorProviderProps = {
limit: 3,
pathName: 'posts/rich-text',
}),
Link.configure({
openOnClick: false,
autolink: true,
defaultProtocol: 'https',
protocols: ['http', 'https'],
isAllowedUri: (url, ctx) => {
try {
// construct URL
const parsedUrl = url.includes(':')
? new URL(url)
: new URL(`${ctx.defaultProtocol}://${url}`)

// use default validation
if (!ctx.defaultValidate(parsedUrl.href)) {
return false
}

// disallowed protocols
const disallowedProtocols = ['ftp', 'file', 'mailto']
const protocol = parsedUrl.protocol.replace(':', '')

if (disallowedProtocols.includes(protocol)) {
return false
}

// only allow protocols specified in ctx.protocols
const allowedProtocols = ctx.protocols.map((p) => (typeof p === 'string' ? p : p.scheme))

if (!allowedProtocols.includes(protocol)) {
return false
}

// disallowed domains
const disallowedDomains = ['example-phishing.com', 'malicious-site.net']
const domain = parsedUrl.hostname

if (disallowedDomains.includes(domain)) {
return false
}

// all checks have passed
return true
} catch {
return false
}
},
shouldAutoLink: (url) => {
try {
// construct URL
const parsedUrl = url.includes(':') ? new URL(url) : new URL(`https://${url}`)

// only auto-link if the domain is not in the disallowed list
const disallowedDomains = ['example-no-autolink.com', 'another-no-autolink.com']
const domain = parsedUrl.hostname

return !disallowedDomains.includes(domain)
} catch {
return false
}
},
}),
CustomLinkExtension,
],
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ import { useCurrentEditor } from '@tiptap/react'
import { cn } from '../../../../utils/cn'
import { Toolbar, ToolbarGroup } from '../../../primitives/toolbar'

/**
* @deprecated
*/
export const EditorBarGroup = ToolbarGroup

/**
* @deprecated
*/
export const EditorBar: React.FC<{ className?: string; children?: React.ReactNode }> = ({
className,
children,
Expand All @@ -19,7 +25,7 @@ export const EditorBar: React.FC<{ className?: string; children?: React.ReactNod
return (
<div
className={cn(
'overflow-x-auto self-start sticky top-1 z-10 bg-bg rounded-lg w-full flex items-center [scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
'overflow-x-auto self-start sticky top-1 z-11 bg-bg rounded-lg w-full flex items-center [scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
className
)}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { type Color as ReactAriaColor, parseColor } from 'react-aria-components'
import { SelectionBackgroundIcon } from '@phosphor-icons/react'
import { useCurrentEditor } from '@tiptap/react'

import { BaseIcon } from '../../../primitives'
import { ColorPicker, type ColorPickerProps } from '../../../primitives/color-picker'

/**
* @deprecated
*/
export const EditorColorPicker = ({
onPopupClose,
...props
Expand All @@ -29,6 +31,9 @@ export const EditorColorPicker = ({
)
}

/**
* @deprecated
*/
export const EditorBgColorPicker = () => {
const { editor } = useCurrentEditor()

Expand All @@ -39,12 +44,15 @@ export const EditorBgColorPicker = () => {
onPopupClose={(color) => {
editor.chain().setBackColor(color.toString('hex')).run()
}}
label={<BaseIcon icon={SelectionBackgroundIcon} size="md" weight="duotone" />}
buttonClassName="p-4 border border-border/50 bg-secondary/25"
label={<SelectionBackgroundIcon />}
buttonClassName="p-4 bg-secondary/25"
/>
)
}

/**
* @deprecated
*/
export const EditorTextColorPicker = () => {
const { editor } = useCurrentEditor()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
} from '@phosphor-icons/react'
import { useCurrentEditor } from '@tiptap/react'

import { BaseIcon } from '../../../primitives/base-icon'
import { ToolbarItem } from '../../../primitives/toolbar'

type MarkType = 'bold' | 'italic' | 'underline' | 'strike' | 'bulletList' | 'link'
Expand Down Expand Up @@ -75,14 +74,9 @@ const useMark = (type: MarkType) => {
if (!editor.isActive('link')) {
const { state } = editor
const { from, to } = state.selection
const currentText = state.doc.textBetween(from, to, '')
const url = state.doc.textBetween(from, to, '')

editor.chain().focus().extendMarkRange('link').run()
editor
.chain()
.focus()
.setMark('link', { href: currentText || 'https://' })
.run()
editor.chain().focus().insertContent(`[](${url})`).run()
return
}
editor.chain().focus().unsetMark('link').run()
Expand All @@ -93,6 +87,9 @@ const useMark = (type: MarkType) => {
return options[type]
}

/**
* @deprecated
*/
export const MarkButton = (props: { type: MarkType }) => {
const { editor } = useCurrentEditor()
const markOption = useMark(props.type)
Expand All @@ -104,11 +101,9 @@ export const MarkButton = (props: { type: MarkType }) => {
size="md"
variant="default"
className="duration-150 ease-out transition-all h-[36px]"
data-selected={markOption.isSelected}
onClick={markOption.onClick}
aria-label={markOption.label}
>
<BaseIcon icon={markOption.icon} weight={markOption.isSelected ? 'bold' : 'regular'} />
<markOption.icon weight={markOption.isSelected ? 'bold' : 'regular'} />
</ToolbarItem>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { ArrowClockwiseIcon, ArrowCounterClockwiseIcon } from '@phosphor-icons/r
import { useCurrentEditor } from '@tiptap/react'

import { Button } from '../../../../../../v2'
import { BaseIcon } from '../../../primitives/base-icon'

/**
* @deprecated
*/
export const UndoButton = () => {
const { editor } = useCurrentEditor()

Expand All @@ -18,14 +20,16 @@ export const UndoButton = () => {
onClick={() => {
editor.chain().focus().undo().run()
}}
aria-label="Undo"
disabled={!editor.can().undo()}
>
<BaseIcon icon={ArrowCounterClockwiseIcon} weight="regular" />
<ArrowCounterClockwiseIcon className="size-8" />
</Button>
)
}

/**
* @deprecated
*/
export const RedoButton = () => {
const { editor } = useCurrentEditor()

Expand All @@ -39,10 +43,9 @@ export const RedoButton = () => {
onClick={() => {
editor.chain().focus().redo().run()
}}
aria-label="Redo"
disabled={!editor.can().redo()}
>
<BaseIcon icon={ArrowClockwiseIcon} weight="regular" />
<ArrowClockwiseIcon className="size-8" />
</Button>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import { EditorProvider } from './rich-text-provider'

import { CustomFieldError } from '../../../primitives/custom-field-error'
import { Description } from '../../../primitives/field'

/**
* @deprecated
*/
export interface RichTextEditorProps {
editorProviderProps: EditorProviderProps
value?: string | Content | Content[]
Expand All @@ -21,6 +25,9 @@ export interface RichTextEditorProps {
label?: string
}

/**
* @deprecated
*/
export const RichTextEditor = (props: RichTextEditorProps) => {
const editor = useEditor({
...props.editorProviderProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ interface EditorProviderPropsWithEditor extends EditorProviderProps {
}
}

/**
* @deprecated
*/
export function EditorProvider({
children,
slotAfter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ import {
import type { Editor } from '@tiptap/core'
import { useCurrentEditor } from '@tiptap/react'

import { BaseIcon } from '../../../primitives/base-icon'
import { Select, SelectList, SelectOption, SelectTrigger } from '../../../primitives/select'
import {
Select,
SelectContent,
SelectItem,
SelectItemText,
SelectTrigger,
SelectValue,
} from '../../../../../../v2'

const textStylesList = [
{ icon: TextTIcon, label: 'Normal', value: 'p', type: 'paragraph' },
Expand Down Expand Up @@ -45,6 +51,9 @@ const getSelectedTextStyle = (editor: Editor) => {
return null
}

/**
* @deprecated
*/
export const SelectTextStyle = () => {
const { editor } = useCurrentEditor()
if (!editor) throw new Error('Editor provider is missing')
Expand Down Expand Up @@ -72,22 +81,23 @@ export const SelectTextStyle = () => {

return (
<Select
selectedKey={getSelectedTextStyle(editor)}
className="w-72"
defaultSelectedKey="Normal"
placeholder="Choose style"
aria-label="Select text style"
onSelectionChange={selectChange}
value={getSelectedTextStyle(editor) ?? 'p'}
defaultValue="p"
onValueChange={selectChange}
>
<SelectTrigger className="h-[36px]" />
<SelectList items={textStylesList}>
{(item) => (
<SelectOption key={item.value} id={item.value} textValue={item.value}>
<BaseIcon icon={item.icon} size="sm" weight="regular" />
{item.label}
</SelectOption>
)}
</SelectList>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
{textStylesList.map((textStyle) => (
<SelectItem key={textStyle.value} value={textStyle.value}>
<SelectItemText className="flex items-center gap-2">
<textStyle.icon />
{textStyle.label}
</SelectItemText>
</SelectItem>
))}
</SelectContent>
</Select>
)
}
Loading
Loading