Skip to content

Commit 9282ed4

Browse files
Merge branch 'develop' into feature/raw-titles-content-picker
2 parents 8733cc5 + fa1cca6 commit 9282ed4

File tree

17 files changed

+11381
-7627
lines changed

17 files changed

+11381
-7627
lines changed

CONTRIBUTING.md

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,45 @@ The `develop` branch is the development branch which means it contains the next
2828

2929
## Local Environment
3030

31-
This repository contains a local environment setup using the `@wordpress/env` package. Before you can start that environment you will need to run `npm ci` in both the repository root and `example` directory. This will install the required dependencies.
31+
This repository contains a local environment setup using the `@wordpress/env` package and uses npm workspaces to manage dependencies for both the root package and the `example` workspace.
3232

33-
Next, run `npm run build` in both the root and `example` directories to build and compile the needed assets or if you want to watch for changes instead, use `npm run start`.
33+
### Installation
3434

35-
Lastly, navigate your terminal to the `example` directory and run `npm run wp-env start` to start the local environment. The environment should be available at [http://localhost:8888](http://localhost:8888) and the credentials to login to the admin are: `admin` `password`.
35+
From the repository root, run:
36+
37+
```bash
38+
npm ci
39+
```
40+
41+
This will install all dependencies for both the root package and the `example` workspace automatically.
42+
43+
### Building
44+
45+
To build the assets, run from the repository root:
46+
47+
```bash
48+
npm run build
49+
```
50+
51+
This will build both the main package and the example workspace. Alternatively, if you want to watch for changes during development, use:
52+
53+
```bash
54+
npm run start
55+
```
56+
57+
You can also build workspaces individually if needed:
58+
- From root: `npm run build` (builds main package)
59+
- From example: `npm run build` (builds example workspace)
60+
61+
### Starting the Local Environment
62+
63+
From the repository root, run:
64+
65+
```bash
66+
npm run start-test-env
67+
```
68+
69+
This will start the WordPress environment and import test media. The environment should be available at [http://localhost:8888](http://localhost:8888) and the credentials to login to the admin are: `admin` `password`.
3670

3771
## Working on a new or existing component
3872

components/content-picker/PickedItem.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import styled from '@emotion/styled';
22
import { useSortable } from '@dnd-kit/sortable';
33
import { CSS } from '@dnd-kit/utilities';
4+
import { safeHTML } from '@wordpress/dom';
45
import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url';
56
import { decodeEntities } from '@wordpress/html-entities';
67
import { __ } from '@wordpress/i18n';
@@ -20,8 +21,9 @@ export type PickedItemType = {
2021
type: string;
2122
uuid: string;
2223
title: string;
23-
url: string;
24+
url?: string;
2425
status?: string; // Optional status field for checking trashed posts
26+
info?: string;
2527
};
2628

2729
const PickedItemContainer = styled.div<{
@@ -131,6 +133,13 @@ const ItemURL = styled.span`
131133
text-overflow: ellipsis;
132134
`;
133135

136+
const ItemInfo = styled.span`
137+
font-size: 0.75rem;
138+
line-height: 1.4;
139+
color: #757575;
140+
margin-top: 4px;
141+
`;
142+
134143
const MoveButton = styled(Button)`
135144
&.components-button.has-icon {
136145
min-width: 20px;
@@ -188,16 +197,24 @@ export const PickedItemPreview: React.FC<{ item: PickedItemType; isDeleted?: boo
188197
item,
189198
isDeleted = false,
190199
}) => {
191-
const decodedTitle = decodeEntities(item.title);
200+
const { title, url, info } = item;
201+
const decodedTitle = decodeEntities(title);
192202
return (
193203
<>
194204
<ItemTitle isDeleted={isDeleted}>
195205
<Truncate title={decodedTitle} aria-label={decodedTitle}>
196206
{decodedTitle}
197207
</Truncate>
198208
</ItemTitle>
199-
{item.url && !isDeleted && (
200-
<ItemURL>{filterURLForDisplay(safeDecodeURI(item.url)) || ''}</ItemURL>
209+
{url && !isDeleted && (
210+
<ItemURL>{filterURLForDisplay(safeDecodeURI(url)) || ''}</ItemURL>
211+
)}
212+
{info && (
213+
<ItemInfo
214+
dangerouslySetInnerHTML={{
215+
__html: safeHTML(info),
216+
}}
217+
/>
201218
)}
202219
</>
203220
);

components/content-picker/SortableList.tsx

Lines changed: 74 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/**
2+
* External dependencies
3+
*/
14
import {
25
DndContext,
36
closestCenter,
@@ -11,16 +14,26 @@ import {
1114
defaultDropAnimation,
1215
} from '@dnd-kit/core';
1316
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
17+
import styled from '@emotion/styled';
18+
19+
/**
20+
* WordPress dependencies
21+
*/
1422
import { __experimentalTreeGrid as TreeGrid } from '@wordpress/components';
1523
import { useCallback, useState, useMemo } from '@wordpress/element';
1624
import { __ } from '@wordpress/i18n';
1725
import { useSelect } from '@wordpress/data';
1826
import { Post, User, store as coreStore } from '@wordpress/core-data';
19-
import styled from '@emotion/styled';
27+
28+
/**
29+
* Internal dependencies
30+
*/
2031
import PickedItem, { PickedItemType } from './PickedItem';
2132
import { DraggableChip } from './DraggableChip';
22-
import { ContentSearchMode } from '../content-search/types';
2333
import { toPlainTextTitle } from '../content-search/utils';
34+
import { ContentSearchMode, QueryFieldsFilter } from '../content-search/types';
35+
import type { PickedItemFilter } from './index';
36+
import { Term } from './types';
2437

2538
const dropAnimation = {
2639
...defaultDropAnimation,
@@ -34,20 +47,10 @@ interface SortableListProps {
3447
mode: ContentSearchMode;
3548
setPosts: (posts: Array<PickedItemType>) => void;
3649
PickedItemPreviewComponent?: React.ComponentType<{ item: PickedItemType }>;
50+
queryFieldsFilter?: QueryFieldsFilter;
51+
pickedItemFilter?: PickedItemFilter;
3752
}
3853

39-
type Term = {
40-
count: number;
41-
description: string;
42-
id: number;
43-
link: string;
44-
meta: Record<string, unknown>;
45-
name: string;
46-
parent: number;
47-
slug: string;
48-
taxonomy: string;
49-
};
50-
5154
function getEntityKind(mode: ContentSearchMode) {
5255
let type;
5356
switch (mode) {
@@ -85,6 +88,8 @@ const SortableList: React.FC<SortableListProps> = ({
8588
mode = 'post',
8689
setPosts,
8790
PickedItemPreviewComponent,
91+
queryFieldsFilter,
92+
pickedItemFilter,
8893
}) => {
8994
const hasMultiplePosts = posts.length > 1;
9095
const [activeId, setActiveId] = useState<string | null>(null);
@@ -97,19 +102,25 @@ const SortableList: React.FC<SortableListProps> = ({
97102
// @ts-ignore-next-line - The WordPress types are missing the hasFinishedResolution method.
98103
const { getEntityRecord, hasFinishedResolution } = select(coreStore);
99104

105+
let fields = ['link', 'type', 'id'];
106+
107+
if (mode === 'user') {
108+
fields.push('name');
109+
} else if (mode === 'post') {
110+
fields.push('title');
111+
fields.push('url');
112+
fields.push('subtype');
113+
fields.push('status'); // Include status to check for trashed posts
114+
} else {
115+
fields.push('name');
116+
fields.push('taxonomy');
117+
}
118+
119+
if (queryFieldsFilter) {
120+
fields = queryFieldsFilter(fields, mode);
121+
}
122+
100123
return posts.reduce<{ [key: string]: PickedItemType | null }>((acc, item) => {
101-
const fields = ['link', 'type', 'id'];
102-
if (mode === 'user') {
103-
fields.push('name');
104-
} else if (mode === 'post') {
105-
fields.push('title');
106-
fields.push('url');
107-
fields.push('subtype');
108-
fields.push('status'); // Include status to check for trashed posts
109-
} else {
110-
fields.push('name');
111-
fields.push('taxonomy');
112-
}
113124
const getEntityRecordParameters = [
114125
entityKind,
115126
item.type,
@@ -121,31 +132,42 @@ const SortableList: React.FC<SortableListProps> = ({
121132
if (result) {
122133
let newItem: Partial<PickedItemType>;
123134

124-
if (mode === 'post') {
125-
const post = result as Post;
126-
newItem = {
127-
title: toPlainTextTitle(post.title.rendered),
128-
url: post.link,
129-
id: post.id,
130-
type: post.type,
131-
status: post.status, // Include status for trashed post detection
132-
};
133-
} else if (mode === 'user') {
134-
const user = result as User;
135-
newItem = {
136-
title: toPlainTextTitle(user.name),
137-
url: user.link,
138-
id: user.id,
139-
type: 'user',
140-
};
141-
} else {
142-
const taxonomy = result as Term;
143-
newItem = {
144-
title: toPlainTextTitle(taxonomy.name),
145-
url: taxonomy.link,
146-
id: taxonomy.id,
147-
type: taxonomy.taxonomy,
148-
};
135+
switch (mode) {
136+
case 'post': {
137+
const post = result as Post;
138+
newItem = {
139+
title: toPlainTextTitle(post.title.rendered),
140+
url: post.link,
141+
id: post.id,
142+
type: post.type,
143+
status: post.status, // Include status for trashed post detection
144+
};
145+
break;
146+
}
147+
case 'user': {
148+
const user = result as User;
149+
newItem = {
150+
title: toPlainTextTitle(user.name),
151+
url: user.link,
152+
id: user.id,
153+
type: 'user',
154+
};
155+
break;
156+
}
157+
default: {
158+
const taxonomy = result as Term;
159+
newItem = {
160+
title: toPlainTextTitle(taxonomy.name),
161+
url: taxonomy.link,
162+
id: taxonomy.id,
163+
type: taxonomy.taxonomy,
164+
};
165+
break;
166+
}
167+
}
168+
169+
if (pickedItemFilter) {
170+
newItem = pickedItemFilter(newItem, result);
149171
}
150172

151173
if (item.uuid) {
@@ -160,7 +182,7 @@ const SortableList: React.FC<SortableListProps> = ({
160182
return acc;
161183
}, {});
162184
},
163-
[posts, entityKind],
185+
[posts, entityKind, queryFieldsFilter, pickedItemFilter, mode],
164186
);
165187

166188
const items = posts.map((item) => item.uuid);

components/content-picker/index.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,27 @@ import { select } from '@wordpress/data';
33
import { useMemo } from '@wordpress/element';
44
import { __ } from '@wordpress/i18n';
55
import { VisuallyHidden } from '@wordpress/components';
6+
import { Post, User } from '@wordpress/core-data';
67
import { v4 as uuidv4 } from 'uuid';
78
import { ContentSearch } from '../content-search';
89
import SortableList from './SortableList';
910
import { StyledComponentContext } from '../styled-components-context';
1011
import { defaultRenderItemType } from '../content-search/SearchItem';
11-
import { ContentSearchMode, QueryFilter, RenderItemComponentProps } from '../content-search/types';
12+
import {
13+
ContentSearchMode,
14+
QueryFilter,
15+
QueryFieldsFilter,
16+
RenderItemComponentProps,
17+
SearchResultFilter,
18+
} from '../content-search/types';
1219
import { NormalizedSuggestion } from '../content-search/utils';
1320
import { PickedItemType } from './PickedItem';
21+
import { Term } from './types';
22+
23+
export type PickedItemFilter = (
24+
item: Partial<PickedItemType>,
25+
originalResult: Post | Term | User,
26+
) => Partial<PickedItemType>;
1427

1528
const NAMESPACE = 'tenup-content-picker';
1629

@@ -48,6 +61,9 @@ export interface ContentPickerProps {
4861
placeholder?: string;
4962
onPickChange?: (ids: any[]) => void;
5063
queryFilter?: QueryFilter;
64+
queryFieldsFilter?: QueryFieldsFilter;
65+
searchResultFilter?: SearchResultFilter;
66+
pickedItemFilter?: PickedItemFilter;
5167
maxContentItems?: number;
5268
isOrderable?: boolean;
5369
singlePickedLabel?: string;
@@ -73,6 +89,9 @@ export const ContentPicker: React.FC<ContentPickerProps> = ({
7389
console.log('Content picker list change', ids); // eslint-disable-line no-console
7490
},
7591
queryFilter = undefined,
92+
queryFieldsFilter,
93+
searchResultFilter,
94+
pickedItemFilter,
7695
maxContentItems = 1,
7796
isOrderable = false,
7897
singlePickedLabel = __('You have selected the following item:', '10up-block-components'),
@@ -152,6 +171,8 @@ export const ContentPicker: React.FC<ContentPickerProps> = ({
152171
contentTypes={contentTypes}
153172
mode={mode}
154173
queryFilter={queryFilter}
174+
queryFieldsFilter={queryFieldsFilter}
175+
searchResultFilter={searchResultFilter}
155176
perPage={perPage}
156177
fetchInitialResults={fetchInitialResults}
157178
renderItemType={renderItemType}
@@ -196,6 +217,8 @@ export const ContentPicker: React.FC<ContentPickerProps> = ({
196217
mode={mode}
197218
setPosts={onPickChange}
198219
PickedItemPreviewComponent={PickedItemPreviewComponent}
220+
queryFieldsFilter={queryFieldsFilter}
221+
pickedItemFilter={pickedItemFilter}
199222
/>
200223
</ul>
201224
</StyleWrapper>

0 commit comments

Comments
 (0)