-
Notifications
You must be signed in to change notification settings - Fork 892
set x-slate-fragment in PlateView #4431
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 10 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
0563029
fix
felixfeng33 f6bfc8c
fix
felixfeng33 5c0c75a
Add changeset for PlateStatic copy functionality
claude[bot] 2d14273
fix
felixfeng33 f798237
Update platejs-core-minor.md
zbeyens 33b6e8b
Merge branch 'main' into staitc/copy
zbeyens 699e57f
test: add tests for PlateStatic copy functionality
claude[bot] 159588f
temp
felixfeng33 1cd9fb7
fix
felixfeng33 6c78e99
fix
felixfeng33 13dbdcd
test: add comprehensive tests for PlateStatic copy functionality
claude[bot] c2feeb6
fix review
felixfeng33 5f6b669
fix
felixfeng33 ce2407b
tests
felixfeng33 982176d
docs: update changeset to minor and add changelog entry
claude[bot] a569e96
Merge branch 'main' into staitc/copy
felixfeng33 1863d89
lint
felixfeng33 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
--- | ||
"@platejs/core": patch | ||
--- | ||
|
||
Added copy functionality to **PlateStatic** component. When users copy content from a static Plate editor, the selection is now properly serialized with the `x-slate-fragment` data format, enabling seamless paste operations into other Slate editors while preserving the rich structure of the content. | ||
|
||
- Added `onCopy` handler to PlateStatic that sets clipboard data in three formats: Slate fragment, HTML, and plain text | ||
- Added `setFragmentDataStatic` utility to handle copy operations for static editors | ||
- Added `getSelectedDomBlocks` utility to extract selected DOM elements with Slate metadata | ||
- Added `getPlainText` utility to recursively extract plain text from DOM nodes |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export default function DevLayout(props: { children: React.ReactNode }) { | ||
return ( | ||
<> | ||
<main>{props.children}</main> | ||
</> | ||
); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
'use client'; | ||
|
||
import { useEffect, useState } from 'react'; | ||
|
||
import { usePlateViewEditor } from '@platejs/core/react'; | ||
|
||
import { BaseEditorKit } from '@/registry/components/editor/editor-base-kit'; | ||
import { playgroundValue } from '@/registry/examples/values/playground-value'; | ||
import { EditorView } from '@/registry/ui/editor'; | ||
import { EditorStatic } from '@/registry/ui/editor-static'; | ||
|
||
export default function DevPage() { | ||
const [isClient, setIsClient] = useState(false); | ||
|
||
useEffect(() => { | ||
setIsClient(true); | ||
}, []); | ||
|
||
// const editor = createStaticEditor({ plugins: BaseEditorKit }); | ||
const editor = usePlateViewEditor( | ||
{ | ||
plugins: BaseEditorKit, | ||
value: playgroundValue, | ||
}, | ||
[] | ||
); | ||
|
||
if (!isClient) { | ||
return <main>Loading...</main>; | ||
} | ||
|
||
return ( | ||
<main> | ||
<EditorView editor={editor} /> | ||
|
||
<h1>123</h1> | ||
|
||
<EditorStatic editor={editor} /> | ||
</main> | ||
); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
packages/core/src/lib/static/editor/createStaticEditor.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { type Editor, type Value, createEditor } from '@platejs/slate'; | ||
|
||
import type { AnyPluginConfig } from '../../plugin'; | ||
import type { CorePlugin } from '../../plugins'; | ||
|
||
import { | ||
type CreateSlateEditorOptions, | ||
type WithSlateOptions, | ||
withSlate, | ||
} from '../../editor'; | ||
import { getStaticPlugins } from '../plugins/getStaticPlugins'; | ||
|
||
type CreateStaticEditorOptions< | ||
V extends Value = Value, | ||
P extends AnyPluginConfig = CorePlugin, | ||
> = CreateSlateEditorOptions<V, P> & { | ||
/** Enable copy plugin. */ | ||
copyPlugin?: boolean; | ||
}; | ||
|
||
type WithStaticOptions< | ||
V extends Value = Value, | ||
P extends AnyPluginConfig = CorePlugin, | ||
> = WithSlateOptions<V, P> & { | ||
copyPlugin?: boolean; | ||
}; | ||
|
||
const withStatic = < | ||
V extends Value = Value, | ||
P extends AnyPluginConfig = CorePlugin, | ||
>( | ||
editor: Editor, | ||
options: WithStaticOptions<V, P> = {} | ||
) => { | ||
const { plugins = [], ...rest } = options; | ||
|
||
const staticPlugins = getStaticPlugins({ | ||
copyPlugin: options.copyPlugin, | ||
}) as any; | ||
|
||
options.plugins = [...staticPlugins, ...plugins]; | ||
|
||
return withSlate<V, P>(editor, options); | ||
}; | ||
|
||
export const createStaticEditor = < | ||
V extends Value = Value, | ||
P extends AnyPluginConfig = CorePlugin, | ||
>({ | ||
editor = createEditor(), | ||
...options | ||
}: CreateStaticEditorOptions<V, P> = {}) => { | ||
return withStatic<V, P>(editor, options); | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/** | ||
* @file Automatically generated by barrelsby. | ||
*/ | ||
|
||
export * from './createStaticEditor'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import type { DOMElement, DOMNode, DOMText } from '@platejs/slate'; | ||
|
||
const getDefaultView = (value: any): Window | null => { | ||
return value?.ownerDocument?.defaultView || null; | ||
}; | ||
|
||
/** Check if a DOM node is an element node. */ | ||
|
||
const isDOMElement = (value: any): value is DOMElement => { | ||
return isDOMNode(value) && value.nodeType === 1; | ||
}; | ||
|
||
/** Check if a value is a DOM node. */ | ||
|
||
const isDOMNode = (value: any): value is DOMNode => { | ||
const window = getDefaultView(value); | ||
return !!window && value instanceof window.Node; | ||
}; | ||
|
||
/** Check if a DOM node is an element node. */ | ||
const isDOMText = (value: any): value is DOMText => { | ||
return isDOMNode(value) && value.nodeType === 3; | ||
}; | ||
|
||
export const getPlainText = (domNode: DOMNode) => { | ||
let text = ''; | ||
|
||
if (isDOMText(domNode) && domNode.nodeValue) { | ||
return domNode.nodeValue; | ||
} | ||
|
||
if (isDOMElement(domNode)) { | ||
for (const childNode of Array.from(domNode.childNodes)) { | ||
text += getPlainText(childNode); | ||
} | ||
|
||
const display = getComputedStyle(domNode).getPropertyValue('display'); | ||
|
||
if (display === 'block' || display === 'list' || domNode.tagName === 'BR') { | ||
text += '\n'; | ||
} | ||
} | ||
|
||
return text; | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import type { Descendant } from '@platejs/slate'; | ||
|
||
import type { SlateEditor } from '../..'; | ||
|
||
import { createTSlatePlugin } from '../../plugin'; | ||
import { getPlainText } from '../internal/getPlainText'; | ||
import { getSelectedDomBlocks } from '../utils/getSelectedDomBlocks'; | ||
import { getSelectedDomNode } from '../utils/getSelectedDomNode'; | ||
import { isSelectOutside } from '../utils/isSelectOutside'; | ||
|
||
export const setFragmentDataStatic = ( | ||
editor: SlateEditor, | ||
data: Pick<DataTransfer, 'getData' | 'setData'> | ||
) => { | ||
const domBlocks = getSelectedDomBlocks(); | ||
const html = getSelectedDomNode(); | ||
|
||
if (!html || !domBlocks) return; | ||
|
||
const selectOutside = isSelectOutside(html); | ||
|
||
if (selectOutside) return; | ||
|
||
// only crossing multiple blocks | ||
if (domBlocks.length > 0) { | ||
const fragment: Descendant[] = []; | ||
|
||
Array.from(domBlocks).forEach((node: any) => { | ||
const blockId = node.dataset.slateId; | ||
const block = editor.api.node({ id: blockId, at: [] }); | ||
|
||
// prevent inline elements like link and table cells. | ||
if (block && block[1].length === 1) { | ||
fragment.push(block[0]); | ||
} | ||
}); | ||
|
||
const string = JSON.stringify(fragment); | ||
const encoded = window.btoa(encodeURIComponent(string)); | ||
|
||
data.setData('application/x-slate-fragment', encoded); | ||
data.setData('text/html', html.innerHTML); | ||
data.setData('text/plain', getPlainText(html)); | ||
} | ||
}; | ||
|
||
export const CopyPlugin = createTSlatePlugin({ | ||
felixfeng33 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
key: 'copy', | ||
options: {}, | ||
}).overrideEditor(({ editor }) => ({ | ||
transforms: { | ||
setFragmentData(fragment) { | ||
return setFragmentDataStatic(editor, fragment); | ||
felixfeng33 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
}, | ||
})); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { CopyPlugin } from './CopyPlugin'; | ||
|
||
export type GetStaticPluginsOptions = { | ||
/** Enable copy plugin. */ | ||
copyPlugin?: boolean; | ||
felixfeng33 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
|
||
export const getStaticPlugins = ({ | ||
copyPlugin = true, | ||
}: GetStaticPluginsOptions) => { | ||
const staticPlugins = [CopyPlugin.configure({ enabled: copyPlugin })]; | ||
|
||
return [...staticPlugins]; | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/** | ||
* @file Automatically generated by barrelsby. | ||
*/ | ||
|
||
export * from './CopyPlugin'; | ||
export * from './getStaticPlugins'; |
15 changes: 15 additions & 0 deletions
15
packages/core/src/lib/static/utils/getSelectedDomBlocks.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/** Get the slate nodes from the DOM selection */ | ||
export const getSelectedDomBlocks = () => { | ||
const selection = window.getSelection(); | ||
|
||
if (!selection || selection.rangeCount === 0) return; | ||
|
||
const range = selection.getRangeAt(0); | ||
const fragment = range.cloneContents(); | ||
|
||
const domBlocks = fragment.querySelectorAll( | ||
'[data-slate-node="element"][data-slate-id]' | ||
); | ||
|
||
return Array.from(domBlocks); | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/** Get the DOM node from the DOM selection */ | ||
export const getSelectedDomNode = () => { | ||
const selection = window.getSelection(); | ||
|
||
if (!selection || selection.rangeCount === 0) return; | ||
const range = selection.getRangeAt(0); | ||
|
||
const htmlFragment = range.cloneContents(); | ||
const div = document.createElement('div'); | ||
div.append(htmlFragment); | ||
|
||
return div; | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { getSelectedDomNode } from './getSelectedDomNode'; | ||
|
||
/** Check if the DOM selection is outside the editor */ | ||
export const isSelectOutside = (html?: HTMLElement): boolean => { | ||
const domNodes = html ?? getSelectedDomNode(); | ||
|
||
if (!domNodes) return false; | ||
|
||
const selectOutside = !!domNodes?.querySelector('[data-slate-editor="true"'); | ||
|
||
return selectOutside; | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import React, { useCallback } from 'react'; | ||
|
||
import { type PlateStaticProps, PlateStatic } from '../../lib'; | ||
|
||
export type PlateViewProps = PlateStaticProps & {}; | ||
|
||
export const PlateView = (props: PlateViewProps) => { | ||
return ( | ||
<PlateStatic | ||
onCopy={useCallback( | ||
(e: React.ClipboardEvent<HTMLDivElement>) => { | ||
props.editor.tf.setFragmentData(e.clipboardData); | ||
|
||
if (e.clipboardData.getData('application/x-slate-fragment')) { | ||
e.preventDefault(); | ||
} | ||
}, | ||
[props.editor.tf] | ||
)} | ||
{...props} | ||
/> | ||
); | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.