Skip to content

Commit

Permalink
Add remove link and get link methods
Browse files Browse the repository at this point in the history
  • Loading branch information
florianduros committed Jan 4, 2023
1 parent 921adc7 commit bd16267
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 25 deletions.
2 changes: 2 additions & 0 deletions platforms/web/lib/composer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const formattingFunctions: FormattingFunctions = {
clear: () => {},
insertText: (text: string) => {},
link: (link: string, text?: string) => {},
removeLinks: () => {},
getLink: () => '',
};

let replacedWithText: string | null = null;
Expand Down
2 changes: 2 additions & 0 deletions platforms/web/lib/composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ export function processInput(
);
}
break;
case 'removeLinks':
return action(composerModel.remove_links(), 'remove_links');
case 'sendMessage':
// We create this event type when the user presses Ctrl+Enter.
// We don't do anythign here, but the user may want to hook in
Expand Down
25 changes: 20 additions & 5 deletions platforms/web/lib/testUtils/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,40 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { forwardRef } from 'react';
import { forwardRef, MutableRefObject } from 'react';

import { InputEventProcessor } from '../types';
import { FormattingFunctions, InputEventProcessor } from '../types';
import { useWysiwyg } from '../useWysiwyg';

interface EditorProps {
initialContent?: string;
inputEventProcessor?: InputEventProcessor;
actionsRef?: MutableRefObject<FormattingFunctions | null>;
}

export const Editor = forwardRef<HTMLDivElement, EditorProps>(function Editor(
{ initialContent, inputEventProcessor }: EditorProps,
{ initialContent, inputEventProcessor, actionsRef }: EditorProps,
forwardRef,
) {
const { ref, isWysiwygReady, wysiwyg, actionStates, content } = useWysiwyg({
initialContent,
inputEventProcessor,
});

if (actionsRef) actionsRef.current = wysiwyg;

const keys = Object.keys(wysiwyg).filter(
(key) => key !== 'insertText' && key !== 'link',
) as Array<Exclude<keyof typeof wysiwyg, 'insertText' | 'link'>>;
(key) =>
key !== 'insertText' &&
key !== 'link' &&
key !== 'removeLinks' &&
key !== 'getLink',
) as Array<
Exclude<
keyof typeof wysiwyg,
'insertText' | 'link' | 'removeLinks' | 'getLink'
>
>;
return (
<>
{keys.map((key) => (
Expand Down Expand Up @@ -63,6 +75,9 @@ export const Editor = forwardRef<HTMLDivElement, EditorProps>(function Editor(
>
link with text
</button>
<button type="button" onClick={() => wysiwyg.removeLinks()}>
remove links
</button>
<div
ref={(node) => {
if (node) {
Expand Down
2 changes: 2 additions & 0 deletions platforms/web/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export type FormattingFunctions = Record<
> & {
insertText: (text: string) => void;
link: (link: string, text?: string) => void;
removeLinks: () => void;
getLink: () => string;
};

export type Wysiwyg = {
Expand Down
7 changes: 6 additions & 1 deletion platforms/web/lib/useFormattingFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import { RefObject, useMemo } from 'react';
import { BlockType, FormattingFunctions } from './types';
import { sendWysiwygInputEvent } from './useListeners';
import { LinkEvent } from './useListeners/types';
import { ComposerModel } from '../generated/wysiwyg';

export function useFormattingFunctions(
editorRef: RefObject<HTMLElement | null>,
composerModel: ComposerModel | null,
) {
const formattingFunctions = useMemo<FormattingFunctions>(() => {
// The formatting action like inline code doesn't have an input type
Expand Down Expand Up @@ -54,8 +56,11 @@ export function useFormattingFunctions(
insertText: (text: string) => sendEvent('insertText', text),
link: (link: string, text?: string) =>
sendEvent('insertLink', { link, text }),
removeLinks: () => sendEvent('removeLinks'),
getLink: () =>
composerModel?.get_link_action()?.edit_link?.link || '',
};
}, [editorRef]);
}, [editorRef, composerModel]);

return formattingFunctions;
}
63 changes: 45 additions & 18 deletions platforms/web/lib/useWysiwyg.formatting.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import {
waitFor,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createRef, MutableRefObject } from 'react';

import { Editor } from './testUtils/Editor';
import { select } from './testUtils/selection';
import { FormattingFunctions } from './types';

describe.each([
[
Expand Down Expand Up @@ -220,25 +222,26 @@ describe('insertText', () => {
});

describe('link', () => {
let buttonLink: HTMLButtonElement;
let buttonLinkWithText: HTMLButtonElement;
let textbox: HTMLDivElement;

beforeEach(async () => {
render(<Editor />);
textbox = screen.getByRole('textbox');
async function renderEditor(
initialContent?: string,
ref?: MutableRefObject<FormattingFunctions | null>,
) {
render(<Editor initialContent={initialContent} actionsRef={ref} />);
const textbox: HTMLDivElement = screen.getByRole('textbox');
await waitFor(() =>
expect(textbox).toHaveAttribute('contentEditable', 'true'),
);
buttonLink = screen.getByRole('button', { name: 'link' });
buttonLinkWithText = screen.getByRole('button', {
name: 'link with text',
});
});
return textbox;
}

it('Should insert the link with text', async () => {
// When
userEvent.click(buttonLinkWithText);
const textbox = await renderEditor();
await userEvent.click(
screen.getByRole('button', {
name: 'link with text',
}),
);

// Then
await waitFor(() =>
Expand All @@ -248,16 +251,40 @@ describe('link', () => {

it('Should transform the selected text into link', async () => {
// When
fireEvent.input(textbox, {
data: 'foobar',
inputType: 'insertText',
});
const textbox = await renderEditor('foobar');
select(textbox, 0, 6);
userEvent.click(buttonLink);
await userEvent.click(screen.getByRole('button', { name: 'link' }));

// Then
await waitFor(() =>
expect(textbox).toContainHTML('<a href="my link">foobar</a>'),
);
});

it('Should remove the link', async () => {
// When
const textbox = await renderEditor('<a href="my link">foobar</a>');
select(textbox, 0, 6);
await userEvent.click(
screen.getByRole('button', { name: 'remove links' }),
);

// Then
await waitFor(() => expect(textbox).toContainHTML('foobar'));
});

it('Should get the link', async () => {
// When
const actionsRef = createRef<FormattingFunctions>();
const textbox = await renderEditor(
'<a href="my link">foobar</a>',
actionsRef,
);
select(textbox, 0, 6);

// Then
await waitFor(() =>
expect(actionsRef.current?.getLink()).toBe('my link'),
);
});
});
2 changes: 1 addition & 1 deletion platforms/web/lib/useWysiwyg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function useWysiwyg(wysiwygProps?: WysiwygProps) {
composerModel,
);

const formattingFunctions = useFormattingFunctions(ref);
const formattingFunctions = useFormattingFunctions(ref, composerModel);

const { content, actionStates } = useListeners(
ref,
Expand Down

0 comments on commit bd16267

Please sign in to comment.