Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions entry_types/scrolled/config/locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1926,6 +1926,8 @@ de:
review:
add_comment: Kommentar hinzufügen
cancel_add_comment: Abbrechen
hide_comments: Kommentare ausblenden
show_comments: Kommentare einblenden
comment_toolbar: Kommentare
filter:
label: Kommentare filtern
Expand Down
2 changes: 2 additions & 0 deletions entry_types/scrolled/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1755,6 +1755,8 @@ en:
review:
add_comment: Add comment
cancel_add_comment: Cancel
hide_comments: Hide comments
show_comments: Show comments
comment_toolbar: Comments
filter:
label: Filter comments
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React from 'react';
import '@testing-library/jest-dom/extend-expect';
import {fireEvent} from '@testing-library/react';

import {EditableText} from 'frontend/commenting/EditableText';
import {commentHighlightStyles as highlightStyles} from 'pageflow-scrolled/review';
import {renderEntry, useCommentingPageObjects} from 'support/pageObjects/commenting';

describe('comment visibility', () => {
useCommentingPageObjects();

function renderEntryWithComments() {
return renderEntry({
seed: {contentElements: [{typeName: 'withTestId', configuration: {testId: 5}}]},
commenting: {currentUser: {id: 42, name: 'Alice'}, commentThreads: []}
});
}

it('collapses the toolbar to a show button when hidden', () => {
const entry = renderEntryWithComments();

fireEvent.click(entry.getHideCommentsButton());

expect(entry.queryCommentToolbar()).toBeNull();
expect(entry.getShowCommentsButton()).toBeInTheDocument();
});

it('drives the toggle through a view transition when supported', () => {
const startViewTransition = jest.fn(run => {
run();
return {finished: Promise.resolve(), ready: Promise.resolve(), updateCallbackDone: Promise.resolve()};
});
document.startViewTransition = startViewTransition;

try {
const entry = renderEntryWithComments();

fireEvent.click(entry.getHideCommentsButton());

expect(startViewTransition).toHaveBeenCalledTimes(1);
expect(entry.queryCommentToolbar()).toBeNull();
expect(entry.getShowCommentsButton()).toBeInTheDocument();
}
finally {
delete document.startViewTransition;
}
});

it('restores the toolbar from the show button', () => {
const entry = renderEntryWithComments();

fireEvent.click(entry.getHideCommentsButton());
fireEvent.click(entry.getShowCommentsButton());

expect(entry.getCommentToolbar()).toBeInTheDocument();
expect(entry.queryShowCommentsButton()).toBeNull();
});

it('hides comment badges while hidden and restores them when shown', () => {
const entry = renderEntry({
seed: {
sections: [{id: 1, permaId: 10}],
contentElements: [{typeName: 'withTestId', configuration: {testId: 5}}]
},
commenting: {
currentUser: {id: 42, name: 'Alice'},
commentThreads: [
{id: 1, subjectType: 'ContentElement', subjectId: 1, comments: []},
{id: 2, subjectType: 'Section', subjectId: 10, comments: []}
]
}
});

expect(entry.queryAllCommentBadges().length).toBeGreaterThan(0);

fireEvent.click(entry.getHideCommentsButton());
expect(entry.queryAllCommentBadges()).toHaveLength(0);

fireEvent.click(entry.getShowCommentsButton());
expect(entry.queryAllCommentBadges().length).toBeGreaterThan(0);
});

it('hides inline comment highlights when hidden', () => {
const value = [{type: 'paragraph', children: [{text: 'Some text to comment on'}]}];

const entry = renderEntry({
contentElement: {
ui: <EditableText value={value} />,
typeOptions: {inlineComments: true}
},
commenting: {
currentUser: {id: 42, name: 'Alice'},
commentThreads: [
{id: 1, subjectType: 'ContentElement', subjectId: 10,
subjectRange: {anchor: {path: [0, 0], offset: 5}, focus: {path: [0, 0], offset: 9}},
comments: []}
]
}
});

expect(document.querySelector(`.${highlightStyles.highlight}`)).toBeInTheDocument();

fireEvent.click(entry.getHideCommentsButton());

expect(document.querySelector(`.${highlightStyles.highlight}`)).toBeNull();
expect(entry.getByText('Some text to comment on')).toBeInTheDocument();
});

it('leaves add comment mode when hidden', () => {
const entry = renderEntryWithComments();

fireEvent.click(entry.getAddCommentButton());
expect(entry.getCancelAddCommentButton()).toBeInTheDocument();

fireEvent.click(entry.getHideCommentsButton());
fireEvent.click(entry.getShowCommentsButton());

expect(entry.getAddCommentButton()).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export function renderEntry({
return {
...result,
getCommentToolbar: () => result.getByRole('group', {name: 'Comments'}),
queryCommentToolbar: () => result.queryByRole('group', {name: 'Comments'}),
getHideCommentsButton: () => result.getByRole('button', {name: 'Hide comments'}),
getShowCommentsButton: () => result.getByRole('button', {name: 'Show comments'}),
queryShowCommentsButton: () => result.queryByRole('button', {name: 'Show comments'}),
getAddCommentButton: () => result.getByRole('button', {name: 'Add comment'}),
getCancelAddCommentButton: () => result.getByRole('button', {name: 'Cancel add comment'}),
getNewThreadInput: () => result.getByPlaceholderText('Add a comment...'),
Expand All @@ -49,6 +53,8 @@ export function useCommentingPageObjects() {
useFakeTranslations({
'pageflow_scrolled.review.add_comment': 'Add comment',
'pageflow_scrolled.review.cancel_add_comment': 'Cancel add comment',
'pageflow_scrolled.review.hide_comments': 'Hide comments',
'pageflow_scrolled.review.show_comments': 'Show comments',
'pageflow_scrolled.review.comment_toolbar': 'Comments',
'pageflow_scrolled.review.comment_count': '%{count} comments',
'pageflow_scrolled.review.select_content_element': 'Select to comment',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, {createContext, useCallback, useContext, useMemo, useState} from 'react';
import {flushSync} from 'react-dom';

const CommentingVisibilityContext = createContext({
visible: true,
toggle: () => {}
});

export function CommentingVisibilityProvider({children}) {
const [visible, setVisible] = useState(true);

const toggle = useCallback(() => {
const flip = () => setVisible(previous => !previous);

// Morph the toolbar and its collapsed puck into each other where the
// browser supports it. flushSync forces React to commit synchronously so
// the transition captures the post-toggle DOM.
if (document.startViewTransition) {
document.startViewTransition(() => flushSync(flip));
}
else {
flip();
}
}, []);

const value = useMemo(() => ({visible, toggle}), [visible, toggle]);

return (
<CommentingVisibilityContext.Provider value={value}>
{children}
</CommentingVisibilityContext.Provider>
);
}

export function useCommentingVisibility() {
return useContext(CommentingVisibilityContext);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {api} from '../api';
import {widths} from '../layouts';
import {useAddCommentMode} from './AddCommentModeProvider';
import {AddCommentOverlay} from './AddCommentOverlay';
import {useCommentingVisibility} from './CommentingVisibilityProvider';
import {Popover} from './Popover';
import {useSelectedSubject} from './SelectedSubjectProvider';

Expand All @@ -26,9 +27,14 @@ export function ContentElementDecorator({type, width, customMargin, permaId, chi
}

function DefaultCommentDecorator({permaId, flush, children}) {
const {visible} = useCommentingVisibility();
const {active} = useAddCommentMode();
const {isSelected} = useSelectedSubject('ContentElement', permaId);

if (!visible) {
return children;
}

return (
<div className={classNames(styles.wrapper, {[styles.selected]: isSelected})}>
<div inert={active ? '' : undefined}>{children}</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {PlainEditableText, renderElement, renderLeaf} from '../EditableText';
import {useContentElementAttributes} from '../useContentElementAttributes';
import {useAddCommentMode} from './AddCommentModeProvider';
import {useCommentDisplayFilter} from './CommentDisplayFilterProvider';
import {useCommentingVisibility} from './CommentingVisibilityProvider';
import {useSelectedSubject} from './SelectedSubjectProvider';
import {AddCommentHint} from './AddCommentHint';
import {PopoversColumn} from './PopoversColumn';
Expand All @@ -25,8 +26,9 @@ const defaultValue = [{

export const EditableText = React.memo(function EditableText(props) {
const {inlineComments} = useContentElementAttributes();
const {visible} = useCommentingVisibility();

if (inlineComments) {
if (inlineComments && visible) {
return <CommentingEditableText {...props} />;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,24 @@ import {createReviewSession} from 'pageflow/review';
import {ReviewStateProvider, ReviewMessageHandler} from 'pageflow-scrolled/review';
import {AddCommentModeProvider} from './AddCommentModeProvider';
import {CommentDisplayFilterProvider} from './CommentDisplayFilterProvider';
import {CommentingVisibilityProvider} from './CommentingVisibilityProvider';
import {SelectedSubjectProvider} from './SelectedSubjectProvider';
import {FloatingToolbar} from './FloatingToolbar';

export function EntryDecorator({commentingInitialState, children}) {
return (
<ReviewStateProvider initialState={commentingInitialState}>
<ReviewSessionSetup initialState={commentingInitialState} />
<CommentDisplayFilterProvider>
<SelectedSubjectProvider>
<AddCommentModeProvider>
{children}
<FloatingToolbar />
</AddCommentModeProvider>
</SelectedSubjectProvider>
</CommentDisplayFilterProvider>
<CommentingVisibilityProvider>
<CommentDisplayFilterProvider>
<SelectedSubjectProvider>
<AddCommentModeProvider>
{children}
<FloatingToolbar />
</AddCommentModeProvider>
</SelectedSubjectProvider>
</CommentDisplayFilterProvider>
</CommentingVisibilityProvider>
</ReviewStateProvider>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
import React from 'react';
import React, {useEffect} from 'react';
import classNames from 'classnames';

import {useLocatedCommentThreads} from 'pageflow-scrolled/review';
import {useI18n} from '../i18n';
import {useAddCommentMode} from './AddCommentModeProvider';
import {useCommentDisplayFilter} from './CommentDisplayFilterProvider';
import {useCommentingVisibility} from './CommentingVisibilityProvider';
import {useCommentNavigation} from './SelectedSubjectProvider';

import AddCommentIcon from './images/addComment.svg';
import CancelCommentIcon from './images/cancelComment.svg';
import ChevronIcon from './images/chevron.svg';
import HideCommentsIcon from './images/hideComments.svg';
import ShowCommentsIcon from './images/showComments.svg';
import styles from './FloatingToolbar.module.css';

export function FloatingToolbar() {
const {t} = useI18n({locale: 'ui'});
const {visible} = useCommentingVisibility();
const {active, deactivate} = useAddCommentMode();

useEffect(() => {
if (!visible && active) {
deactivate();
}
}, [visible, active, deactivate]);

if (!visible) {
return <ShowCommentsButton />;
}

return (
<div className={styles.toolbar}
Expand All @@ -23,11 +38,42 @@ export function FloatingToolbar() {
<PositionIndicator />
<ResolutionToggleButton />
<NavigationArrows />
<HideCommentsButton />
<AddCommentButton />
</div>
);
}

function HideCommentsButton() {
const {t} = useI18n({locale: 'ui'});
const {toggle} = useCommentingVisibility();
const label = t('pageflow_scrolled.review.hide_comments');

return (
<button className={styles.button}
onClick={toggle}
aria-label={label}
title={label}>
<HideCommentsIcon className={styles.toggleIcon} />
</button>
);
}

function ShowCommentsButton() {
const {t} = useI18n({locale: 'ui'});
const {toggle} = useCommentingVisibility();
const label = t('pageflow_scrolled.review.show_comments');

return (
<button className={styles.puck}
onClick={toggle}
aria-label={label}
title={label}>
<ShowCommentsIcon className={styles.toggleIcon} />
</button>
);
}

function PositionIndicator() {
const {t} = useI18n({locale: 'ui'});
const {count, position} = useCommentNavigation();
Expand Down
Loading
Loading