diff --git a/frontend/src/components/v2/SecretInput/SecretInput.tsx b/frontend/src/components/v2/SecretInput/SecretInput.tsx index 5e517c08f..666c41d69 100644 --- a/frontend/src/components/v2/SecretInput/SecretInput.tsx +++ b/frontend/src/components/v2/SecretInput/SecretInput.tsx @@ -1,36 +1,35 @@ /* eslint-disable react/no-danger */ -import { HTMLAttributes } from "react"; +import { forwardRef, HTMLAttributes } from "react"; import ContentEditable from "react-contenteditable"; -import sanitizeHtml from "sanitize-html"; +import sanitizeHtml, { DisallowedTagsModes } from "sanitize-html"; import { useToggle } from "@app/hooks"; const REGEX = /\${([^}]+)}/g; -const stripSpanTags = (str: string) => str.replace(/<\/?span[^>]*>/g, ""); const replaceContentWithDot = (str: string) => { let finalStr = ""; - let isHtml = false; for (let i = 0; i < str.length; i += 1) { const char = str.at(i); - - if (char === "<" || char === ">") { - isHtml = char === "<"; - finalStr += char; - } else if (!isHtml && char !== "\n") { - finalStr += "•"; - } else { - finalStr += char; - } + finalStr += char === "\n" ? "\n" : "•"; } return finalStr; }; -const syntaxHighlight = (orgContent?: string | null, isVisible?: boolean) => { - if (orgContent === "") return "EMPTY"; - if (!orgContent) return "missing"; - if (!isVisible) return replaceContentWithDot(orgContent); - const content = stripSpanTags(orgContent); - const newContent = content.replace( +const sanitizeConf = { + allowedTags: ["span"], + disallowedTagsMode: "escape" as DisallowedTagsModes +}; + +const syntaxHighlight = (content?: string | null, isVisible?: boolean) => { + 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, (_a, b) => `${${b}}` @@ -39,57 +38,58 @@ const syntaxHighlight = (orgContent?: string | null, isVisible?: boolean) => { return newContent; }; -const sanitizeConf = { - allowedTags: ["div", "span", "br", "p"] -}; - type Props = Omit, "onChange" | "onBlur"> & { value?: string | null; isVisible?: boolean; isDisabled?: boolean; - onChange?: (val: string, html: string) => void; - onBlur?: (sanitizedHtml: string) => void; + onChange?: (val: string) => void; + onBlur?: () => void; }; -export const SecretInput = ({ - value, - isVisible, - onChange, - onBlur, - isDisabled, - ...props -}: Props) => { - const [isSecretFocused, setIsSecretFocused] = useToggle(); +export const SecretInput = forwardRef( + ({ value, isVisible, onChange, onBlur, isDisabled, ...props }, ref) => { + const [isSecretFocused, setIsSecretFocused] = useToggle(); - return ( -
+ return (
- { - if (onChange) onChange(evt.currentTarget.innerText.trim(), evt.currentTarget.innerHTML); - }} - onFocus={() => setIsSecretFocused.on()} - disabled={isDisabled} - spellCheck={false} - onBlur={(evt) => { - if (onBlur) onBlur(sanitizeHtml(evt.currentTarget.innerHTML || "", sanitizeConf)); - setIsSecretFocused.off(); - }} - html={isVisible || isSecretFocused ? value || "" : syntaxHighlight(value, false)} - {...props} - /> -
- ); -}; + className="thin-scrollbar relative overflow-y-auto overflow-x-hidden" + style={{ maxHeight: `${21 * 7}px` }} + > +
+ { + if (onChange) onChange(evt.currentTarget.innerText.trim()); + }} + onFocus={() => setIsSecretFocused.on()} + disabled={isDisabled} + spellCheck={false} + onBlur={() => { + if (onBlur) onBlur(); + setIsSecretFocused.off(); + }} + html={ + isVisible || isSecretFocused + ? sanitizeHtml( + value?.replaceAll("<", "<").replaceAll(">", ">") || "", + sanitizeConf + ) + : syntaxHighlight(value, false) + } + {...props} + /> +
+ ); + } +); + +SecretInput.displayName = "SecretInput"; diff --git a/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx b/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx index cdb95864a..a0ecd8628 100644 --- a/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx +++ b/frontend/src/views/DashboardPage/components/SecretInputRow/SecretInputRow.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-no-useless-fragment */ -import { memo, useEffect,useRef, useState } from "react"; +import { memo, useEffect, useRef, useState } from "react"; import { Control, Controller, @@ -32,7 +32,8 @@ import { PopoverTrigger, SecretInput, Tag, - Tooltip} from "@app/components/v2"; + Tooltip +} from "@app/components/v2"; import { useToggle } from "@app/hooks"; import { WsTag } from "@app/hooks/api/types"; @@ -83,7 +84,7 @@ export const SecretInputRow = memo( isKeyError, keyError, secUniqId, - autoCapitalization, + autoCapitalization }: Props): JSX.Element => { const isKeySubDisabled = useRef(false); // comment management in a row @@ -94,7 +95,7 @@ export const SecretInputRow = memo( } = useFieldArray({ control, name: `secrets.${index}.tags` }); // 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 const overrideAction = useWatch({ @@ -127,47 +128,40 @@ export const SecretInputRow = memo( const isOverridden = overrideAction === SecretActionType.Created || overrideAction === SecretActionType.Modified; - - const [editorRef, setEditorRef] = useState(isOverridden ? secValueOverride : secValue); const [hoveredTag, setHoveredTag] = useState(null); const handleTagOnMouseEnter = (wsTag: WsTag) => { setHoveredTag(wsTag); - } + }; const handleTagOnMouseLeave = () => { 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 tags = useWatch({ control, name: `secrets.${index}.tags`, exact: true, defaultValue: [] }) || []; + const tags = + useWatch({ control, name: `secrets.${index}.tags`, exact: true, defaultValue: [] }) || []; const selectedTagIds = tags.reduce>( (prev, curr) => ({ ...prev, [curr.slug]: true }), {} ); - const [isInviteLinkCopied, setInviteLinkCopied] = useToggle(false); - + const [isSecValueCopied, setIsSecValueCopied] = useToggle(false); useEffect(() => { let timer: NodeJS.Timeout; - if (isInviteLinkCopied) { - timer = setTimeout(() => setInviteLinkCopied.off(), 2000); + if (isSecValueCopied) { + timer = setTimeout(() => setIsSecValueCopied.off(), 2000); } return () => clearTimeout(timer); - }, [isInviteLinkCopied]); - - - useEffect(() => { - setEditorRef(isOverridden ? secValueOverride : secValue); - }, [isOverridden]); + }, [isSecValueCopied]); const copyTokenToClipboard = () => { navigator.clipboard.writeText((secValueOverride || secValue) as string); - setInviteLinkCopied.on(); + setIsSecValueCopied.on(); }; const onSecretOverride = () => { @@ -191,8 +185,8 @@ export const SecretInputRow = memo( const onSelectTag = (selectedTag: WsTag) => { const shouldAppend = !selectedTagIds[selectedTag.slug]; if (shouldAppend) { - const {_id: id, name, slug, tagColor} = selectedTag - append({_id: id, name, slug, tagColor}); + const { _id: id, name, slug, tagColor } = selectedTag; + append({ _id: id, name, slug, tagColor }); } else { const pos = tags.findIndex(({ slug }: { slug: string }) => selectedTag.slug === slug); remove(pos); @@ -272,7 +266,7 @@ export const SecretInputRow = memo( ( + render={({ field }) => ( { - onChange(val); - setEditorRef(html); - }} - onBlur={(html) => { - setEditorRef(html); - onBlur(); - }} + {...field} /> )} /> @@ -297,7 +283,7 @@ export const SecretInputRow = memo( ( + render={({ field }) => ( { - onChange(val); - setEditorRef(html); - }} - value={editorRef} - onBlur={(html) => { - setEditorRef(html); - onBlur(); - }} + {...field} /> )} /> @@ -323,38 +301,41 @@ export const SecretInputRow = memo(
- {secretTags.map(({ id, slug, tagColor}) => { + {secretTags.map(({ id, slug, tagColor }) => { return ( - <> - - -
- remove(i)} - key={id} - className="cursor-pointer" - > -
-
- {slug} -
- -
- - onSelectTag(wsTag)} - handleTagOnMouseEnter={(wsTag: WsTag) => handleTagOnMouseEnter(wsTag)} - handleTagOnMouseLeave={() => handleTagOnMouseLeave()} - checkIfTagIsVisible={(wsTag: WsTag) => checkIfTagIsVisible(wsTag)} - handleOnCreateTagOpen={() => onCreateTagOpen()} - /> - - - ) + <> + + +
+ remove(i)} + key={id} + className="cursor-pointer" + > +
+
+ {slug} +
+ +
+ + onSelectTag(wsTag)} + handleTagOnMouseEnter={(wsTag: WsTag) => handleTagOnMouseEnter(wsTag)} + handleTagOnMouseLeave={() => handleTagOnMouseLeave()} + checkIfTagIsVisible={(wsTag: WsTag) => checkIfTagIsVisible(wsTag)} + handleOnCreateTagOpen={() => onCreateTagOpen()} + /> + + + ); })}
@@ -365,7 +346,7 @@ export const SecretInputRow = memo( className="py-[0.42rem]" onClick={copyTokenToClipboard} > - +
diff --git a/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx b/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx index c69cf6514..e8f3730cb 100644 --- a/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx +++ b/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx @@ -1,4 +1,3 @@ -import { useRef } from "react"; import { Controller, useForm } from "react-hook-form"; import { faCheck, faCopy, faTrash, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -39,14 +38,11 @@ export const SecretEditRow = ({ value: defaultValue } }); - const editorRef = useRef(defaultValue); const [isDeleting, setIsDeleting] = useToggle(); const { createNotification } = useNotificationContext(); const handleFormReset = () => { reset(); - const val = getValues(); - editorRef.current = val.value; }; const handleCopySecretToClipboard = async () => { @@ -78,7 +74,6 @@ export const SecretEditRow = ({ try { await onSecretDelete(environment, secretName); reset({ value: undefined }); - editorRef.current = undefined; } finally { setIsDeleting.off(); } @@ -90,20 +85,7 @@ export const SecretEditRow = ({ ( - { - onChange(val); - editorRef.current = html; - }} - onBlur={(html) => { - editorRef.current = html; - onBlur(); - }} - isVisible={isVisible} - /> - )} + render={({ field }) => } />