mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-02 16:55:02 +00:00
Compare commits
1 Commits
doc/add-gr
...
revert-894
Author | SHA1 | Date | |
---|---|---|---|
d123594131 |
@ -1,35 +1,36 @@
|
|||||||
/* eslint-disable react/no-danger */
|
/* eslint-disable react/no-danger */
|
||||||
import { forwardRef, HTMLAttributes } from "react";
|
import { HTMLAttributes } from "react";
|
||||||
import ContentEditable from "react-contenteditable";
|
import ContentEditable from "react-contenteditable";
|
||||||
import sanitizeHtml, { DisallowedTagsModes } from "sanitize-html";
|
import sanitizeHtml from "sanitize-html";
|
||||||
|
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
|
|
||||||
const REGEX = /\${([^}]+)}/g;
|
const REGEX = /\${([^}]+)}/g;
|
||||||
|
const stripSpanTags = (str: string) => str.replace(/<\/?span[^>]*>/g, "");
|
||||||
const replaceContentWithDot = (str: string) => {
|
const replaceContentWithDot = (str: string) => {
|
||||||
let finalStr = "";
|
let finalStr = "";
|
||||||
|
let isHtml = false;
|
||||||
for (let i = 0; i < str.length; i += 1) {
|
for (let i = 0; i < str.length; i += 1) {
|
||||||
const char = str.at(i);
|
const char = str.at(i);
|
||||||
finalStr += char === "\n" ? "\n" : "•";
|
|
||||||
|
if (char === "<" || char === ">") {
|
||||||
|
isHtml = char === "<";
|
||||||
|
finalStr += char;
|
||||||
|
} else if (!isHtml && char !== "\n") {
|
||||||
|
finalStr += "•";
|
||||||
|
} else {
|
||||||
|
finalStr += char;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return finalStr;
|
return finalStr;
|
||||||
};
|
};
|
||||||
|
|
||||||
const sanitizeConf = {
|
const syntaxHighlight = (orgContent?: string | null, isVisible?: boolean) => {
|
||||||
allowedTags: ["span"],
|
if (orgContent === "") return "EMPTY";
|
||||||
disallowedTagsMode: "escape" as DisallowedTagsModes
|
if (!orgContent) return "missing";
|
||||||
};
|
if (!isVisible) return replaceContentWithDot(orgContent);
|
||||||
|
const content = stripSpanTags(orgContent);
|
||||||
const syntaxHighlight = (content?: string | null, isVisible?: boolean) => {
|
const newContent = content.replace(
|
||||||
if (content === "") return "EMPTY";
|
|
||||||
if (!content) return "missing";
|
|
||||||
if (!isVisible) return replaceContentWithDot(content);
|
|
||||||
|
|
||||||
const sanitizedContent = sanitizeHtml(
|
|
||||||
content.replaceAll("<", "<").replaceAll(">", ">"),
|
|
||||||
sanitizeConf
|
|
||||||
);
|
|
||||||
const newContent = sanitizedContent.replace(
|
|
||||||
REGEX,
|
REGEX,
|
||||||
(_a, b) =>
|
(_a, b) =>
|
||||||
`<span class="ph-no-capture text-yellow">${<span class="ph-no-capture text-yello-200/80">${b}</span>}</span>`
|
`<span class="ph-no-capture text-yellow">${<span class="ph-no-capture text-yello-200/80">${b}</span>}</span>`
|
||||||
@ -38,58 +39,57 @@ const syntaxHighlight = (content?: string | null, isVisible?: boolean) => {
|
|||||||
return newContent;
|
return newContent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sanitizeConf = {
|
||||||
|
allowedTags: ["div", "span", "br", "p"]
|
||||||
|
};
|
||||||
|
|
||||||
type Props = Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "onBlur"> & {
|
type Props = Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "onBlur"> & {
|
||||||
value?: string | null;
|
value?: string | null;
|
||||||
isVisible?: boolean;
|
isVisible?: boolean;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
onChange?: (val: string) => void;
|
onChange?: (val: string, html: string) => void;
|
||||||
onBlur?: () => void;
|
onBlur?: (sanitizedHtml: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretInput = forwardRef<HTMLDivElement, Props>(
|
export const SecretInput = ({
|
||||||
({ value, isVisible, onChange, onBlur, isDisabled, ...props }, ref) => {
|
value,
|
||||||
const [isSecretFocused, setIsSecretFocused] = useToggle();
|
isVisible,
|
||||||
|
onChange,
|
||||||
|
onBlur,
|
||||||
|
isDisabled,
|
||||||
|
...props
|
||||||
|
}: Props) => {
|
||||||
|
const [isSecretFocused, setIsSecretFocused] = useToggle();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div
|
||||||
|
className="thin-scrollbar relative overflow-y-auto overflow-x-hidden"
|
||||||
|
style={{ maxHeight: `${21 * 7}px` }}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="thin-scrollbar relative overflow-y-auto overflow-x-hidden"
|
dangerouslySetInnerHTML={{
|
||||||
style={{ maxHeight: `${21 * 7}px` }}
|
__html: syntaxHighlight(value, isVisible || isSecretFocused)
|
||||||
>
|
}}
|
||||||
<div
|
className={`absolute top-0 left-0 z-0 h-full w-full text-ellipsis whitespace-pre-line break-all ${
|
||||||
dangerouslySetInnerHTML={{
|
!value && value !== "" && "italic text-red-600/70"
|
||||||
__html: syntaxHighlight(value, isVisible || isSecretFocused)
|
}`}
|
||||||
}}
|
/>
|
||||||
className={`absolute top-0 left-0 z-0 h-full w-full inline-block text-ellipsis whitespace-pre-wrap break-all ${
|
<ContentEditable
|
||||||
!value && value !== "" && "italic text-red-600/70"
|
className="relative z-10 h-full w-full text-ellipsis whitespace-pre-line break-all text-transparent caret-white outline-none"
|
||||||
}`}
|
role="textbox"
|
||||||
ref={ref}
|
onChange={(evt) => {
|
||||||
/>
|
if (onChange) onChange(evt.currentTarget.innerText.trim(), evt.currentTarget.innerHTML);
|
||||||
<ContentEditable
|
}}
|
||||||
className="relative z-10 h-full w-full text-ellipsis inline-block whitespace-pre-wrap break-all text-transparent caret-white outline-none"
|
onFocus={() => setIsSecretFocused.on()}
|
||||||
role="textbox"
|
disabled={isDisabled}
|
||||||
onChange={(evt) => {
|
spellCheck={false}
|
||||||
if (onChange) onChange(evt.currentTarget.innerText.trim());
|
onBlur={(evt) => {
|
||||||
}}
|
if (onBlur) onBlur(sanitizeHtml(evt.currentTarget.innerHTML || "", sanitizeConf));
|
||||||
onFocus={() => setIsSecretFocused.on()}
|
setIsSecretFocused.off();
|
||||||
disabled={isDisabled}
|
}}
|
||||||
spellCheck={false}
|
html={isVisible || isSecretFocused ? value || "" : syntaxHighlight(value, false)}
|
||||||
onBlur={() => {
|
{...props}
|
||||||
if (onBlur) onBlur();
|
/>
|
||||||
setIsSecretFocused.off();
|
</div>
|
||||||
}}
|
);
|
||||||
html={
|
};
|
||||||
isVisible || isSecretFocused
|
|
||||||
? sanitizeHtml(
|
|
||||||
value?.replaceAll("<", "<").replaceAll(">", ">") || "",
|
|
||||||
sanitizeConf
|
|
||||||
)
|
|
||||||
: syntaxHighlight(value, false)
|
|
||||||
}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
SecretInput.displayName = "SecretInput";
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable react/jsx-no-useless-fragment */
|
/* eslint-disable react/jsx-no-useless-fragment */
|
||||||
import { memo, useEffect, useRef, useState } from "react";
|
import { memo, useEffect,useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Control,
|
Control,
|
||||||
Controller,
|
Controller,
|
||||||
@ -32,8 +32,7 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
SecretInput,
|
SecretInput,
|
||||||
Tag,
|
Tag,
|
||||||
Tooltip
|
Tooltip} from "@app/components/v2";
|
||||||
} from "@app/components/v2";
|
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
import { WsTag } from "@app/hooks/api/types";
|
import { WsTag } from "@app/hooks/api/types";
|
||||||
|
|
||||||
@ -84,7 +83,7 @@ export const SecretInputRow = memo(
|
|||||||
isKeyError,
|
isKeyError,
|
||||||
keyError,
|
keyError,
|
||||||
secUniqId,
|
secUniqId,
|
||||||
autoCapitalization
|
autoCapitalization,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const isKeySubDisabled = useRef<boolean>(false);
|
const isKeySubDisabled = useRef<boolean>(false);
|
||||||
// comment management in a row
|
// comment management in a row
|
||||||
@ -95,7 +94,7 @@ export const SecretInputRow = memo(
|
|||||||
} = useFieldArray({ control, name: `secrets.${index}.tags` });
|
} = useFieldArray({ control, name: `secrets.${index}.tags` });
|
||||||
|
|
||||||
// display the tags in alphabetical order
|
// display the tags in alphabetical order
|
||||||
secretTags.sort((a, b) => a?.name?.localeCompare(b?.name));
|
secretTags.sort((a, b) => a?.name?.localeCompare(b?.name))
|
||||||
|
|
||||||
// to get details on a secret
|
// to get details on a secret
|
||||||
const overrideAction = useWatch({
|
const overrideAction = useWatch({
|
||||||
@ -128,40 +127,47 @@ export const SecretInputRow = memo(
|
|||||||
const isOverridden =
|
const isOverridden =
|
||||||
overrideAction === SecretActionType.Created || overrideAction === SecretActionType.Modified;
|
overrideAction === SecretActionType.Created || overrideAction === SecretActionType.Modified;
|
||||||
|
|
||||||
|
|
||||||
|
const [editorRef, setEditorRef] = useState(isOverridden ? secValueOverride : secValue);
|
||||||
const [hoveredTag, setHoveredTag] = useState<WsTag | null>(null);
|
const [hoveredTag, setHoveredTag] = useState<WsTag | null>(null);
|
||||||
|
|
||||||
const handleTagOnMouseEnter = (wsTag: WsTag) => {
|
const handleTagOnMouseEnter = (wsTag: WsTag) => {
|
||||||
setHoveredTag(wsTag);
|
setHoveredTag(wsTag);
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleTagOnMouseLeave = () => {
|
const handleTagOnMouseLeave = () => {
|
||||||
setHoveredTag(null);
|
setHoveredTag(null);
|
||||||
};
|
}
|
||||||
|
|
||||||
const checkIfTagIsVisible = (wsTag: WsTag) => wsTag._id === hoveredTag?._id;
|
const checkIfTagIsVisible = (wsTag: WsTag) => wsTag._id === hoveredTag?._id;
|
||||||
|
|
||||||
const secId = useWatch({ control, name: `secrets.${index}._id`, exact: true });
|
const secId = useWatch({ control, name: `secrets.${index}._id`, exact: true });
|
||||||
const tags =
|
const tags = useWatch({ control, name: `secrets.${index}.tags`, exact: true, defaultValue: [] }) || [];
|
||||||
useWatch({ control, name: `secrets.${index}.tags`, exact: true, defaultValue: [] }) || [];
|
|
||||||
|
|
||||||
const selectedTagIds = tags.reduce<Record<string, boolean>>(
|
const selectedTagIds = tags.reduce<Record<string, boolean>>(
|
||||||
(prev, curr) => ({ ...prev, [curr.slug]: true }),
|
(prev, curr) => ({ ...prev, [curr.slug]: true }),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
const [isSecValueCopied, setIsSecValueCopied] = useToggle(false);
|
const [isInviteLinkCopied, setInviteLinkCopied] = useToggle(false);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let timer: NodeJS.Timeout;
|
let timer: NodeJS.Timeout;
|
||||||
if (isSecValueCopied) {
|
if (isInviteLinkCopied) {
|
||||||
timer = setTimeout(() => setIsSecValueCopied.off(), 2000);
|
timer = setTimeout(() => setInviteLinkCopied.off(), 2000);
|
||||||
}
|
}
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [isSecValueCopied]);
|
}, [isInviteLinkCopied]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setEditorRef(isOverridden ? secValueOverride : secValue);
|
||||||
|
}, [isOverridden]);
|
||||||
|
|
||||||
const copyTokenToClipboard = () => {
|
const copyTokenToClipboard = () => {
|
||||||
navigator.clipboard.writeText((secValueOverride || secValue) as string);
|
navigator.clipboard.writeText((secValueOverride || secValue) as string);
|
||||||
setIsSecValueCopied.on();
|
setInviteLinkCopied.on();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSecretOverride = () => {
|
const onSecretOverride = () => {
|
||||||
@ -185,8 +191,8 @@ export const SecretInputRow = memo(
|
|||||||
const onSelectTag = (selectedTag: WsTag) => {
|
const onSelectTag = (selectedTag: WsTag) => {
|
||||||
const shouldAppend = !selectedTagIds[selectedTag.slug];
|
const shouldAppend = !selectedTagIds[selectedTag.slug];
|
||||||
if (shouldAppend) {
|
if (shouldAppend) {
|
||||||
const { _id: id, name, slug, tagColor } = selectedTag;
|
const {_id: id, name, slug, tagColor} = selectedTag
|
||||||
append({ _id: id, name, slug, tagColor });
|
append({_id: id, name, slug, tagColor});
|
||||||
} else {
|
} else {
|
||||||
const pos = tags.findIndex(({ slug }: { slug: string }) => selectedTag.slug === slug);
|
const pos = tags.findIndex(({ slug }: { slug: string }) => selectedTag.slug === slug);
|
||||||
remove(pos);
|
remove(pos);
|
||||||
@ -266,7 +272,7 @@ export const SecretInputRow = memo(
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name={`secrets.${index}.valueOverride`}
|
name={`secrets.${index}.valueOverride`}
|
||||||
render={({ field }) => (
|
render={({ field: { onChange, onBlur } }) => (
|
||||||
<SecretInput
|
<SecretInput
|
||||||
key={`secrets.${index}.valueOverride`}
|
key={`secrets.${index}.valueOverride`}
|
||||||
isDisabled={
|
isDisabled={
|
||||||
@ -274,8 +280,16 @@ export const SecretInputRow = memo(
|
|||||||
isRollbackMode ||
|
isRollbackMode ||
|
||||||
(isOverridden ? isAddOnly : shouldBeBlockedInAddOnly)
|
(isOverridden ? isAddOnly : shouldBeBlockedInAddOnly)
|
||||||
}
|
}
|
||||||
|
value={editorRef}
|
||||||
isVisible={!isSecretValueHidden}
|
isVisible={!isSecretValueHidden}
|
||||||
{...field}
|
onChange={(val, html) => {
|
||||||
|
onChange(val);
|
||||||
|
setEditorRef(html);
|
||||||
|
}}
|
||||||
|
onBlur={(html) => {
|
||||||
|
setEditorRef(html);
|
||||||
|
onBlur();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -283,7 +297,7 @@ export const SecretInputRow = memo(
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name={`secrets.${index}.value`}
|
name={`secrets.${index}.value`}
|
||||||
render={({ field }) => (
|
render={({ field: { onBlur, onChange } }) => (
|
||||||
<SecretInput
|
<SecretInput
|
||||||
key={`secrets.${index}.value`}
|
key={`secrets.${index}.value`}
|
||||||
isVisible={!isSecretValueHidden}
|
isVisible={!isSecretValueHidden}
|
||||||
@ -292,7 +306,15 @@ export const SecretInputRow = memo(
|
|||||||
isRollbackMode ||
|
isRollbackMode ||
|
||||||
(isOverridden ? isAddOnly : shouldBeBlockedInAddOnly)
|
(isOverridden ? isAddOnly : shouldBeBlockedInAddOnly)
|
||||||
}
|
}
|
||||||
{...field}
|
onChange={(val, html) => {
|
||||||
|
onChange(val);
|
||||||
|
setEditorRef(html);
|
||||||
|
}}
|
||||||
|
value={editorRef}
|
||||||
|
onBlur={(html) => {
|
||||||
|
setEditorRef(html);
|
||||||
|
onBlur();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -301,41 +323,38 @@ export const SecretInputRow = memo(
|
|||||||
</td>
|
</td>
|
||||||
<td className="min-w-sm flex">
|
<td className="min-w-sm flex">
|
||||||
<div className="flex h-8 items-center pl-2">
|
<div className="flex h-8 items-center pl-2">
|
||||||
{secretTags.map(({ id, slug, tagColor }) => {
|
{secretTags.map(({ id, slug, tagColor}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<div>
|
<div>
|
||||||
<Tag
|
<Tag
|
||||||
// isDisabled={isReadOnly || isAddOnly || isRollbackMode}
|
// isDisabled={isReadOnly || isAddOnly || isRollbackMode}
|
||||||
// onClose={() => remove(i)}
|
// onClose={() => remove(i)}
|
||||||
key={id}
|
key={id}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
>
|
>
|
||||||
<div className="rounded-full border-mineshaft-500 bg-transparent flex items-center gap-1.5 justify-around">
|
<div className="rounded-full border-mineshaft-500 bg-transparent flex items-center gap-1.5 justify-around">
|
||||||
<div
|
<div className="w-[10px] h-[10px] rounded-full" style={{ background: tagColor || "#bec2c8" }} />
|
||||||
className="w-[10px] h-[10px] rounded-full"
|
{slug}
|
||||||
style={{ background: tagColor || "#bec2c8" }}
|
</div>
|
||||||
/>
|
</Tag>
|
||||||
{slug}
|
</div>
|
||||||
</div>
|
</PopoverTrigger>
|
||||||
</Tag>
|
<AddTagPopoverContent
|
||||||
</div>
|
wsTags={wsTags}
|
||||||
</PopoverTrigger>
|
secKey={secKey || "this secret"}
|
||||||
<AddTagPopoverContent
|
selectedTagIds={selectedTagIds}
|
||||||
wsTags={wsTags}
|
handleSelectTag={(wsTag: WsTag) => onSelectTag(wsTag)}
|
||||||
secKey={secKey || "this secret"}
|
handleTagOnMouseEnter={(wsTag: WsTag) => handleTagOnMouseEnter(wsTag)}
|
||||||
selectedTagIds={selectedTagIds}
|
handleTagOnMouseLeave={() => handleTagOnMouseLeave()}
|
||||||
handleSelectTag={(wsTag: WsTag) => onSelectTag(wsTag)}
|
checkIfTagIsVisible={(wsTag: WsTag) => checkIfTagIsVisible(wsTag)}
|
||||||
handleTagOnMouseEnter={(wsTag: WsTag) => handleTagOnMouseEnter(wsTag)}
|
handleOnCreateTagOpen={() => onCreateTagOpen()}
|
||||||
handleTagOnMouseLeave={() => handleTagOnMouseLeave()}
|
/>
|
||||||
checkIfTagIsVisible={(wsTag: WsTag) => checkIfTagIsVisible(wsTag)}
|
</Popover>
|
||||||
handleOnCreateTagOpen={() => onCreateTagOpen()}
|
</>
|
||||||
/>
|
)
|
||||||
</Popover>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
<div className="w-0 overflow-hidden group-hover:w-6">
|
<div className="w-0 overflow-hidden group-hover:w-6">
|
||||||
<Tooltip content="Copy value">
|
<Tooltip content="Copy value">
|
||||||
@ -346,7 +365,7 @@ export const SecretInputRow = memo(
|
|||||||
className="py-[0.42rem]"
|
className="py-[0.42rem]"
|
||||||
onClick={copyTokenToClipboard}
|
onClick={copyTokenToClipboard}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={isSecValueCopied ? faCheck : faCopy} />
|
<FontAwesomeIcon icon={isInviteLinkCopied ? faCheck : faCopy} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { useRef } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { faCheck, faCopy, faTrash, faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faCheck, faCopy, faTrash, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
@ -38,11 +39,14 @@ export const SecretEditRow = ({
|
|||||||
value: defaultValue
|
value: defaultValue
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const editorRef = useRef(defaultValue);
|
||||||
const [isDeleting, setIsDeleting] = useToggle();
|
const [isDeleting, setIsDeleting] = useToggle();
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
|
|
||||||
const handleFormReset = () => {
|
const handleFormReset = () => {
|
||||||
reset();
|
reset();
|
||||||
|
const val = getValues();
|
||||||
|
editorRef.current = val.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopySecretToClipboard = async () => {
|
const handleCopySecretToClipboard = async () => {
|
||||||
@ -74,6 +78,7 @@ export const SecretEditRow = ({
|
|||||||
try {
|
try {
|
||||||
await onSecretDelete(environment, secretName);
|
await onSecretDelete(environment, secretName);
|
||||||
reset({ value: undefined });
|
reset({ value: undefined });
|
||||||
|
editorRef.current = undefined;
|
||||||
} finally {
|
} finally {
|
||||||
setIsDeleting.off();
|
setIsDeleting.off();
|
||||||
}
|
}
|
||||||
@ -85,7 +90,20 @@ export const SecretEditRow = ({
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="value"
|
name="value"
|
||||||
render={({ field }) => <SecretInput {...field} isVisible={isVisible} />}
|
render={({ field: { onChange, onBlur } }) => (
|
||||||
|
<SecretInput
|
||||||
|
value={editorRef.current}
|
||||||
|
onChange={(val, html) => {
|
||||||
|
onChange(val);
|
||||||
|
editorRef.current = html;
|
||||||
|
}}
|
||||||
|
onBlur={(html) => {
|
||||||
|
editorRef.current = html;
|
||||||
|
onBlur();
|
||||||
|
}}
|
||||||
|
isVisible={isVisible}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-16 justify-center space-x-3 pl-2 transition-all">
|
<div className="flex w-16 justify-center space-x-3 pl-2 transition-all">
|
||||||
|
Reference in New Issue
Block a user