Skip to content
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

Enable Disable Clipboard Matchers on Update #606

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,14 @@ The Mixin has been considered an anti-pattern for a long time now, so we have de

There is no upgrade path. If you have a use case that relied on the Mixin, you're encouraged to open an issue, and we will try to provide you with a new feature to make it possible, or dedicated support to migrate out of it.

### Clipboard Matchers

Clipboards matchers are used for converting values in controlled components, in v2 you are able to disable and enable matchers during an update event in order to be able to have control
over the way values are converted to deltas vs during an update event where the editor contents is being set

This is specially useful if for instance you have content which value can change (eg. a realtime text editor) but you want to only allow plaintext to be pasted via a matcher, you can disable
the plaintext matcher from the clipboard during the update event

### Toolbar component

Quill has long provided built-in support for custom toolbars, which replaced ReactQuill's (quite inflexible) Toolbar component.
Expand Down Expand Up @@ -510,6 +518,53 @@ const {Quill} = ReactQuill;
: If true, a `pre` tag is used for the editor area instead of the default `div` tag. This prevents Quill from
collapsing continuous whitespaces on paste. [Related issue](https://github.com/quilljs/quill/issues/1751).

`disableClipboardMatchersOnUpdate`
: An array of matchers similar in shape to the matchers that exist in your module clipboard config that will be disabled during an update event

<details>
<summary>Example</summary>

~~~tsx
function plainTextOnly(node: Node) {
return new Delta().insert(node.textContent);
}
const PLAINTEXT_ONLY_MATCHERS: ReactQuill.ClipboardMatcher[] = [
[Node.ELEMENT_NODE, plainTextOnly],
];
// prevent modules from updating in each render
const DEFAULT_MODULES = {
clipboard: {
matchVisual: false,
matchers: PLAINTEXT_ONLY_MATCHERS,
}
}

class RealtimeEditor extends React.Component<RealtimeEditorProps> {
constructor(props: RealtimeEditorProps) {
super(props)
}

public render() {
return (
<div>
<ReactQuill
value={this.props.value}
onChange={this.props.onChange}
modules={DEFAULT_MODULES}
disableClipboardMatchersOnUpdate={PLAINTEXT_ONLY_MATCHERS}
theme={'snow'} />
</div>
)
}
}
~~~

</details>

`enableClipboardMatchersOnUpdate`
: An array of matchers, similar to the matchers in the module clipboard, this time they will be added during an update event, and only exist during such event, you might use
this to have matching functionality that you don't want to have on a paste event

### Methods

If you have [a ref](https://facebook.github.io/react/docs/more-about-refs.html) to a ReactQuill node, you will be able to invoke the following methods:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-quill",
"version": "2.0.0-beta.2",
"version": "2.0.0-beta.3",
"description": "The Quill rich-text editor as a React component.",
"author": "zenoamaro <[email protected]>",
"license": "MIT",
Expand Down
40 changes: 39 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Quill, {
namespace ReactQuill {
export type Value = string | DeltaStatic;
export type Range = RangeStatic | null;
export type ClipboardMatcher = [number | string, (node: any, delta: DeltaStatic) => DeltaStatic];

export interface QuillOptions extends QuillOptionsStatic {
tabIndex?: number,
Expand Down Expand Up @@ -66,6 +67,8 @@ namespace ReactQuill {
tabIndex?: number,
theme?: string,
value?: Value,
disableClipboardMatchersOnUpdate?: ClipboardMatcher[],
enableClipboardMatchersOnUpdate?: ClipboardMatcher[],
}

export interface UnprivilegedEditor {
Expand Down Expand Up @@ -382,7 +385,42 @@ class ReactQuill extends React.Component<ReactQuillProps, ReactQuillState> {
this.value = value;
const sel = this.getEditorSelection();
if (typeof value === 'string') {
editor.setContents(editor.clipboard.convert(value));
// if we have matchers to disable during an update event in order to sanitize our data differently than on a paste event
if (this.props.disableClipboardMatchersOnUpdate || this.props.enableClipboardMatchersOnUpdate) {

// first we grab our old clipboard matchers as we want to restore them after the update
// sadly the type of this is a ClipboardStatic type which doesn't expect to be modified
// however it can indded be manually modified, the issue is that Quill lacks removeMatcher functionality
// and as so it has to be performed manually
const oldClipboardMatchers: ReactQuill.ClipboardMatcher[] = (editor.clipboard as any).matchers;
// now we build new clipboard matchers based on these, as we filter we create a new array in place
let newClipboardMatchers: ReactQuill.ClipboardMatcher[] =
(editor.clipboard as any).matchers.filter((matcher: ReactQuill.ClipboardMatcher) => {
// if we have no matchers to disable we return true and it goes in
if (!this.props.disableClipboardMatchersOnUpdate) {
return true;
}
// otherwise we check if it exists within the list
return !this.props.disableClipboardMatchersOnUpdate
.find((disabledMatcher) => disabledMatcher[0] === matcher[0] && disabledMatcher[1] === matcher[1])
});

// now if we have matchers to enable we add them
if (this.props.enableClipboardMatchersOnUpdate) {
newClipboardMatchers = newClipboardMatchers.concat(this.props.enableClipboardMatchersOnUpdate)
}

// now we update the matcher list
(editor.clipboard as any).matchers = newClipboardMatchers;

// perform the set contents action
editor.setContents(editor.clipboard.convert(value));

// and then restore the matchers
(editor.clipboard as any).matchers = oldClipboardMatchers;
} else {
editor.setContents(editor.clipboard.convert(value));
}
} else {
editor.setContents(value);
}
Expand Down