Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: insert link plugin tinymce #455

Closed
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
53ce3a6
feat: insert link plugin tinymce
johnvente Jan 18, 2024
e9dee15
test: adding more test unit for insert link modal
johnvente Jan 18, 2024
15f00a8
test: adding test for insertlinkmodaltoggle hook
johnvente Jan 18, 2024
2dcc8a6
test: some tests for formatblocks util
johnvente Jan 18, 2024
214ea4d
test: more test for insertlinkmodal component
johnvente Jan 18, 2024
173e9f3
test: adding more coverage for insertlinkmodal components
johnvente Jan 19, 2024
3eebebb
refactor: subsectios name for blocklist component
johnvente Jan 19, 2024
9cd8d8b
fix: remove enzyme for tests
johnvente Jan 22, 2024
b7b42e6
refactor: remove blank line
johnvente Jan 24, 2024
c963cfa
refactor: moving utils to their components and url validation
johnvente Feb 15, 2024
efc7dc8
chore: upgrade with main branch
johnvente Feb 15, 2024
dc14183
fix: changing exd to openedx modules
johnvente Feb 15, 2024
64cb44e
fix: tests for filterblock updated and config for tests
johnvente Feb 15, 2024
18c0802
feat: adding prefix for http https urls and translations for insert link
johnvente Feb 16, 2024
fa00def
fix: solve conflicts
johnvente Feb 20, 2024
08b1e75
refactor: removing confirmlinkformatalert for urls
johnvente Feb 20, 2024
71172ae
fix: limit insert link only for course sections
johnvente Feb 22, 2024
095aa67
fix: handling no selected text for jum to
johnvente Feb 29, 2024
7055c2b
fix: disable tooltip with no selected text and external url edge case
johnvente Mar 15, 2024
a8c88b5
fix: jump to link edition
johnvente Apr 2, 2024
b11bf08
fix: removing delete jump to link
johnvente Apr 2, 2024
b5b40b4
feat: adding support for editing a selected link
johnvente Apr 5, 2024
989b538
fix: test for shared components
johnvente Apr 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"fast-xml-parser": "^4.0.10",
"frontend-components-tinymce-advanced-plugins": "^1.0.2",
"lodash-es": "^4.17.21",
"lodash.clonedeep": "^4.5.0",
"lodash.flatten": "^4.4.0",
"moment": "^2.29.4",
"moment-shortformat": "^2.1.0",
Expand Down
1 change: 1 addition & 0 deletions src/editors/data/constants/tinyMCE.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const buttons = StrictDict({
undo: 'undo',
underline: 'underline',
a11ycheck: 'a11ycheck',
insertLink: 'insertlink',
});

export const plugins = listKeyStore([
Expand Down
37 changes: 37 additions & 0 deletions src/editors/sharedComponents/InsertLinkModal/BlockLink/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import PropTypes from 'prop-types';
import { Button } from '@edx/paragon';
import { LinkOff } from '@edx/paragon/icons';
import { formatBlockPath } from '../utils';

import './index.scss';

const BlockLink = ({ path, onCloseLink }) => {
const { title, subTitle } = formatBlockPath(path);
return (
<div className="link-container d-flex row p-4 rounded border border-gray-400 mx-4 mt-3">
<div className="col-10">
<p className="text-left">{subTitle}</p>
<p className="h2 w-20 title">{title}</p>
</div>
<div className="col-2">
<Button
variant="tertiary"
className="d-flex justify-content-center align-items-center"
data-testid="close-link-button"
size="lg"
iconBefore={LinkOff}
onClick={onCloseLink}
>
&nbsp;
</Button>
</div>
</div>
);
};

BlockLink.propTypes = {
path: PropTypes.string.isRequired,
onCloseLink: PropTypes.func.isRequired,
};

export default BlockLink;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.link-container {
.title {
overflow-wrap: break-word;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import Enzyme, { shallow } from 'enzyme';
johnvente marked this conversation as resolved.
Show resolved Hide resolved
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
import { formatBlockPath } from '../utils';
import BlockLink from './index';

Enzyme.configure({ adapter: new Adapter() });

describe('BlockLink Component', () => {
const defaultProps = {
path: 'Some Path',
onCloseLink: jest.fn(),
};

test('renders with default props', () => {
const wrapper = shallow(<BlockLink {...defaultProps} />);
expect(wrapper.text()).toContain('Some Path');
});

test('renders correctly with custom path', () => {
const customProps = {
...defaultProps,
path: 'Custom Path',
};
const wrapper = shallow(<BlockLink {...customProps} />);
expect(wrapper.text()).toContain('Custom Path');
});

test('calls onCloseLink when the button is clicked', () => {
const wrapper = shallow(<BlockLink {...defaultProps} />);
wrapper.find({ 'data-testid': 'close-link-button' }).simulate('click');
expect(defaultProps.onCloseLink).toHaveBeenCalledTimes(1);
});

test('renders with valid title and subtitle', () => {
const customProps = {
path: 'Root Section / Child 1',
onCloseLink: jest.fn(),
};
const wrapper = shallow(<BlockLink {...customProps} />);
const { title, subTitle } = formatBlockPath(customProps.path);

expect(wrapper.text()).toContain(title);
expect(wrapper.text()).toContain(subTitle);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`BlocksList Component snapshot 1`] = `
<div>
<div
class="block-list-container"
>
<div
class="pgn-transition-replace-group position-relative"
>
<div
style="padding: .1px 0px;"
>
<div
class="w-100 d-flex justify-content-space-between p-3 pgn__action-row"
>
<button
class="col-11 py-4 btn btn-tertiary"
data-testid="block-name"
type="button"
>
<span
class="w-100 text-left"
>
Any display name
</span>
</button>
<button
class="col-1 py-4 btn btn-tertiary"
data-testid="block-navigation"
type="button"
>

<span
class="pgn__icon btn-icon-after"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m6.49 20.13 1.77 1.77 9.9-9.9-9.9-9.9-1.77 1.77L14.62 12l-8.13 8.13Z"
fill="currentColor"
/>
</svg>
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;
149 changes: 149 additions & 0 deletions src/editors/sharedComponents/InsertLinkModal/BlocksList/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { useState } from 'react';
import PropTypes from 'prop-types';
import { Button, TransitionReplace, ActionRow } from '@edx/paragon';
import { ArrowForwardIos, ArrowBack } from '@edx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';

import {
blockTypes,
getSectionsList,
getChildrenFromList,
} from '../utils';

import messages from './messages';
import './index.scss';

const BlocksList = ({ blocks, onBlockSelected }) => {
const intl = useIntl();
const messageBlockType = {
[blockTypes.section]: intl.formatMessage(
messages.blocksListSubsectionTitle,
),
[blockTypes.subsection]: intl.formatMessage(messages.blocksListUnitTitle),
[blockTypes.unit]: intl.formatMessage(messages.blocksListUnitTitle),
};

const [blockState, setBlockState] = useState({
blockSelected: {},
type: blockTypes.subsection,
hasNavigated: false,
blocksNavigated: [],
});

const sections = getSectionsList(blocks);
const subSections = getChildrenFromList(
johnvente marked this conversation as resolved.
Show resolved Hide resolved
blockState.blockSelected,
blocks,
);
const listItems = blockState.hasNavigated ? subSections : sections;

const isBlockSelectedUnit = blockState.type === blockTypes.unit;
const blockNameButtonClass = isBlockSelectedUnit ? 'col-12' : 'col-11';

const handleSelectBlock = (block, navigate = false) => {
if (navigate) {
setBlockState({
...blockState,
blocksNavigated: [...blockState.blocksNavigated, block.id],
blockSelected: block,
type: block.type,
hasNavigated: true,
});
} else {
onBlockSelected(block);
}
};

const handleGoBack = () => {
const newValue = blockState.blocksNavigated.filter(
(id) => id !== blockState.blockSelected.id,
);
if (newValue.length) {
const lastBlockIndex = newValue.length - 1;
const blockId = newValue[lastBlockIndex];
const newBlock = blocks[blockId];
setBlockState({

Check warning on line 65 in src/editors/sharedComponents/InsertLinkModal/BlocksList/index.jsx

View check run for this annotation

Codecov / codecov/patch

src/editors/sharedComponents/InsertLinkModal/BlocksList/index.jsx#L62-L65

Added lines #L62 - L65 were not covered by tests
...blockState,
type: newBlock.type,
hasNavigated: true,
blockSelected: newBlock,
blocksNavigated: newValue,
});
} else {
setBlockState({
...blockState,
type: blockState.section,
hasNavigated: false,
blockSelected: {},
});
}
};

return (
<>
{blockState.hasNavigated && (
<ActionRow className="w-100 d-flex justify-content-space-between p-3">
<Button
variant="tertiary"
className="col-1"
onClick={handleGoBack}
iconBefore={ArrowBack}
data-testid="block-back-navigation"
>
&nbsp;
</Button>

johnvente marked this conversation as resolved.
Show resolved Hide resolved
<p className="col-11 text-center">{messageBlockType[blockState.type]}</p>
</ActionRow>
)}
<div className="block-list-container">
{listItems.map((block) => (
<TransitionReplace key={`transition_${block.id}`}>
<ActionRow
key={block.id}
className="w-100 d-flex justify-content-space-between p-3"
>
<Button
variant="tertiary"
className={`${blockNameButtonClass} py-4`}
onClick={() => handleSelectBlock(block)}
data-testid="block-name"
>
<span className="w-100 text-left">{block.displayName}</span>
</Button>
{!isBlockSelectedUnit && (
<Button
variant="tertiary"
className="col-1 py-4"
onClick={() => handleSelectBlock(block, true)}
data-testid="block-navigation"
iconAfter={ArrowForwardIos}
>
&nbsp;
</Button>
)}
</ActionRow>
</TransitionReplace>
))}
</div>
</>
);
};

const blockShape = PropTypes.shape({
id: PropTypes.string.isRequired,
blockId: PropTypes.string.isRequired,
lmsWebUrl: PropTypes.string.isRequired,
legacyWebUrl: PropTypes.string.isRequired,
studentViewUrl: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired,
children: PropTypes.arrayOf(PropTypes.string),
});

BlocksList.propTypes = {
blocks: PropTypes.objectOf(blockShape).isRequired,
onBlockSelected: PropTypes.func.isRequired,
};

export default BlocksList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.block-list-container {
height: 200px;
overflow-y: auto;
overflow-x: none;
}
Loading
Loading