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

Replaced visibility message with indicator icon #1429

Merged
merged 1 commit into from
Jan 30, 2025
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
30 changes: 29 additions & 1 deletion packages/koenig-lexical/src/components/KoenigCardWrapper.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import CardContext from '../context/CardContext';
import KoenigComposerContext from '../context/KoenigComposerContext';
import React from 'react';
import {$getNodeByKey, CLICK_COMMAND, COMMAND_PRIORITY_LOW} from 'lexical';
import {CardWrapper} from './ui/CardWrapper';
import {EDIT_CARD_COMMAND, SELECT_CARD_COMMAND} from '../plugins/KoenigBehaviourPlugin';
import {DESELECT_CARD_COMMAND, EDIT_CARD_COMMAND, SELECT_CARD_COMMAND} from '../plugins/KoenigBehaviourPlugin';
import {mergeRegister} from '@lexical/utils';
import {useKoenigSelectedCardContext} from '../context/KoenigSelectedCardContext';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';

const KoenigCardWrapper = ({nodeKey, width, wrapperStyle, IndicatorIcon, children}) => {
const {cardConfig} = React.useContext(KoenigComposerContext);
const [editor] = useLexicalComposerContext();
const [cardType, setCardType] = React.useState(null);
const [captionHasFocus, setCaptionHasFocus] = React.useState(null);
Expand All @@ -20,6 +22,21 @@ const KoenigCardWrapper = ({nodeKey, width, wrapperStyle, IndicatorIcon, childre
const isSelected = selectedCardKey === nodeKey;
const isEditing = isSelected && isEditingCard;

const toggleEditMode = React.useCallback((event) => {
event.preventDefault();
event.stopPropagation();

editor.update(() => {
const cardNode = $getNodeByKey(nodeKey);

if (cardNode?.hasEditMode?.() && !isEditing) {
editor.dispatchCommand(EDIT_CARD_COMMAND, {cardKey: nodeKey, focusEditor: true});
} else if (isEditing) {
editor.dispatchCommand(DESELECT_CARD_COMMAND, {cardKey: nodeKey, focusEditor: true});
}
});
}, [editor, isEditing, nodeKey]);

React.useLayoutEffect(() => {
editor.getEditorState().read(() => {
const cardNode = $getNodeByKey(nodeKey);
Expand Down Expand Up @@ -127,6 +144,14 @@ const KoenigCardWrapper = ({nodeKey, width, wrapperStyle, IndicatorIcon, childre
};
}, [editor, isSelected, isEditing, nodeKey, containerRef]);

let isVisibilityActive = false;
if (cardConfig?.feature?.contentVisibilityAlpha) {
editor.getEditorState().read(() => {
const cardNode = $getNodeByKey(nodeKey);
isVisibilityActive = cardNode?.getIsVisibilityActive?.();
});
}

return (
<CardContext.Provider value={{
isSelected,
Expand All @@ -143,11 +168,14 @@ const KoenigCardWrapper = ({nodeKey, width, wrapperStyle, IndicatorIcon, childre
ref={containerRef}
cardType={cardType}
cardWidth={width}
feature={cardConfig?.feature}
IndicatorIcon={IndicatorIcon}
isDragging={isDragging}
isEditing={isEditing}
isSelected={isSelected}
isVisibilityActive={isVisibilityActive}
wrapperStyle={wrapperStyle}
onIndicatorClick={toggleEditMode}
>
{children}
</CardWrapper>
Expand Down
49 changes: 36 additions & 13 deletions packages/koenig-lexical/src/components/ui/CardWrapper.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
import VisibilityIndicator from '../../assets/icons/kg-indicator-visibility.svg?react';

const CARD_WIDTH_CLASSES = {
wide: [
Expand All @@ -17,12 +18,14 @@ const DEFAULT_INDICATOR_POSITION = {
export const CardWrapper = React.forwardRef(({
cardType,
cardWidth,
feature,
IndicatorIcon,
indicatorPosition,
isDragging,
isEditing,
isSelected,
onClick,
isVisibilityActive,
onIndicatorClick,
wrapperStyle,
children,
...props
Expand Down Expand Up @@ -53,20 +56,40 @@ export const CardWrapper = React.forwardRef(({
...(indicatorPosition || {})
};

let indicatorIcon;
if (feature?.contentVisibilityAlpha && isVisibilityActive) {
indicatorIcon = (
<div className="sticky top-0 lg:top-8">
<VisibilityIndicator
aria-label="Card is hidden for select audiences"
className="absolute left-[-6rem] size-5 cursor-pointer text-grey"
data-testid="visibility-indicator"
style={{
left: position.left,
top: position.top
}}
onClick={onIndicatorClick}
/>
</div>
);
} else if (IndicatorIcon) {
indicatorIcon = (
<div className="sticky top-0 lg:top-8">
<IndicatorIcon
aria-label={`${cardType} indicator`}
className="absolute left-[-6rem] size-5 text-grey"
style={{
left: position.left,
top: position.top
}}
/>
</div>
);
}

return (
<>
{IndicatorIcon &&
<div className="sticky top-0 lg:top-8">
<IndicatorIcon
aria-label={`${cardType} indicator`}
className="absolute left-[-6rem] size-5 text-grey"
style={{
left: position.left,
top: position.top
}}
/>
</div>
}
{indicatorIcon}
<div
ref={ref}
className={className}
Expand Down
9 changes: 6 additions & 3 deletions packages/koenig-lexical/src/components/ui/cards/HtmlCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import {sanitizeHtml} from '../../../utils/sanitize-html';

export function HtmlCard({html, updateHtml, isEditing, darkMode, visibilityMessage}) {
const {cardConfig} = React.useContext(KoenigComposerContext);
const isContentVisibilityEnabled = cardConfig?.feature?.contentVisibility || false;
const {feature = {}} = cardConfig;
const {contentVisibility, contentVisibilityAlpha} = feature;

const displayVisibilityMessage = contentVisibility && !contentVisibilityAlpha;

return (
<>
{isEditing
? (
<>
{isContentVisibilityEnabled && <CardVisibilityMessage message={visibilityMessage} />}
{displayVisibilityMessage && <CardVisibilityMessage message={visibilityMessage} />}
<HtmlEditor
darkMode={darkMode}
html={html}
Expand All @@ -24,7 +27,7 @@ export function HtmlCard({html, updateHtml, isEditing, darkMode, visibilityMessa
</>
)
: <div>
{isContentVisibilityEnabled && <CardVisibilityMessage message={visibilityMessage} />}
{displayVisibilityMessage && <CardVisibilityMessage message={visibilityMessage} />}
<HtmlDisplay html={html} />
<div className="absolute inset-0 z-50 mt-0"></div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions packages/koenig-lexical/src/hooks/useVisibilityToggle.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {$getNodeByKey} from 'lexical';
import {generateVisibilityMessage, generateVisibilityMessageAlpha, getVisibilityOptions, parseVisibilityToToggles, serializeOptionsToVisibility, serializeTogglesToVisibility} from '../utils/visibility';
import {generateVisibilityMessage, getVisibilityOptions, parseVisibilityToToggles, serializeOptionsToVisibility, serializeTogglesToVisibility} from '../utils/visibility';

export const useVisibilityToggle = (editor, nodeKey, cardConfig) => {
const isStripeEnabled = cardConfig?.stripeEnabled;
Expand All @@ -18,8 +18,8 @@ export const useVisibilityToggle = (editor, nodeKey, cardConfig) => {
const visibilityOptions = getVisibilityOptions(currentVisibility, {isStripeEnabled});

let visibilityMessage = '';
if (isVisibilityActive) {
visibilityMessage = isContentVisibilityAlphaEnabled ? generateVisibilityMessageAlpha(currentVisibility) : generateVisibilityMessage(currentVisibility);
if (isVisibilityActive && !isContentVisibilityAlphaEnabled) {
visibilityMessage = generateVisibilityMessage(currentVisibility);
}

return {
Expand Down
1 change: 1 addition & 0 deletions packages/koenig-lexical/src/nodes/HtmlNode.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class HtmlNode extends BaseHtmlNode {
return (
<KoenigCardWrapper
IndicatorIcon={HtmlIndicatorIcon}
isVisibilityActive={this.getIsVisibilityActive()}
nodeKey={this.getKey()}
wrapperStyle="wide"
>
Expand Down
82 changes: 0 additions & 82 deletions packages/koenig-lexical/src/utils/visibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,85 +135,3 @@ export function generateVisibilityMessage(visibility) {

return message;
}

export function generateVisibilityMessageAlpha(visibility) {
const toggles = parseVisibilityToToggles(visibility);
const {web, email} = toggles;

const allWeb = web.nonMembers && web.freeMembers && web.paidMembers;
const noWeb = !web.nonMembers && !web.freeMembers && !web.paidMembers;
const allWebMembers = web.freeMembers && web.paidMembers;
const noWebMembers = !web.freeMembers && !web.paidMembers;
const allEmail = email.freeMembers && email.paidMembers;
const noEmail = !email.freeMembers && !email.paidMembers;
const shownToAll = allWeb && allEmail;
const shownToNone = noWeb && noEmail;

if (shownToAll) {
return 'Visible to all web and email';
}

if (shownToNone) {
return 'Not visible on web or email';
}

let message = 'Visible to ';

if (allWeb) {
message += 'all web';

if (!noEmail) {
message += ', ';
}
} else if (!noWeb) {
if (web.nonMembers) {
message += 'anonymous';

if (!noWebMembers) {
message += ' and ';
}
}

if (allWebMembers) {
message += 'logged in';
} else {
if (web.freeMembers) {
message += 'free';

if (web.paidMembers) {
message += ', ';
}
}

if (web.paidMembers) {
message += 'paid';
}
}

message += ' web';

if (!noEmail) {
message += ', ';
}
}

if (allEmail) {
message += 'all email recipients';
} else if (!noEmail) {
if (email.freeMembers) {
message += 'free';

if (email.paidMembers) {
message += ', ';
}
}

if (email.paidMembers) {
message += 'paid';
}

message += ' email';
}

return message;
}
54 changes: 22 additions & 32 deletions packages/koenig-lexical/test/e2e/content-visibility.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ test.describe('Content Visibility', async () => {

test('toolbar shows settings panel on click', async function () {
const card = await insertHtmlCard();

await card.getByTestId('edit-html').click();

// settings are visible
Expand All @@ -185,73 +184,64 @@ test.describe('Content Visibility', async () => {

test('clicking on edit button transitions card into edit mode', async function () {
const card = await insertHtmlCard();

await card.getByTestId('edit-html').click();

await expect(card).toHaveAttribute('data-kg-card-editing', 'true');
});

test('visibility settings - defaults to show on email and web and all members', async function () {
test('visibility settings defaults to show on email and web and all members', async function () {
const card = await insertHtmlCard();

await card.getByTestId('edit-html').click();

await expect(card.getByTestId('visibility-message')).not.toBeVisible();

await expect(card.getByTestId('visibility-toggle-web-nonMembers')).toBeChecked();
await expect(card.getByTestId('visibility-toggle-web-freeMembers')).toBeChecked();
await expect(card.getByTestId('visibility-toggle-web-paidMembers')).toBeChecked();
await expect(card.getByTestId('visibility-toggle-email-freeMembers')).toBeChecked();
await expect(card.getByTestId('visibility-toggle-email-paidMembers')).toBeChecked();
});

test('can toggle visibility settings - show to anonymous web is off', async function () {
test('can toggle visibility settings ', async function () {
const card = await insertHtmlCard();

await card.getByTestId('edit-html').click();
await card.getByTestId('tab-visibility').click();
await card.getByTestId('visibility-toggle-web-nonMembers').click();

await expect(card.getByTestId('visibility-message')).toContainText('Visible to logged in web, all email recipients');
});

test('can toggle visibility settings - show to web is off', async function () {
const card = await insertHtmlCard();

await card.getByTestId('edit-html').click();
await card.getByTestId('tab-visibility').click();
await card.getByTestId('visibility-toggle-web-nonMembers').click();
await expect(card.getByTestId('visibility-toggle-web-nonMembers')).not.toBeChecked();
await card.getByTestId('visibility-toggle-web-freeMembers').click();
await expect(card.getByTestId('visibility-toggle-web-freeMembers')).not.toBeChecked();
await card.getByTestId('visibility-toggle-web-paidMembers').click();

await expect(card.getByTestId('visibility-message')).toContainText('Visible to all email recipients');
});

test('can toggle visibility settings - show on email is off', async function () {
const card = await insertHtmlCard();

await card.getByTestId('edit-html').click();
await card.getByTestId('tab-visibility').click();
await expect(card.getByTestId('visibility-toggle-web-paidMembers')).not.toBeChecked();
await card.getByTestId('visibility-toggle-email-freeMembers').click();
await expect(card.getByTestId('visibility-toggle-email-freeMembers')).not.toBeChecked();
await card.getByTestId('visibility-toggle-email-paidMembers').click();
await expect(card.getByTestId('visibility-toggle-email-paidMembers')).not.toBeChecked();

await expect(card.getByTestId('visibility-message')).toContainText('Visible to all web');
// change from the beta - visibility message is no longer shown
await expect(card.getByTestId('visibility-message')).not.toBeVisible();
});

test('can toggle visibility - disable everything', async function () {
test('visibility icon is shown when visibility changes from shown-to-all', async function () {
const card = await insertHtmlCard();

await expect(page.getByTestId('visibility-indicator')).not.toBeVisible();

await card.getByTestId('edit-html').click();
await card.getByTestId('tab-visibility').click();
await expect(card).toHaveAttribute('data-kg-card-editing', 'true');
await card.getByTestId('visibility-toggle-web-nonMembers').click();
await card.getByTestId('visibility-toggle-web-freeMembers').click();
await card.getByTestId('visibility-toggle-web-paidMembers').click();
await card.getByTestId('visibility-toggle-email-freeMembers').click();
await card.getByTestId('visibility-toggle-email-paidMembers').click();

await expect(card.getByTestId('visibility-message')).toContainText('Not visible on web or email');
await expect(page.getByTestId('visibility-indicator')).toBeVisible();

// clicking visibility indicator toggles edit mode
await page.getByTestId('visibility-indicator').click();
await expect(card).toHaveAttribute('data-kg-card-editing', 'false');
await page.getByTestId('visibility-indicator').click();
await expect(card).toHaveAttribute('data-kg-card-editing', 'true');
});

test('can toggle visibility - member settings hidden when stripe is not enabled', async function () {
test('paid member visibility settings hidden when stripe is not enabled', async function () {
await initialize({page, uri: '/#/?content=false&labs=contentVisibility,contentVisibilityAlpha&stripe=false'});
const card = await insertHtmlCard();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ describe('useVisibilityToggle', () => {
const {result} = callHook({web: {nonMember: false, memberSegment: ''}, email: {memberSegment: 'status:free,status:-free'}}, {feature: {contentVisibilityAlpha: true}});
const {visibilityMessage} = result.current;

expect(visibilityMessage).toBe('Visible to all email recipients');
// NOTE: visibility message display has been removed in contentVisibilityAlpha
expect(visibilityMessage).toBe('');
});

it('returns working toggleVisibility function', () => {
Expand Down
Loading
Loading