Skip to content

Commit c7456f5

Browse files
authored
Merge branch 'next' into feat-provider-overrides
2 parents 2cccec9 + 0f321b5 commit c7456f5

File tree

5 files changed

+93
-3
lines changed

5 files changed

+93
-3
lines changed

.source

apps/dashboard/src/components/workflow-editor/steps/email/maily.tsx

+12-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Editor } from '@maily-to/core';
33
import { Editor as EditorDigest } from '@maily-to/core-digest';
44
import type { Editor as TiptapEditor } from '@tiptap/core';
55
import { Editor as TiptapEditorReact } from '@tiptap/react';
6+
import { FeatureFlagsKeysEnum } from '@novu/shared';
67

78
import { useWorkflow } from '@/components/workflow-editor/workflow-provider';
89
import { useParseVariables } from '@/hooks/use-parse-variables';
@@ -11,8 +12,8 @@ import { cn } from '@/utils/ui';
1112
import { createEditorBlocks, createExtensions, DEFAULT_EDITOR_CONFIG, MAILY_EMAIL_WIDTH } from './maily-config';
1213
import { calculateVariables, VariableFrom } from './variables/variables';
1314
import { RepeatMenuDescription } from './views/repeat-menu-description';
14-
import { FeatureFlagsKeysEnum } from '@novu/shared';
1515
import { useFeatureFlag } from '@/hooks/use-feature-flag';
16+
import { useRemoveGrammarly } from '@/hooks/use-remove-grammarly';
1617

1718
type MailyProps = HTMLAttributes<HTMLDivElement> & {
1819
value: string;
@@ -42,6 +43,7 @@ export const Maily = ({ value, onChange, className, ...rest }: MailyProps) => {
4243
const blocks = useMemo(() => {
4344
return createEditorBlocks({ track, digestStepBeforeCurrent, isEnhancedDigestEnabled });
4445
}, [digestStepBeforeCurrent, isEnhancedDigestEnabled, track]);
46+
const editorParentRef = useRemoveGrammarly<HTMLDivElement>();
4547

4648
const handleCalculateVariables = useCallback(
4749
({ query, editor, from }: { query: string; editor: TiptapEditor; from: VariableFrom }) => {
@@ -125,10 +127,19 @@ export const Maily = ({ value, onChange, className, ...rest }: MailyProps) => {
125127
<>
126128
{overrideTippyBoxStyles()}
127129
<div
130+
ref={editorParentRef}
128131
className={cn(
129132
`shadow-xs mx-auto flex min-h-full max-w-[${MAILY_EMAIL_WIDTH}px] flex-col items-start rounded-lg bg-white [&_a]:pointer-events-none`,
130133
className
131134
)}
135+
data-gramm={false}
136+
data-gramm_editor={false}
137+
data-enable-grammarly="false"
138+
aria-autocomplete="none"
139+
aria-multiline={false}
140+
autoCapitalize="off"
141+
autoCorrect="off"
142+
spellCheck={false}
132143
{...rest}
133144
>
134145
<_Editor

apps/dashboard/src/components/workflow-editor/steps/email/views/for-view.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export function ForView(props: NodeViewProps) {
1212
draggable="true"
1313
data-drag-handle=""
1414
data-type="repeat"
15-
className="mly-relative border-soft-100 mx-[-0.5rem] rounded-md border px-2 py-2"
15+
className="mly-relative border-soft-100 mx-[-0.5rem] rounded-md border px-3 py-3"
1616
>
1717
<NodeViewContent className="is-editable" />
1818
<div
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { useLayoutEffect, useRef } from 'react';
2+
import { useDataRef } from './use-data-ref';
3+
4+
type MutationObserverOptions = {
5+
childList?: boolean;
6+
attributes?: boolean;
7+
characterData?: boolean;
8+
subtree?: boolean;
9+
attributeOldValue?: boolean;
10+
characterDataOldValue?: boolean;
11+
attributeFilter?: string[];
12+
};
13+
14+
type UseMutationObserverProps = {
15+
target: React.RefObject<Node> | Node | null;
16+
callback: MutationCallback;
17+
options?: MutationObserverOptions;
18+
};
19+
20+
export function useMutationObserver({
21+
target,
22+
callback,
23+
options = { childList: true, subtree: true },
24+
}: UseMutationObserverProps) {
25+
const observerRef = useRef<MutationObserver | null>(null);
26+
const callbackRef = useDataRef<MutationCallback>(callback);
27+
28+
useLayoutEffect(() => {
29+
const targetNode = target && 'current' in target ? target.current : target;
30+
if (!targetNode) return;
31+
32+
// Create MutationObserver with reference to the latest callback
33+
const observer = new MutationObserver((mutations, observer) => {
34+
callbackRef.current(mutations, observer);
35+
});
36+
37+
// Store observer in ref
38+
observerRef.current = observer;
39+
40+
// Start observing
41+
observer.observe(targetNode, options);
42+
43+
return () => {
44+
observer.disconnect();
45+
observerRef.current = null;
46+
};
47+
}, [callbackRef, target, options]);
48+
49+
return observerRef;
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { useRef, useCallback } from 'react';
2+
3+
import { useMutationObserver } from './use-mutation-observer';
4+
5+
export function useRemoveGrammarly<T extends HTMLElement>() {
6+
const target = useRef<T>(null);
7+
const handleGrammarlyRemoval = useCallback((mutations: MutationRecord[]) => {
8+
for (const mutation of mutations) {
9+
if (mutation.type === 'childList') {
10+
for (const node of mutation.addedNodes) {
11+
const isGrammarlyElement =
12+
node instanceof HTMLElement && node.shadowRoot && node.tagName === 'GRAMMARLY-EXTENSION';
13+
14+
if (isGrammarlyElement) {
15+
node.remove();
16+
}
17+
}
18+
}
19+
}
20+
}, []);
21+
22+
useMutationObserver({
23+
target: target,
24+
callback: handleGrammarlyRemoval,
25+
options: { childList: true, subtree: true },
26+
});
27+
28+
return target;
29+
}

0 commit comments

Comments
 (0)