Skip to content

Commit

Permalink
initial impl
Browse files Browse the repository at this point in the history
  • Loading branch information
dbadura committed Jul 26, 2024
1 parent 9d9ade6 commit abcb219
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 115 deletions.
3 changes: 1 addition & 2 deletions src/components/Extensibility/components-form/GenericList.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { ResourceForm } from 'shared/ResourceForm';
import { useGetTranslation } from 'components/Extensibility/helpers';
import pluralize from 'pluralize';
import { fromJS } from 'immutable';
import { enhanceLink } from 'shared/helpers/crd';

export function GenericList({
storeKeys,
Expand Down Expand Up @@ -57,7 +56,7 @@ export function GenericList({
return (
<ResourceForm.CollapsibleSection
defaultOpen={defaultOpen}
tooltipContent={t(tooltipContent)}
tooltipContent={tExt(tooltipContent)}
container
title={tFromStoreKeys(storeKeys, schema)}
nestingLevel={nestingLevel}
Expand Down
51 changes: 7 additions & 44 deletions src/components/Extensibility/helpers/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { createContext, useContext } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import { OrderedMap } from 'immutable';
import { last, merge } from 'lodash';

import { EMPTY_TEXT_PLACEHOLDER } from 'shared/constants';
import { ExternalLink } from 'shared/components/ExternalLink/ExternalLink';
import { prettifyNamePlural } from 'shared/utils/helpers';

import { jsonataWrapper } from './jsonataWrapper';
import {
createTranslationTextWithLinks,
processTranslation,
} from 'shared/helpers/linkExtractor';

export const TranslationBundleContext = createContext({
translationBundle: 'extensibility',
Expand All @@ -22,7 +25,7 @@ export const applyFormula = (value, formula, t, additionalSources) => {
}
};

export const useGetTranslation = path => {
export const useGetTranslation = () => {
const { translationBundle } = useContext(TranslationBundleContext);
const { t, i18n } = useTranslation([translationBundle]);
//doesn't always work, add `translationBundle.` at the beginning of a path
Expand Down Expand Up @@ -153,30 +156,6 @@ export const getObjectValueWorkaround = (
}
};

const processTranslation = trans => {
const i18VarRegex = /{{.*?}}/g;
const matchesIterator = trans?.matchAll(i18VarRegex);
const matches = matchesIterator ? [...matchesIterator].flat() : null;

if (matches?.length) {
matches.forEach((link, index) => {
const linkReplacement = processLink(link, index);
trans = trans.replace(link, linkReplacement);
});
return { matches: matches, trans: trans };
} else return { matches: [], trans: trans };
};

const processLink = (link, index) => {
let linkText;
if (link.match(/\[(.*?)]/)) {
linkText = link.match(/\[(.*?)]/)[1];
} else {
linkText = link.match(/\((.*?)\)/)[1];
}
return `<${index}>${linkText}</${index}>`;
};

export const useCreateResourceDescription = descID => {
const { t, i18n } = useGetTranslation();
if (!descID) return;
Expand All @@ -185,23 +164,7 @@ export const useCreateResourceDescription = descID => {
const trans = t(descID.replace(helmBracketsRegex, '$1'));

if (typeof trans === 'string') {
const { matches, trans: processedTrans } = processTranslation(trans);
if (matches.length) {
return (
<Trans
i18nKey={processedTrans}
i18n={i18n}
t={t}
components={matches.map((result, idx) => {
const url = result.match(/\((.*?)\)/)[1];

return <ExternalLink url={url} key={idx} />;
})}
/>
);
} else {
return processedTrans;
}
return createTranslationTextWithLinks(trans, t, i18n);
}
};

Expand Down
76 changes: 7 additions & 69 deletions src/shared/components/DescriptionHint/DescriptionHint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { Button, Popover, Text } from '@ui5/webcomponents-react';
import { createPortal } from 'react-dom';
import React, { CSSProperties, ReactNode, useRef, useState } from 'react';
import { uniqueId } from 'lodash';
import { ExternalLink } from 'shared/components/ExternalLink/ExternalLink';
import { useGetTranslation } from '../../../components/Extensibility/helpers';
import { createTranslationTextWithLinks } from '../../helpers/linkExtractor';
import { useTranslation } from 'react-i18next'; // this regex catch 2 things, markdown URL or normal URL

// this regex catch 2 things, markdown URL or normal URL
// markdown url consists of 2 groups: [text](url)
//source: https://github.com/kyma-incubator/milv/blob/main/pkg/parser.go#L22
const LinkRegexExpression = /\[([^\]]*)\]\(([^)]*)\)|\bhttps?:\/\/\S*\b/gm;
// const LinkRegexExpression = /\[([^\]]*)\]\(([^)]*)\)|\bhttps?:\/\/\S*\b/gm;

type HintButtonProps = {
setShowTitleDescription: React.Dispatch<React.SetStateAction<boolean>>;
Expand All @@ -27,13 +27,13 @@ export function HintButton({
}: HintButtonProps) {
const [ID] = useState(uniqueId('id-')); //todo: migrate to useID from react after upgrade to version 18+
const descBtnRef = useRef(null);
const { t, i18n } = useTranslation();
let desc = description;

const { t, widgetT } = useGetTranslation();
let desc = t(description);
if (!disableLinkDetection && typeof description === 'string') {
// desc = enhanceWithLinks(description);
const result = createTranslationTextWithLinks(description, t, i18n);
desc = result;
}
console.log(desc, description);
return (
<>
<Button
Expand Down Expand Up @@ -69,65 +69,3 @@ export function HintButton({
</>
);
}

function enhanceWithLinks(text: string): JSX.Element | null {
// const [a,b ] = useState("")
if (!text) {
return null;
}
const re = new RegExp(LinkRegexExpression);

const regexResult = text.matchAll(re);
// const result = re.exec(text)
if (!regexResult) {
return <>{text}</>;
}

// console.log(regexResult);

const result = [...regexResult];
console.log(result);
if (result.length === 0) {
return <>{text}</>;
}

let modifiedText = text;
let idx = 0;
for (const r of result) {
modifiedText = modifiedText.replace(r[0], `<>`);
// console.log(r[0], modifiedText)
idx++;
// console.log(r);
}

return (
<>
{modifiedText.split('<>').map((val, idx) => {
console.log('Val: ', val);
console.log('idx: ', idx, 'result len: ', result.length);
if (idx >= result.length) {
return <>{val}</>;
}
console.log(idx);
const r = result[idx];
console.log(idx, ':', r);
if (r[1] === undefined) {
return (
<>
{val}
<ExternalLink url={r[0]}>{r[0]}</ExternalLink>
</>
);
} else {
// md link
return (
<>
{val}
<ExternalLink url={r[2]}>{r[1]}</ExternalLink>
</>
);
}
})}
</>
);
}
98 changes: 98 additions & 0 deletions src/shared/helpers/linkExtractor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { Trans } from 'react-i18next';
import { ExternalLink } from 'shared/components/ExternalLink/ExternalLink';

// TODO: change the name to something like: insertLinksToTranslation
export const processTranslation = text => {
let matches = [];
let result = getI18nVarLink(text);
matches = matches.concat(result.matches);
text = result.text;

result = getMdLink(text, matches.length);
text = result.text;
matches = matches.concat(result.matches);

result = getHTMLLinks(text, matches.length);
matches = matches.concat(result.matches);
text = result.text;

return { matches: matches ? matches : [], trans: text };
};

const getI18nVarLink = text => {
const i18VarRegex = /{{.*?}}/g;
let matchesIterator = text?.matchAll(i18VarRegex);
let matches = matchesIterator ? [...matchesIterator].flat() : null;

if (matches?.length) {
matches = matches.map((link, index) => {
const linkReplacement = processLink(link, index);
text = text.replace(link, linkReplacement);
return link.replace('{{', '').replace('}}', '');
});
}
return { matches, text };
};

const getMdLink = (text, globalIdx) => {
const mdLinkRegex = /\[([^\]]*)]\(([^)]*)\)/g;
let matchesIterator = text?.matchAll(mdLinkRegex);
let matches = matchesIterator ? [...matchesIterator] : null;

if (matches?.length) {
matches = matches.map((link, index) => {
const localIdx = index + globalIdx;
const linkReplacement = `<${localIdx}>${link[1]}</${localIdx}>`;
text = text.replace(link[0], linkReplacement);
return `(${link[2]})`;
});
}
return { matches, text };
};

const getHTMLLinks = (text, globalIdx) => {
const httpRegex = /\bhttps?:\/\/\S*\b/g;
let matchesIterator = text?.matchAll(httpRegex);
let matches = matchesIterator ? [...matchesIterator].flat() : null;

if (matches?.length) {
matches = matches.map((link, index) => {
const idx = index + globalIdx;
const linkReplacement = `<${idx}>${link}</${idx}>`;
text = text.replace(link, linkReplacement);
return `(${link})`;
});
}
return { matches, text };
};

export const createTranslationTextWithLinks = (text, t, i18n) => {
// const { t, i18n } = useGetTranslation();
const { matches, trans: processedTrans } = processTranslation(text);
if (matches.length) {
return (
<Trans
i18nKey={processedTrans}
i18n={i18n}
t={t}
components={matches.map((result, idx) => {
const url = result.match(/\((.*?)\)/)[1];

return <ExternalLink url={url} key={idx} />;
})}
/>
);
} else {
return processedTrans;
}
};

const processLink = (link, index) => {
let linkText;
if (link.match(/\[(.*?)]/)) {
linkText = link.match(/\[(.*?)]/)[1];
} else {
linkText = link.match(/\((.*?)\)/)[1];
}
return `<${index}>${linkText}</${index}>`;
};
63 changes: 63 additions & 0 deletions src/shared/helpers/linkExtractor.js.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { processTranslation } from 'shared/helpers/linkExtractor';

describe('process text with different link style', () => {
it('Markdown style links', () => {
//GIVEN
const text = `Welcome to [Kyma](kyma-project.io)`;

//WHEN
const result = processTranslation(text);

//THEN
expect(result.matches).toHaveLength(1);
expect(result.matches).toContain('(kyma-project.io)');
});

it('i18N variables Markdown style links', () => {
//GIVEN
const text = `Welcome to {{[Kyma](kyma-project.io)}}`;

//WHEN
const result = processTranslation(text);

//THEN
expect(result.matches).toHaveLength(1);
expect(result.matches).toContain('(kyma-project.io)');
});
it('Ordinary link with http and https', () => {
//GIVEN
const text = `Welcome to https://kyma-project.io or http://sap.com`;

//WHEN
const result = processTranslation(text);

//THEN
expect(result.matches).toHaveLength(2);
expect(result.matches).toContain('(https://kyma-project.io)');
expect(result.matches).toContain('(http://sap.com)');
});

it('Different link types mixed together', () => {
//GIVEN
const text = `Welcome to https://kyma-project.io.
[Here](https://kyma-project.io/#/02-get-started/01-quick-install) you can find more information about installing kyma.
If you have any problem you can try to reach {{[community](https://pages.community.sap.com/topics/kyma)}}
Get familiar with other product at http://sap.com`;

//WHEN
const result = processTranslation(text);

//THEN
expect(result.matches).toHaveLength(4);
expect(result.matches).toContain('(https://kyma-project.io)');
expect(result.matches).toContain('(http://sap.com)');
expect(result.matches).toContain(
'(https://kyma-project.io/#/02-get-started/01-quick-install)',
);
expect(result.matches).toContain(
'(https://pages.community.sap.com/topics/kyma)',
);
});
});

describe('No links', () => {});

0 comments on commit abcb219

Please sign in to comment.