in packages/dmn-editor/src/propertiesPanel/DocumentationLinksFormGroup.tsx [228:422]
function DocumentationLinksInput({
title,
url,
isReadOnly,
isUrlExpanded,
onChange,
onRemove,
setUrlExpanded,
autoFocus: parentAutoFocus,
}: {
title: string;
url: string;
isReadOnly: boolean;
isUrlExpanded: boolean;
onChange: (newUrlTitle: string, newUrl: string) => void;
onRemove: () => void;
setUrlExpanded: (isExpanded: boolean) => void;
autoFocus: boolean;
}) {
const urlTitleRef = useRef<HTMLInputElement>(null);
const uuid = useMemo(() => generateUuid(), []);
const [titleIsUrl, setTitleIsUrl] = useState(false);
const updatedOnToogle = useRef(false);
const { hovered } = useDraggableItemContext();
const [autoFocus, setAutoFocus] = useState(false);
const parseUrl = useCallback((newUrl: string) => {
try {
const url = new URL(newUrl);
return url.toString();
} catch (error) {
try {
if (!newUrl.includes("http://") && !newUrl.includes("https://")) {
const urlWithProtocol = "https://" + newUrl + "/";
const url = new URL(urlWithProtocol);
// the new URL automatically converts the whitespaces to %20
// this check verifies if the url has whitespaces
return url.toString() === urlWithProtocol ? url.toString() : undefined;
}
} catch (error) {
return undefined;
}
return undefined;
}
}, []);
const toogleExpanded = useCallback(
(title: string, url: string) => {
const parsedUrl = parseUrl(url);
if (parsedUrl !== undefined && isUrlExpanded === true && (title === "" || titleIsUrl)) {
// valid parsed url and empty title
setTitleIsUrl(true);
updatedOnToogle.current = true;
onChange(parsedUrl, parsedUrl);
setUrlExpanded(false);
setAutoFocus(false);
} else if (parsedUrl !== undefined && parsedUrl !== url && isUrlExpanded === true) {
// valid parsed url and different than the current url
updatedOnToogle.current = true;
onChange(title, parsedUrl);
setUrlExpanded(false);
setAutoFocus(false);
} else if (url !== "" && parsedUrl === undefined && title === "") {
// invalid parsed url and empty title
updatedOnToogle.current = true;
onChange("", url);
} else if (url !== "" && parsedUrl === undefined) {
// nothing should be done with an invalid url
} else {
setUrlExpanded(!isUrlExpanded);
setAutoFocus(!isUrlExpanded);
}
},
[isUrlExpanded, titleIsUrl, parseUrl, setUrlExpanded, onChange]
);
const isUrl = useMemo(() => {
try {
return new URL(url) && !isUrlExpanded;
} catch (error) {
return false;
}
}, [url, isUrlExpanded]);
const allUniqueNames = useMemo(() => new Map<string, string>(), []);
const validateTitle = useCallback((id, name, allUniqueNames) => true, []);
const validateUrl = useCallback(
(id: string, url: string | undefined, allUniqueNames) => {
if (url !== undefined && url !== "") {
return parseUrl(url) !== undefined;
}
return true;
},
[parseUrl]
);
const urlDescriptionTooltip = useMemo(() => {
return url !== "" ? (
<Text component={TextVariants.p}>{url}</Text>
) : (
<Text component={TextVariants.p}>Empty URL</Text>
);
}, [url]);
const removeTooltip = useMemo(() => <Text component={TextVariants.p}>Remove</Text>, []);
return (
<React.Fragment>
<div
className={"kie-dmn-editor--documentation-link--row"}
data-testid={"kie-tools--dmn-editor--documentation-link--row"}
>
<Button
title={"Expand / collapse documentation link"}
variant={ButtonVariant.plain}
className={"kie-dmn-editor--documentation-link--row-expand-toogle"}
onClick={() => toogleExpanded(title, url)}
>
{(isUrlExpanded && <AngleDownIcon />) || <AngleRightIcon />}
</Button>
<div className={"kie-dmn-editor--documentation-link--row-item"}>
{!isUrlExpanded ? (
<>
<div ref={urlTitleRef} className={"kie-dmn-editor--documentation-link--row-title"}>
{isUrl ? (
<a href={url} target={"_blank"} data-testid={"kie-tools--dmn-editor--documentation-link--row-title"}>
{title}
</a>
) : (
<p style={title === "" ? {} : invalidInlineFeelNameStyle} onClick={() => setUrlExpanded(true)}>
{title !== "" ? title : PLACEHOLDER_URL_TITLE}
</p>
)}
</div>
{!isUrlExpanded && (
<Tooltip content={urlDescriptionTooltip} position={TooltipPosition.topStart} triggerRef={urlTitleRef} />
)}
</>
) : (
<div className={"kie-dmn-editor--documentation-link--row-inputs"}>
<InlineFeelNameInput
isPlain={true}
isReadOnly={isReadOnly}
id={`${uuid}-name`}
shouldCommitOnBlur={true}
placeholder={PLACEHOLDER_URL_TITLE}
name={title ?? ""}
onRenamed={(newUrlTitle) => {
if (!updatedOnToogle.current && newUrlTitle !== title) {
onChange(newUrlTitle, url);
setTitleIsUrl(false);
}
// reset the changedByToogle
updatedOnToogle.current = false;
}}
allUniqueNames={() => allUniqueNames}
validate={validateTitle}
autoFocus={parentAutoFocus || autoFocus}
onKeyDown={(e) => {
if (e.code === "Enter") {
// onRenamed and onKeyDown are performed simultaneously, calling the toggleExpdaded callback
// with a outdate title value, making it necessary to use the e.currentTarget.value.
toogleExpanded(e.currentTarget.value, url);
}
}}
/>
<InlineFeelNameInput
className={"kie-dmn-editor--documentation-link--row-inputs-url"}
isPlain={true}
isReadOnly={isReadOnly}
id={`${uuid}-url`}
shouldCommitOnBlur={true}
placeholder={PLACEHOLDER_URL}
name={url ?? ""}
onRenamed={(newUrl: string) => {
if (!updatedOnToogle.current && newUrl !== url) {
onChange(title, newUrl);
}
// reset the changedByToogle
updatedOnToogle.current = false;
}}
allUniqueNames={() => allUniqueNames}
validate={validateUrl}
saveInvalidValue={true}
onKeyDown={(e) => {
if (e.code === "Enter") {
// onRenamed and onKeyDown are performed simultaneously, calling the toggleExpdaded callback
// with a outdate url value, making it necessary to use the e.currentTarget.value.
toogleExpanded(title, e.currentTarget.value);
}
}}
/>
</div>
)}