@plaudit/gutenberg-api-extensions 2.95.0 → 2.97.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/blocks/basic-custom-block-bindings-support.js +1 -1
  3. package/dist/blocks/basic-custom-block-bindings-support.js.map +1 -1
  4. package/dist/blocks/conditions.js.map +1 -1
  5. package/dist/blocks/problematic-blocks-blocker.js.map +1 -1
  6. package/dist/blocks/simple-block.d.ts +4 -4
  7. package/dist/blocks/simple-block.js +2 -2
  8. package/dist/blocks/simple-block.js.map +1 -1
  9. package/dist/lib/useful-types.d.ts +6 -7
  10. package/package.json +13 -15
  11. package/dist/lib/compat-types.d.ts +0 -34
  12. package/dist/lib/compat-types.js +0 -3
  13. package/dist/lib/compat-types.js.map +0 -1
  14. package/src/blocks/MoveError.ts +0 -7
  15. package/src/blocks/PathError.ts +0 -18
  16. package/src/blocks/SNPFlexibleItemsListComponent.tsx +0 -30
  17. package/src/blocks/SNPGroupComponent.tsx +0 -38
  18. package/src/blocks/SNPListComponent.tsx +0 -25
  19. package/src/blocks/SNPTreeContext.tsx +0 -13
  20. package/src/blocks/basic-custom-block-bindings-support.tsx +0 -248
  21. package/src/blocks/common-native-property-constructors.tsx +0 -927
  22. package/src/blocks/conditions.ts +0 -261
  23. package/src/blocks/csnp-api.ts +0 -221
  24. package/src/blocks/data-controller/actions.ts +0 -20
  25. package/src/blocks/data-controller/reducer.ts +0 -146
  26. package/src/blocks/data-controller/trigger-handlers.ts +0 -150
  27. package/src/blocks/data-controller/utils.ts +0 -415
  28. package/src/blocks/data-controller-manager.ts +0 -50
  29. package/src/blocks/data-controller.ts +0 -165
  30. package/src/blocks/hooks/built-in-suspendable-option-protocols/select.ts +0 -51
  31. package/src/blocks/hooks/built-in-suspendable-option-protocols/settings.ts +0 -70
  32. package/src/blocks/hooks/useSuspendableOptions.ts +0 -122
  33. package/src/blocks/index.ts +0 -23
  34. package/src/blocks/layered-styles-api.ts +0 -142
  35. package/src/blocks/layered-styles-impl.ts +0 -95
  36. package/src/blocks/layout/LaidOutProperty.tsx +0 -72
  37. package/src/blocks/layout/LaidOutPropertyRow.tsx +0 -28
  38. package/src/blocks/layout/NodeContext.tsx +0 -54
  39. package/src/blocks/layout/PanelRoot.tsx +0 -30
  40. package/src/blocks/layout/TabsRoot.tsx +0 -56
  41. package/src/blocks/layout/ToolsPanelContext.tsx +0 -22
  42. package/src/blocks/problematic-blocks-blocker.ts +0 -24
  43. package/src/blocks/problematic-variations-blocker.ts +0 -32
  44. package/src/blocks/shared-exportable-types.ts +0 -6
  45. package/src/blocks/shared-internal-types.ts +0 -18
  46. package/src/blocks/simple-block.tsx +0 -74
  47. package/src/blocks/simple-native-property-api.ts +0 -173
  48. package/src/blocks/simple-native-property-impl.tsx +0 -335
  49. package/src/blocks/simple-native-property-internal-shared.ts +0 -19
  50. package/src/blocks/snp-api.ts +0 -5
  51. package/src/blocks/snp-data-store.ts +0 -72
  52. package/src/blocks/utilities.ts +0 -66
  53. package/src/controls/AsynchronousFormTokenField.tsx +0 -86
  54. package/src/controls/BaseSortableItemsControl.tsx +0 -84
  55. package/src/controls/ExtendedFormTokenField.tsx +0 -144
  56. package/src/controls/ExtendedPostPicker.ts +0 -57
  57. package/src/controls/ExtendedRadioControl.tsx +0 -107
  58. package/src/controls/ExtendedTaxonomyPicker.tsx +0 -100
  59. package/src/controls/ExtendedTermPicker.tsx +0 -61
  60. package/src/controls/ExtendedTextareaControl.tsx +0 -65
  61. package/src/controls/ExtendedUserPicker.ts +0 -56
  62. package/src/controls/FileControl.tsx +0 -48
  63. package/src/controls/FullSizeToggleControl.tsx +0 -95
  64. package/src/controls/ImageControl.tsx +0 -143
  65. package/src/controls/InspectorPanel.tsx +0 -37
  66. package/src/controls/LazySuggestionsComboboxControl.tsx +0 -64
  67. package/src/controls/MultiSelectControl.tsx +0 -59
  68. package/src/controls/PickOne.tsx +0 -88
  69. package/src/controls/PromisableComponent.tsx +0 -56
  70. package/src/controls/ProperLinkControl.tsx +0 -98
  71. package/src/controls/SimpleToggle.tsx +0 -9
  72. package/src/controls/SortableFlexibleItemsControl.tsx +0 -37
  73. package/src/controls/SortableItemsControl.tsx +0 -22
  74. package/src/controls/basicNumericallyIdedItemPicker.tsx +0 -75
  75. package/src/controls/hooks/useImprovedTokenManager.ts +0 -163
  76. package/src/controls/hooks/useMultiSingleConversionLayer.ts +0 -17
  77. package/src/controls/hooks/useNonRenderingCounter.ts +0 -6
  78. package/src/controls/hooks/useOutputMemoizingFilter.ts +0 -16
  79. package/src/controls/hooks/useSortableItemsModel.ts +0 -196
  80. package/src/controls/hooks/useSuggestions.ts +0 -91
  81. package/src/controls/hooks/useTokenManager.ts +0 -177
  82. package/src/controls/index.ts +0 -24
  83. package/src/controls/shared.ts +0 -50
  84. package/src/controls/types.ts +0 -18
  85. package/src/editor/insert-sibling-or-child-block-shortcut.tsx +0 -60
  86. package/src/editor/install-insert-sole-allowed-block-shortcut-support.tsx +0 -51
  87. package/src/editor/simple-gutenberg-endpoints-api.ts +0 -31
  88. package/src/editor/simple-gutenberg-endpoints-impl.ts +0 -126
  89. package/src/index.ts +0 -30
  90. package/src/lib/compat-types.ts +0 -21
  91. package/src/lib/gutenberg-api-extensions-state/custom-block-bindings-support-logic.ts +0 -35
  92. package/src/lib/gutenberg-api-extensions-state/general-logic.ts +0 -41
  93. package/src/lib/gutenberg-api-extensions-state/layered-block-styles-logic.ts +0 -43
  94. package/src/lib/gutenberg-api-extensions-state/snp-logic.ts +0 -240
  95. package/src/lib/gutenberg-api-extensions-state.ts +0 -69
  96. package/src/lib/helpers.ts +0 -115
  97. package/src/lib/modified-fast-deep-equals.ts +0 -91
  98. package/src/lib/plaudit-icons/column-1.tsx +0 -6
  99. package/src/lib/plaudit-icons/column-2.tsx +0 -6
  100. package/src/lib/plaudit-icons/column-3.tsx +0 -6
  101. package/src/lib/plaudit-icons/placement-center.tsx +0 -3
  102. package/src/lib/plaudit-icons/placement-end.tsx +0 -3
  103. package/src/lib/plaudit-icons/placement-start.tsx +0 -3
  104. package/src/lib/plaudit-icons/placement-stretch.tsx +0 -3
  105. package/src/lib/plaudit-icons/plaudit-icon.tsx +0 -4
  106. package/src/lib/plaudit-icons/reusable-block-marker.tsx +0 -3
  107. package/src/lib/plaudit-icons.ts +0 -13
  108. package/src/lib/sectioned-cache-store.ts +0 -120
  109. package/src/lib/suspense/promise-handlers.ts +0 -72
  110. package/src/lib/suspense.tsx +0 -18
  111. package/src/lib/useful-types.ts +0 -82
  112. package/src/schemas/README.md +0 -1
  113. package/src/schemas/plaudit-block-schema.json +0 -818
@@ -1,144 +0,0 @@
1
- import {Spinner, FormTokenField, Modal} from '@wordpress/components';
2
- import {useDebounce} from "@wordpress/compose";
3
- import {type MapSelect, useSelect} from "@wordpress/data";
4
- import {memo, useCallback, useEffect, useMemo, useRef, useState} from "@wordpress/element";
5
- import {__} from "@wordpress/i18n";
6
-
7
- import type {SNPControlSlots} from "../blocks";
8
- import {packDisplayTokenText, unpackDisplayedTokenText, useImprovedTokenManager, ValidationState} from "./hooks/useImprovedTokenManager";
9
- import {useMultiSingleConversionLayer} from "./hooks/useMultiSingleConversionLayer";
10
- import {potentialWpErrorToString} from "../lib/helpers";
11
- import {TokenItem} from "../lib/useful-types";
12
-
13
- export {packDisplayTokenText, unpackDisplayedTokenText, ValidationState};
14
-
15
- import type {ReactNode} from "react";
16
-
17
- type ExtendedFormTokenFieldPropsBase = {
18
- label?: string;
19
- help?: ReactNode;
20
-
21
- validationQuery(tokens: string[], ...args: Parameters<MapSelect>): TokenItem[]|undefined;
22
- suggestionQuery(input: string, ...args: Parameters<MapSelect>): TokenItem[]|undefined;
23
- stringToTokenConverter(suggestion: string): TokenItem;
24
- validator?: (value: string) => boolean;
25
- expandOnFocus?: boolean;
26
- initialSuggestions?: TokenItem[];
27
- hasLoadingError?: (input: string, ...args: Parameters<MapSelect>) => boolean;
28
- hasValidationError?: (...args: Parameters<MapSelect>) => boolean;
29
- }&Partial<Pick<SNPControlSlots, 'Messages'>>;
30
- type ExtendedFormTokenFieldPropsSingle = ExtendedFormTokenFieldPropsBase&{
31
- value?: string;
32
- onChange: (value: string) => void;
33
- multiple: false;
34
- };
35
- type ExtendedFormTokenFieldPropsMultiple = ExtendedFormTokenFieldPropsBase&{
36
- value?: string[];
37
- onChange: (value: string[]) => void;
38
- multiple?: true|undefined;
39
- maxLength?: number;
40
- };
41
- export type ExtendedFormTokenFieldProps = ExtendedFormTokenFieldPropsSingle|ExtendedFormTokenFieldPropsMultiple;
42
-
43
- const EMPTY_ARRAY: TokenItem[] = [];
44
- export function ExtendedFormTokenField(props: ExtendedFormTokenFieldProps) {
45
- const [debouncedInput, setDebouncedInput, setImmediateInput, input] = useDebouncedValue('');
46
- const [hasBeenFocused, setHasBeenFocused] = useState(false);
47
-
48
- const tokenTitleCacheRef = useRef<Map<TokenItem['value'], TokenItem['title']>|undefined>(undefined);
49
- if (tokenTitleCacheRef.current === undefined) {
50
- tokenTitleCacheRef.current = new Map();
51
- }
52
-
53
- const {rawSuggestions, isLoading, hasLoadingError, thrownLoadingError} = useSelect((select, registry) => {
54
- if (!debouncedInput && (!hasBeenFocused || props.initialSuggestions !== undefined)) {
55
- return {rawSuggestions: props.initialSuggestions ?? EMPTY_ARRAY, isLoading: false};
56
- }
57
- let res;
58
- try {
59
- res = props.suggestionQuery(debouncedInput, select, registry);
60
- } catch (e) {
61
- return {rawSuggestions: [], isLoading: false, hasLoadingError: true, thrownLoadingError: potentialWpErrorToString(e)};
62
- }
63
- if (res === undefined) {
64
- return {rawSuggestions: props.initialSuggestions ?? EMPTY_ARRAY, isLoading: true};
65
- }
66
-
67
- return {rawSuggestions: res, isLoading: false, hasLoadingError: props.hasLoadingError?.(debouncedInput, select, registry)};
68
- }, [debouncedInput, props.initialSuggestions, props.suggestionQuery, hasBeenFocused]);
69
- const suggestions = useMemo(() => rawSuggestions?.map(packDisplayTokenText), [rawSuggestions]);
70
-
71
- const {value, setValue} = useMultiSingleConversionLayer(props.value, props.onChange, props.stringToTokenConverter, props.multiple);
72
- const {currentTokens, isValidating, updateComponentValue, tokenStatusCache, thrownValidationError}
73
- = useImprovedTokenManager(value, setValue, props.validationQuery, props.stringToTokenConverter, tokenTitleCacheRef.current);
74
-
75
- // This is just a trick to avoid needing to send an additional validation request when adding a token from the suggestions list
76
- useMemo(() => {
77
- if (rawSuggestions) {
78
- for (const suggestion of rawSuggestions) {
79
- if (suggestion.status !== undefined && !tokenStatusCache.has(suggestion.value)) {
80
- tokenStatusCache.set(suggestion.value, suggestion.status);
81
- }
82
- }
83
- }
84
- }, [rawSuggestions, tokenStatusCache]);
85
-
86
- const {hasValidationError} = useSelect((select, registry) => ({
87
- hasValidationError: !!thrownValidationError || props.hasValidationError?.(select, registry)
88
- }), [props.hasLoadingError, props.hasValidationError, thrownValidationError]);
89
-
90
- const {help, expandOnFocus = false} = props;
91
-
92
- //TODO: If focus is in field but user hasn't started typing, show a message telling them to start typing
93
- return <>
94
- <FormTokenField
95
- value={currentTokens}
96
- label={props.label}
97
- placeholder="Start typing to see suggestions"
98
- suggestions={suggestions}
99
- onChange={updateComponentValue}
100
- displayTransform={useCallback((token: string) => tokenTitleCacheRef.current!.get(unpackDisplayedTokenText(token).value) ?? token, [tokenTitleCacheRef])}
101
- maxLength={props.multiple !== false ? props.maxLength : 1}
102
- __experimentalValidateInput={props.validator}
103
- __experimentalAutoSelectFirstMatch={true}
104
- __experimentalExpandOnFocus={expandOnFocus}
105
- __experimentalShowHowTo={props.multiple !== false}
106
- onInputChange={setDebouncedInput}
107
- onFocus={useCallback(() => {
108
- setHasBeenFocused(true);
109
- setImmediateInput(input);
110
- }, [input])}
111
- __next40pxDefaultSize
112
- __nextHasNoMarginBottom
113
- />
114
- {help && <div><span className="components-form-token-field__help">{help}</span></div>}
115
- {hasLoadingError && <ErrorSummaryWithModal summary={<span className="components-form-token-field__help">{__("An Error Occurred While Loading Suggestions")}</span>} detail={thrownLoadingError} />}
116
- {isLoading && <div><Spinner /><span className="components-form-token-field__help">{__("Updating Suggestions")}</span></div>}
117
- {hasValidationError && <ErrorSummaryWithModal summary={<span className="components-form-token-field__help">{__("An Error Occurred While Validating")}</span>} detail={thrownValidationError} />}
118
- {isValidating && <div><Spinner /><span className="components-form-token-field__help">{__("Validating")}</span></div>}
119
- </>;
120
- }
121
-
122
- const ErrorSummaryWithModal = memo(({summary, detail}: {summary: ReactNode, detail: ReactNode}) => {
123
- const [isOpen, setOpen] = useState(false);
124
- const openModal = useCallback(() => setOpen(true), [setOpen]);
125
- const closeModal = () => setOpen(false);
126
-
127
- return <>
128
- <div onClick={openModal} children={summary} />
129
- {isOpen && <Modal title="Error Details" onRequestClose={closeModal} children={<pre>{detail}</pre>} />}
130
- </>;
131
- });
132
-
133
- function useDebouncedValue<V>(defaultValue: V): [V, (value: V) => any, (value: V) => any, V] {
134
- const [value, setValue] = useState(defaultValue);
135
- const [debouncedValue, setDebouncedState] = useState(defaultValue);
136
- const setDebounced = useDebounce(setDebouncedState, 100);
137
- useEffect(() => {
138
- setDebounced(value);
139
- }, [value, setDebounced]);
140
- return [debouncedValue, setValue, useCallback((value: V) => {
141
- setValue(value);
142
- setDebouncedState(value);
143
- }, [setValue, setDebounced]), value];
144
- }
@@ -1,57 +0,0 @@
1
- import apiFetch from "@wordpress/api-fetch";
2
- import {memo, useMemo} from "@wordpress/element";
3
-
4
- import {basicNumericallyIdedItemPicker} from "./basicNumericallyIdedItemPicker";
5
- import type {SNPControlSlots} from "../blocks";
6
- import {ValidationState} from "./ExtendedFormTokenField";
7
- import {registerSimpleGutenbergApiEndpoint} from "../editor/simple-gutenberg-endpoints-api";
8
- import type {TokenItem, WPTaxonomyQuery} from "../lib/useful-types";
9
-
10
- import type {ReactNode} from "react";
11
-
12
- type ExtendedPostPickerPropsBase = {
13
- label?: string;
14
- help?: ReactNode;
15
- postTypes?: string[];
16
- placeholder?: string;
17
- taxonomyQuery?: WPTaxonomyQuery;
18
- }&Partial<Pick<SNPControlSlots, 'Messages'>>;
19
- type ExtendedPostPickerPropsSingle = ExtendedPostPickerPropsBase&{
20
- onChange(value: string): void;
21
- value?: string;
22
- multiple: false;
23
- };
24
- type ExtendedPostPickerPropsMultiple = ExtendedPostPickerPropsBase&{
25
- onChange(value: string[]): void;
26
- value?: string[];
27
- multiple?: true|undefined;
28
- };
29
- export type ExtendedPostPickerProps = ExtendedPostPickerPropsSingle|ExtendedPostPickerPropsMultiple;
30
- export type ExtendedPostPickerConstructorProps = ExtendedPostPickerProps;
31
-
32
- registerSimpleGutenbergApiEndpoint(
33
- "plaudit-common.post-table-search-options",
34
- data => apiFetch<Array<{id: number, title: string, type: string}>>({data, method: 'POST', path: "/plaudit/common/v1/post-table-search"}),
35
- {
36
- transformer: posts => posts.map(post => ({value: post.id.toString(), title: post.title, status: ValidationState.Valid})),
37
- maxCachedResults: 10
38
- }
39
- );
40
- registerSimpleGutenbergApiEndpoint(
41
- "plaudit-common.post-table-search-validation",
42
- data => apiFetch<Array<{id: number, title: string, type: string}>>({data, method: 'POST', path: "/plaudit/common/v1/post-table-search"}),
43
- {
44
- transformer: posts => posts
45
- .map((post): TokenItem => ({value: post.id.toString(), title: post.title, status: ValidationState.Valid})),
46
- maxCachedResults: 10
47
- }
48
- );
49
-
50
- export const ExtendedPostPicker = memo((props: ExtendedPostPickerProps) => {
51
- const {postTypes, taxonomyQuery, ...passthrough} = props;
52
-
53
- const queryArgs = useMemo(() => {
54
- return {postTypes: postTypes?.join(','), taxonomyQuery};
55
- }, [postTypes, taxonomyQuery]);
56
- return basicNumericallyIdedItemPicker(passthrough, "plaudit-common.post-table-search", queryArgs);
57
- });
@@ -1,107 +0,0 @@
1
- import {BaseControl, RadioControl, __experimentalVStack as VStack} from "@wordpress/components";
2
- import {useInstanceId} from "@wordpress/compose";
3
- import {useCallback, useState} from "@wordpress/element";
4
-
5
- import type {SNPControlSlots} from "../blocks";
6
-
7
- import type {ChangeEvent, ComponentPropsWithoutRef, ElementType} from "react";
8
-
9
- type RawElementSafeProps<P, T extends ElementType> = P&Omit<ComponentPropsWithoutRef<T>, 'as' | keyof P | 'children'>;
10
- export type ExtendedRadioControlProps = RawElementSafeProps<ComponentPropsWithoutRef<typeof RadioControl> & { allowCustom?: boolean }, 'input'>&Partial<SNPControlSlots>;
11
-
12
- /**
13
- * This is a variant of WordPress'
14
- */
15
- export function ExtendedRadioControl(props: ExtendedRadioControlProps) {
16
- const {
17
- label,
18
- className,
19
- selected,
20
- help,
21
- onChange,
22
- hideLabelFromVision,
23
- options = [],
24
- allowCustom,
25
- Label, Messages,
26
- ...additionalProps
27
- } = props;
28
- const instanceId = useInstanceId( ExtendedRadioControl );
29
- const id = `inspector-radio-control-${instanceId}`;
30
- const [valueIsCustom, setValueIsCustom] = useState(
31
- () => options.find(option => option.value === selected) === undefined);
32
- const [customValue, setCustomValue] = useState(valueIsCustom ? selected : "");
33
-
34
- const onChangeValue = useCallback((event: ChangeEvent<HTMLInputElement>) => {
35
- setValueIsCustom(options.find(option => option.value === event.target.value) === undefined);
36
- onChange(event.target.value);
37
- }, [onChange, options, setValueIsCustom]);
38
- const onChangeCustom = useCallback((e: ChangeEvent<HTMLInputElement>) => {
39
- onChangeValue(e);
40
- setValueIsCustom(true);
41
- setCustomValue(e.target.value);
42
- }, [onChangeValue, setValueIsCustom, setCustomValue]);
43
-
44
- if (!options?.length) {
45
- return null;
46
- }
47
-
48
- const distinctnessSet = new Set<string>();
49
- const classNames = [...className?.split(/\s+/g) ?? [], 'components-radio-control'].filter(cn => distinctnessSet.add(cn)).join(' ');
50
-
51
- return (
52
- <BaseControl
53
- __nextHasNoMarginBottom
54
- label={Label ? <Label /> : label}
55
- id={id}
56
- hideLabelFromVision={hideLabelFromVision}
57
- help={help}
58
- className={classNames}
59
- >
60
- <VStack spacing={1}>
61
- {options.map((option, index) => (
62
- <div
63
- key={`${id}-${index}`}
64
- className="components-radio-control__option"
65
- >
66
- <input
67
- id={`${id}-${index}`}
68
- className="components-radio-control__input"
69
- type="radio"
70
- name={id}
71
- value={option.value}
72
- onChange={onChangeValue}
73
- checked={option.value === selected}
74
- aria-describedby={help ? `${id}__help` : undefined}
75
- {...additionalProps}
76
- />
77
- <label
78
- className="components-radio-control__label"
79
- htmlFor={`${id}-${index}`}
80
- >
81
- {option.label}
82
- </label>
83
- </div>
84
- ))}
85
- {allowCustom && <div
86
- key={`${id}-${options.length}`}
87
- className="components-radio-control__option"
88
- >
89
- <input
90
- id={`${id}-${options.length}`}
91
- className="components-radio-control__input"
92
- type="radio"
93
- name={id}
94
- value={customValue ?? ''}
95
- onChange={onChangeValue}
96
- checked={valueIsCustom}
97
- aria-describedby={help ? `${id}__help` : undefined}
98
- {...additionalProps}
99
- />
100
- <label id={`${id}-custom-label`} className="components-radio-control__label" htmlFor={`${id}-${options.length}`}>Custom:</label>
101
- <input aria-labelledby={`${id}-custom-label`} type="text" value={customValue ?? ''} onChange={onChangeCustom} />
102
- </div>}
103
- </VStack>
104
- {Messages && <Messages/>}
105
- </BaseControl>
106
- );
107
- }
@@ -1,100 +0,0 @@
1
- import {type BaseEntityRecords, store as coreStore, type Taxonomy} from "@wordpress/core-data";
2
- import {useSelect} from "@wordpress/data";
3
- import {useCallback, useMemo} from "@wordpress/element";
4
-
5
- import type {SNPControlSlots} from "../blocks";
6
- import type {TokenItem} from "../lib/useful-types";
7
- import {useOutputMemoizingFilter} from "./hooks/useOutputMemoizingFilter";
8
- import {ExtendedFormTokenField, unpackDisplayedTokenText, ValidationState} from "./ExtendedFormTokenField";
9
-
10
- import type {ReactNode} from "react";
11
-
12
- type ExtendedTaxonomyPickerPropsBase = {
13
- label?: string;
14
- help?: ReactNode;
15
- placeholder?: string;
16
- visibility?: Partial<BaseEntityRecords.TaxonomyVisibility>
17
- }&Partial<Pick<SNPControlSlots, 'Messages'>>;
18
- export type ExtendedTaxonomyPickerPropsSingle = {
19
- onChange(value: string): void;
20
- value?: string;
21
- multiple: false;
22
- };
23
- export type ExtendedTaxonomyPickerPropsMultiple = {
24
- onChange(value: string[]): void;
25
- value?: string[];
26
- multiple?: true|undefined;
27
- };
28
- export type ExtendedTaxonomyPickerProps = ExtendedTaxonomyPickerPropsBase&(ExtendedTaxonomyPickerPropsSingle|ExtendedTaxonomyPickerPropsMultiple);
29
-
30
- export function ExtendedTaxonomyPicker(props: ExtendedTaxonomyPickerProps) {
31
- const {visibility, ...extendedFormTokenFieldProps} = props;
32
- const availableTaxonomies = useTaxonomiesFilteredByVisibility(visibility);
33
- const taxonomyVisibilityFilter = makeTaxonomyVisibilityFilter(visibility);
34
-
35
- const taxonomySuggestionQuery = useOutputMemoizingFilter((input: string, taxonomies: Taxonomy[]|null) => {
36
- return taxonomies?.filter(taxonomy => !props.value?.includes(taxonomy.slug))
37
- .filter(taxonomy => taxonomy.name.toLowerCase().includes(input.toLowerCase()) || taxonomy.slug.toLowerCase().includes(input.toLowerCase()))
38
- .map(taxonomy => ({value: taxonomy.slug, status: ValidationState.Valid, title: taxonomy.name})) ?? [];
39
- }, [props.value]);
40
- const taxonomyValidationQuery = useOutputMemoizingFilter((tokens: string[], rawTaxonomies: Taxonomy[]|null) => {
41
- if (rawTaxonomies === null) {
42
- return tokens.map(token => ({value: token, status: ValidationState.Validating}));
43
- }
44
- const tokenItemCreator = (taxonomy: Taxonomy): [string, TokenItem] => {
45
- return [taxonomy.slug, {value: taxonomy.slug, status: ValidationState.Valid, title: taxonomy.name}];
46
- };
47
- const taxonomies = rawTaxonomies?.length ? Object.fromEntries(rawTaxonomies.map(tokenItemCreator)) : undefined;
48
- if (!taxonomies) {
49
- return tokens.map(token => ({value: token, status: ValidationState.Invalid}));
50
- }
51
- return tokens.map(token => taxonomies[token] ?? {value: token, status: ValidationState.Invalid});
52
- });
53
-
54
- return <ExtendedFormTokenField
55
- expandOnFocus={true}
56
- {...extendedFormTokenFieldProps}
57
- stringToTokenConverter={useCallback(token => {
58
- if (availableTaxonomies === null) {
59
- return {value: token, status: ValidationState.Validating};
60
- }
61
- const {value, title} = unpackDisplayedTokenText(token);
62
- const foundTaxonomy = availableTaxonomies.find(taxonomy => taxonomy.slug === value);
63
- if (foundTaxonomy) {
64
- return {value: foundTaxonomy.slug, status: ValidationState.Valid, title: foundTaxonomy.name};
65
- }
66
- return title === undefined ? {value, status: ValidationState.Validating} : {value, status: ValidationState.Invalid, title};
67
- }, [availableTaxonomies])}
68
- suggestionQuery={useCallback((input, select) => {
69
- return taxonomySuggestionQuery(input, taxonomyVisibilityFilter(select(coreStore).getTaxonomies()));
70
- }, [taxonomyVisibilityFilter, taxonomySuggestionQuery])}
71
- validationQuery={useCallback((tokens, select) => {
72
- return taxonomyValidationQuery(tokens, taxonomyVisibilityFilter(select(coreStore).getTaxonomies()));
73
- }, [taxonomyVisibilityFilter, taxonomyValidationQuery])}
74
- validator={useCallback((token: string) => {
75
- if (!availableTaxonomies) {
76
- return false;
77
- }
78
- const {value} = unpackDisplayedTokenText(token);
79
- return availableTaxonomies.find(taxonomy => taxonomy.slug === value) !== undefined;
80
- }, [availableTaxonomies])}
81
- />;
82
- }
83
-
84
- function useTaxonomiesFilteredByVisibility(visibility: ExtendedTaxonomyPickerProps['visibility']) {
85
- const taxonomies = useSelect(select => select(coreStore).getTaxonomies(), []);
86
- return useMemo(() => filterTaxonomiesByVisibility(taxonomies, visibility), [taxonomies, visibility]);
87
- }
88
- function makeTaxonomyVisibilityFilter(visibility: Parameters<typeof filterTaxonomiesByVisibility>[1]) {
89
- return useOutputMemoizingFilter((taxonomies: Parameters<typeof filterTaxonomiesByVisibility>[0]) => filterTaxonomiesByVisibility(taxonomies, visibility), [visibility]);
90
- }
91
- function filterTaxonomiesByVisibility(taxonomies: Taxonomy[]|null, visibility: ExtendedTaxonomyPickerProps['visibility']) {
92
- if (!taxonomies || !visibility) {
93
- return taxonomies;
94
- }
95
- const tests = Object.entries(visibility)
96
- .filter((entry): entry is [keyof BaseEntityRecords.TaxonomyVisibility, boolean] => entry[1] !== undefined && entry[1] !== null);
97
- return tests.length
98
- ? taxonomies.filter(taxonomy => tests.every(([key, value]) => taxonomy.visibility[key] === value))
99
- : taxonomies;
100
- }
@@ -1,61 +0,0 @@
1
- import apiFetch from "@wordpress/api-fetch";
2
- import {select as dataSelect} from "@wordpress/data";
3
- import {useCallback} from "@wordpress/element";
4
-
5
- import type {SNPControlSlots} from "../blocks";
6
- import {ExtendedFormTokenField, unpackDisplayedTokenText, ValidationState} from "./ExtendedFormTokenField";
7
- import {registerSimpleGutenbergApiEndpoint} from "../editor/simple-gutenberg-endpoints-api";
8
- import {store as endpointsStore} from "../editor/simple-gutenberg-endpoints-impl";
9
- import type {TokenItem} from "../lib/useful-types";
10
-
11
- import type {ReactNode} from "react";
12
-
13
- type ExtendedTermPickerPropsBase = {
14
- label?: string;
15
- help?: ReactNode;
16
- taxonomy: string;
17
- placeholder?: string;
18
- }&Partial<Pick<SNPControlSlots, 'Messages'>>;
19
- export type ExtendedTermPickerPropsSingle = ExtendedTermPickerPropsBase&{
20
- onChange(value: string): void;
21
- value?: string;
22
- multiple: false;
23
- };
24
- export type ExtendedTermPickerPropsMultiple = ExtendedTermPickerPropsBase&{
25
- onChange(value: string[]): void;
26
- value?: string[];
27
- multiple?: true|undefined;
28
- };
29
- export type ExtendedTermPickerProps = ExtendedTermPickerPropsSingle|ExtendedTermPickerPropsMultiple;
30
-
31
- export type TermDataStruct = {term_id: number, name: string, slug: number, term_taxonomy_id: number, taxonomy: string};
32
- registerSimpleGutenbergApiEndpoint(
33
- "plaudit-common.term-table-search-options",
34
- data => apiFetch<TermDataStruct[]>({data, method: 'POST', path: "/plaudit/common/v1/term-table-search"}),
35
- {
36
- transformer: terms => terms.map(term => ({value: term.slug, title: term.name, status: ValidationState.Valid})),
37
- maxCachedResults: 10
38
- }
39
- );
40
- registerSimpleGutenbergApiEndpoint(
41
- "plaudit-common.term-table-search-validation",
42
- data => apiFetch<TermDataStruct[]>({data, method: 'POST', path: "/plaudit/common/v1/term-table-search"}),
43
- {
44
- transformer: terms => terms
45
- .map((term) => ({value: term.slug, title: term.name, status: ValidationState.Valid})),
46
- maxCachedResults: 10
47
- }
48
- );
49
-
50
- export function ExtendedTermPicker(props: ExtendedTermPickerProps) {
51
- const {taxonomy} = props;
52
-
53
- const suggestionQuery = useCallback((input: string, select: typeof dataSelect) => {
54
- return select(endpointsStore).get("plaudit-common.term-table-search-options", {search: input, taxonomy}) as TokenItem[]|undefined;
55
- }, [taxonomy]);
56
- const validationQuery = useCallback((slugsBeingValidated: string[], select: typeof dataSelect) => {
57
- return select(endpointsStore).get("plaudit-common.term-table-search-validation", {slugs: slugsBeingValidated.join(','), taxonomy}) as TokenItem[]|undefined;
58
- }, [taxonomy]);
59
-
60
- return <ExtendedFormTokenField {...props} validationQuery={validationQuery} suggestionQuery={suggestionQuery} stringToTokenConverter={unpackDisplayedTokenText} />;
61
- }
@@ -1,65 +0,0 @@
1
- import {TextareaControl} from "@wordpress/components";
2
- import {useDebounce} from "@wordpress/compose";
3
-
4
- import {forwardRef, useCallback, useEffect, useImperativeHandle, useRef} from "@wordpress/element";
5
-
6
- import type {SNPControlSlots} from "../blocks";
7
-
8
- import type {ComponentProps} from "react";
9
-
10
- export type TextareaControlPropsExtension = {newline?: undefined|"\n"|"br"|"p", placeholder?: string|undefined, rich?: boolean};
11
- export type ExtendedTextAreaControlProps = Omit<ComponentProps<typeof TextareaControl>, 'value'>&{value: ComponentProps<typeof TextareaControl>['value']|null|undefined}
12
- &Partial<Pick<SNPControlSlots, 'Messages'>>&TextareaControlPropsExtension;
13
- export const ExtendedTextareaControl = (() => {
14
- const debouncerSettings = {leading: false, maxWait: 250, trailing: true};
15
- return forwardRef<HTMLTextAreaElement, ExtendedTextAreaControlProps>((props, ref) => {
16
- const {newline, onChange, placeholder, rich, value, Messages, ...forwardedProps} = props;
17
- let v = value ?? '';
18
- if (!rich) {
19
- if (newline === "br") {
20
- v = v.replaceAll(/<br>\s*<\/br>|<br\/?>/gi, "\n");
21
- } else if (newline === "p") {
22
- v = v.replaceAll(/<p>(.*?)<\/p>/gi, "$1\n");
23
- }
24
- }
25
- const safeOnChange = useCallback((v: string) => {
26
- if (rich) {
27
- onChange(v);
28
- } else if (newline === "br") {
29
- onChange(v.replaceAll(/\r?\n/g, "<br/>"));
30
- } else if (newline === "p") {
31
- onChange(v.split(/\r?\n/g).map(v => "<p>" + v + "</p>").join(""));
32
- } else {
33
- onChange(v);
34
- }
35
- }, [onChange]);
36
-
37
- const textareaRef = useRef<HTMLTextAreaElement|null>(null);
38
- useImperativeHandle(ref, () => textareaRef.current!, []);
39
-
40
- const debouncedUpdate = useDebounce(safeOnChange, 100, debouncerSettings);
41
-
42
- useEffect(() => {
43
- const id = textareaRef.current?.id;
44
- if (id && rich && document.getElementById(id)) {
45
- (window as any).tinymce.EditorManager.execCommand('mceAddEditor', true, id);
46
- const editor = (window as any).tinymce.editors.findLast((e: {id: string}) => e.id === id);
47
- editor.on('input', () => debouncedUpdate(editor.getContent()));
48
- editor.on('NodeChange', () => debouncedUpdate(editor.getContent()));
49
- editor.on('change', () => {
50
- debouncedUpdate.cancel();
51
- safeOnChange(editor.getContent());
52
- });
53
- return () => {
54
- (window as any).tinymce.EditorManager.execCommand('mceRemoveEditor', true, id);
55
- };
56
- }
57
- return () => {};
58
- }, [rich, debouncedUpdate, textareaRef.current]);
59
-
60
- return <>
61
- <TextareaControl {...forwardedProps} ref={textareaRef} value={v} onChange={safeOnChange} __nextHasNoMarginBottom />
62
- {Messages && <Messages/>}
63
- </>;
64
- })
65
- })();
@@ -1,56 +0,0 @@
1
- import apiFetch from "@wordpress/api-fetch";
2
- import {memo, useMemo} from "@wordpress/element";
3
-
4
- import {basicNumericallyIdedItemPicker} from "./basicNumericallyIdedItemPicker";
5
- import type {SNPControlSlots} from "../blocks";
6
- import {ValidationState} from "./ExtendedFormTokenField";
7
- import {registerSimpleGutenbergApiEndpoint} from "../editor/simple-gutenberg-endpoints-api";
8
- import type {TokenItem} from "../lib/useful-types";
9
-
10
- import type {ReactNode} from "react";
11
-
12
- type ExtendedUserPickerPropsBase = {
13
- label?: string;
14
- help?: ReactNode;
15
- userRoles?: string[];
16
- placeholder?: string;
17
- }&Partial<Pick<SNPControlSlots, 'Messages'>>;
18
- type ExtendedUserPickerPropsSingle = ExtendedUserPickerPropsBase&{
19
- onChange(value: string): void;
20
- value?: string;
21
- multiple: false;
22
- };
23
- type ExtendedUserPickerPropsMultiple = ExtendedUserPickerPropsBase&{
24
- onChange(value: string[]): void;
25
- value?: string[];
26
- multiple?: true|undefined;
27
- };
28
- export type ExtendedUserPickerProps = ExtendedUserPickerPropsSingle|ExtendedUserPickerPropsMultiple;
29
-
30
- registerSimpleGutenbergApiEndpoint(
31
- "plaudit-common.user-table-search-options",
32
- data => apiFetch<Array<{id: number, name: string, roles?: string[]}>>({data, method: 'POST', path: "/plaudit/common/v1/user-table-search"}),
33
- {
34
- transformer: users => users.map(user => ({value: user.id.toString(), title: user.name, status: ValidationState.Valid})),
35
- maxCachedResults: 10
36
- }
37
- );
38
- registerSimpleGutenbergApiEndpoint(
39
- "plaudit-common.user-table-search-validation",
40
- data => apiFetch<Array<{id: number, name: string, roles?: string[]}>>({data, method: 'POST', path: "/plaudit/common/v1/user-table-search"}),
41
- {
42
- transformer: user => user
43
- .map((user): TokenItem => ({value: user.id.toString(), title: user.name, status: ValidationState.Valid})),
44
- maxCachedResults: 10
45
- }
46
- );
47
-
48
- export const ExtendedUserPicker = memo((props: ExtendedUserPickerProps) => {
49
- const {userRoles, ...passthrough} = props;
50
-
51
- const queryArgs = useMemo(() => {
52
- return {roles: userRoles?.join(",")};
53
- }, [userRoles]);
54
-
55
- return basicNumericallyIdedItemPicker(passthrough, "plaudit-common.user-table-search", queryArgs);
56
- })
@@ -1,48 +0,0 @@
1
- import {MediaUpload, MediaUploadCheck} from "@wordpress/block-editor";
2
- import {BaseControl, Button, useBaseControlProps} from "@wordpress/components";
3
- import {store as coreStore} from "@wordpress/core-data";
4
- import {useSuspenseSelect} from "@wordpress/data";
5
- import {useCallback} from "@wordpress/element";
6
- import {__} from "@wordpress/i18n";
7
-
8
- import type {ReactNode} from "react";
9
-
10
- export type FileControlProps = {
11
- label: ReactNode,
12
- help?: ReactNode,
13
- onChange: (value?: number) => void,
14
- value?: number,
15
- allowedTypes?: string[]
16
- };
17
- export function FileControl(props: FileControlProps) {
18
- const {allowedTypes, help, label, onChange, value} = props;
19
- const onSelect = useCallback((media: {id: number}) => {
20
- if (media.id !== value) {
21
- onChange(media.id);
22
- }
23
- }, [onChange, value]);
24
-
25
- const attachment = useSuspenseSelect(select => value !== undefined ? select(coreStore).getMedia(value) : undefined, [value]);
26
- const {baseControlProps, controlProps} = useBaseControlProps({label, help});
27
- return <BaseControl {...baseControlProps} __nextHasNoMarginBottom>
28
- <MediaUploadCheck>
29
- <MediaUpload
30
- onSelect={onSelect}
31
- value={value}
32
- allowedTypes={allowedTypes}
33
- render={useCallback(({open}) => {
34
- if (!attachment?.id) {
35
- return <div {...controlProps}><Button onClick={open} children={__('Select a File', 'plaudit')} __next40pxDefaultSize /></div>
36
- }
37
- return <div {...controlProps}>
38
- <p>{__('Selected File', 'plaudit')}: <a href={attachment.source_url} target="_blank" rel="noopener noreferrer">{attachment.title.rendered}</a></p>
39
- <div className="selected-file-control-buttons">
40
- <Button onClick={open} variant="secondary" children="Replace File" __next40pxDefaultSize />
41
- <Button onClick={() => onChange(undefined)} isDestructive children={__('Remove File', 'plaudit')} __next40pxDefaultSize />
42
- </div>
43
- </div>;
44
- }, [attachment, onChange, controlProps])}
45
- />
46
- </MediaUploadCheck>
47
- </BaseControl>;
48
- }