Skip to content
Open
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
28 changes: 24 additions & 4 deletions components/content-picker/PickedItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styled from '@emotion/styled';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import DOMPurify from 'dompurify';
import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url';
import { decodeEntities } from '@wordpress/html-entities';
import { __ } from '@wordpress/i18n';
Expand All @@ -20,8 +21,9 @@ export type PickedItemType = {
type: string;
uuid: string;
title: string;
url: string;
url?: string;
status?: string; // Optional status field for checking trashed posts
info?: string;
};

const PickedItemContainer = styled.div<{
Expand Down Expand Up @@ -131,6 +133,13 @@ const ItemURL = styled.span`
text-overflow: ellipsis;
`;

const ItemInfo = styled.span`
font-size: 0.75rem;
line-height: 1.4;
color: #757575;
margin-top: 4px;
`;

const MoveButton = styled(Button)`
&.components-button.has-icon {
min-width: 20px;
Expand Down Expand Up @@ -188,16 +197,27 @@ const PickedItemPreview: React.FC<{ item: PickedItemType; isDeleted?: boolean }>
item,
isDeleted = false,
}) => {
const decodedTitle = decodeEntities(item.title);
const { title, url, info } = item;
const decodedTitle = decodeEntities(title);
return (
<>
<ItemTitle isDeleted={isDeleted}>
<Truncate title={decodedTitle} aria-label={decodedTitle}>
{decodedTitle}
</Truncate>
</ItemTitle>
{item.url && !isDeleted && (
<ItemURL>{filterURLForDisplay(safeDecodeURI(item.url)) || ''}</ItemURL>
{url && !isDeleted && (
<ItemURL>{filterURLForDisplay(safeDecodeURI(url)) || ''}</ItemURL>
)}
{info && (
<ItemInfo
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(info, {
ALLOWED_TAGS: ['br', 'strong', 'em'],
ALLOWED_ATTR: [],
}),
}}
/>
)}
</>
);
Expand Down
106 changes: 69 additions & 37 deletions components/content-picker/SortableList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useCallback, useState, useMemo } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { useSelect } from '@wordpress/data';
import { Post, User, store as coreStore } from '@wordpress/core-data';
import { applyFilters } from '@wordpress/hooks';
import styled from '@emotion/styled';
import PickedItem, { PickedItemType } from './PickedItem';
import { DraggableChip } from './DraggableChip';
Expand Down Expand Up @@ -96,19 +97,30 @@ const SortableList: React.FC<SortableListProps> = ({
// @ts-ignore-next-line - The WordPress types are missing the hasFinishedResolution method.
const { getEntityRecord, hasFinishedResolution } = select(coreStore);

let fields = ['link', 'type', 'id'];

if (mode === 'user') {
fields.push('name');
} else if (mode === 'post') {
fields.push('title');
fields.push('url');
fields.push('subtype');
fields.push('status'); // Include status to check for trashed posts
} else {
fields.push('name');
fields.push('taxonomy');
}

/**
* Filter the fields to be fetched from the API.
*
* @param {string[]} fields - The fields to be fetched.
* @param {ContentSearchMode} mode - The mode of the content picker.
* @returns {string[]} - The filtered fields.
*/
fields = applyFilters('tenup.contentPicker.queryFields', fields, mode) as string[];

return posts.reduce<{ [key: string]: PickedItemType | null }>((acc, item) => {
const fields = ['link', 'type', 'id'];
if (mode === 'user') {
fields.push('name');
} else if (mode === 'post') {
fields.push('title');
fields.push('url');
fields.push('subtype');
fields.push('status'); // Include status to check for trashed posts
} else {
fields.push('name');
fields.push('taxonomy');
}
const getEntityRecordParameters = [
entityKind,
item.type,
Expand All @@ -120,33 +132,53 @@ const SortableList: React.FC<SortableListProps> = ({
if (result) {
let newItem: Partial<PickedItemType>;

if (mode === 'post') {
const post = result as Post;
newItem = {
title: post.title.rendered,
url: post.link,
id: post.id,
type: post.type,
status: post.status, // Include status for trashed post detection
};
} else if (mode === 'user') {
const user = result as User;
newItem = {
title: user.name,
url: user.link,
id: user.id,
type: 'user',
};
} else {
const taxonomy = result as Term;
newItem = {
title: taxonomy.name,
url: taxonomy.link,
id: taxonomy.id,
type: taxonomy.taxonomy,
};
switch (mode) {
case 'post': {
const post = result as Post;
newItem = {
title: post.title.rendered,
url: post.link,
id: post.id,
type: post.type,
status: post.status, // Include status for trashed post detection
};
break;
}
case 'user': {
const user = result as User;
newItem = {
title: user.name,
url: user.link,
id: user.id,
type: 'user',
};
break;
}
default: {
const taxonomy = result as Term;
newItem = {
title: taxonomy.name,
url: taxonomy.link,
id: taxonomy.id,
type: taxonomy.taxonomy,
};
break;
}
}

/**
* Filter the item before it is returned.
*
* @param {PickedItemType} newItem - The item to be returned.
* @param {Post | Term | User} result - The result from the getEntityRecord function.
* @returns {PickedItemType} - The filtered item.
*/
newItem = applyFilters(
'tenup.contentPicker.pickedItem',
newItem,
result,
) as Partial<PickedItemType>;

if (item.uuid) {
newItem.uuid = item.uuid;
}
Expand Down
65 changes: 48 additions & 17 deletions components/content-search/SearchItem.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
import DOMPurify from 'dompurify';
import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url';
import { decodeEntities } from '@wordpress/html-entities';
import {
Expand All @@ -14,10 +15,10 @@ import { NormalizedSuggestion } from './utils';
const SearchItemWrapper = styled(Button)`
&&& {
display: flex;
flex-direction: column;
text-align: left;
width: 100%;
justify-content: space-between;
align-items: center;
align-items: flex-start;
border-radius: 2px;
box-sizing: border-box;
height: auto !important;
Expand All @@ -32,6 +33,14 @@ const SearchItemWrapper = styled(Button)`
}
`;

const SearchItemHeaderWrapper = styled.span`
display: flex;
flex-direction: row;
width: 100%;
justify-content: space-between;
align-items: center;
`;

const SearchItemHeader = styled.span`
display: flex;
flex-direction: column;
Expand All @@ -42,10 +51,17 @@ const SearchItemTitle = styled.span<{ showType: boolean }>`
padding-right: ${({ showType }) => (showType ? 0 : undefined)};
`;

const SearchItemInfo = styled.span<{ showType: boolean }>`
const SearchItemURL = styled.span<{ showType: boolean }>`
padding-right: ${({ showType }) => (showType ? 0 : undefined)};
`;

const SearchItemInfo = styled.span`
font-size: 0.75rem;
line-height: 1.4;
color: #757575;
margin-top: 4px;
`;

const SearchItemType = styled.span`
background-color: rgba(0, 0, 0, 0.05);
color: black;
Expand Down Expand Up @@ -81,26 +97,41 @@ const SearchItem: React.FC<RenderItemComponentProps> = ({
contentTypes,
renderType = defaultRenderItemType,
}) => {
const showType = !!(suggestion.type && contentTypes.length > 1);
const { type, title, url, info } = suggestion;
const showType = !!(type && contentTypes.length > 1);

const richTextContent = create({ html: suggestion.title });
const richTextContent = create({ html: title });
const textContent = getTextContent(richTextContent);
const titleContent = decodeEntities(textContent);

return (
<Tooltip text={decodeEntities(suggestion.title)}>
<Tooltip text={decodeEntities(title)}>
<SearchItemWrapper id={id} onClick={onClick}>
<SearchItemHeader>
<SearchItemTitle showType={showType}>
<StyledTextHighlight text={titleContent} highlight={searchTerm} />
</SearchItemTitle>
<SearchItemInfo aria-hidden showType={showType}>
<Truncate numberOfLines={1} limit={55} ellipsizeMode="middle">
{filterURLForDisplay(safeDecodeURI(suggestion.url)) || ''}
</Truncate>
</SearchItemInfo>
</SearchItemHeader>
{showType && <SearchItemType>{renderType(suggestion)}</SearchItemType>}
<SearchItemHeaderWrapper>
<SearchItemHeader>
<SearchItemTitle showType={showType}>
<StyledTextHighlight text={titleContent} highlight={searchTerm} />
</SearchItemTitle>
{url && (
<SearchItemURL aria-hidden showType={showType}>
<Truncate numberOfLines={1} limit={55} ellipsizeMode="middle">
{filterURLForDisplay(safeDecodeURI(url)) || ''}
</Truncate>
</SearchItemURL>
)}
</SearchItemHeader>
{showType && <SearchItemType>{renderType(suggestion)}</SearchItemType>}
</SearchItemHeaderWrapper>
{info && (
<SearchItemInfo
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(info, {
ALLOWED_TAGS: ['br', 'strong', 'em'],
ALLOWED_ATTR: [],
}),
}}
/>
)}
</SearchItemWrapper>
</Tooltip>
);
Expand Down
Loading
Loading