@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,177 +0,0 @@
1
- import {useEffect, useState} from "@wordpress/element";
2
-
3
- import {ValidationState} from "../AsynchronousFormTokenField";
4
- import {isNumeric} from "../shared";
5
- import type {Dict} from "../types";
6
- import type {TokenItem} from "../../lib/useful-types";
7
- import {useNonRenderingCounter} from "./useNonRenderingCounter";
8
-
9
- import {useReducer} from "react";
10
-
11
- type TokenManagerReturnType = {
12
- currentTokens: TokenItem[];
13
- dispatchValueUpdate: (value: (((prevState: (string|TokenItem)[]) => (string|TokenItem)[])|(string|TokenItem)[])) => void;
14
- hasValidationError: boolean;
15
- isInitializing: boolean;
16
- isValidating: boolean;
17
- tokenTitleMappings: Dict<string>
18
- };
19
-
20
- export function useTokenManager(
21
- initialValue: string[], setComponentValue: (value: TokenItem[]) => void, validationQuery: (tokens: string[]) => Promise<TokenItem[]>,
22
- stringToTokenConverter: (value: string) => TokenItem
23
- ): TokenManagerReturnType {
24
- const [validationRequestId, nextValidationRequestId] = useNonRenderingCounter(initialValue.length ? 0 : 1);
25
- const [isValidating, setIsValidating] = useState(false);
26
- const [hasValidationError, setHasValidationError] = useState(false);
27
-
28
- const [latestResolvedRequest, dispatchLatestResolvedRequestUpdate] = useReducer<(prev: number, next: number) => number>(Math.max, validationRequestId.current);
29
- const [validatedTokens, dispatchValidatedTokensUpdate] = useReducer(performValidationStateDictReductionUpdate, {});
30
-
31
- const [currentValue, setCurrentValue] = useState<(string|TokenItem)[]>(initialValue);
32
- const [currentTokens, updateCurrentTokens] = useReducer(currentTokensStateReducer, [],
33
- (): TokenItem[] => initialValue.map(value => ({value, status: ValidationState.Validating})));
34
- const [tokenTitleMappings, updateTokenTitleMappings] = useReducer(tokenTitleMappingsUpdater, {});
35
-
36
- useEffect(() => {
37
- const tokensInNeedOfValidation = currentTokens
38
- .filter(token => token.status === ValidationState.Validating)
39
- .filter(token => !(token.value in validatedTokens))
40
- .map(token => token.value);
41
-
42
- if (tokensInNeedOfValidation.length > 0) {
43
- dispatchValidatedTokensUpdate({data: Object.fromEntries(tokensInNeedOfValidation.map(token => [token, ValidationState.Waiting]))});
44
- }
45
- }, [currentTokens]);
46
-
47
- // I'm splitting out the useEffect call here in order to have one-to-one correspondence between effects and types of state updates
48
- useEffect(() => {
49
- const tokensInNeedOfValidation = Object.entries(validatedTokens)
50
- .filter(([_,v]) => v === ValidationState.Waiting)
51
- .map(([token]) => token);
52
- if (tokensInNeedOfValidation.length === 0) {
53
- return;
54
- }
55
-
56
- const myValidationRequestId = nextValidationRequestId();
57
- setIsValidating(true);
58
- setHasValidationError(false);
59
- dispatchValidatedTokensUpdate({data: Object.fromEntries(tokensInNeedOfValidation.map(token => [token, ValidationState.Validating]))});
60
-
61
- let timeoutId: ReturnType<typeof setTimeout>|undefined = setTimeout(() => {
62
- timeoutId = undefined;
63
- dispatchValidatedTokensUpdate({type: 'rollback', data: Object.fromEntries(tokensInNeedOfValidation.map(token => [token, ValidationState.Waiting]))});
64
- }, 60_000);
65
- validationQuery(tokensInNeedOfValidation)
66
- .then(tokens => {
67
- if (timeoutId === undefined) {
68
- throw "Timeout";
69
- }
70
- dispatchValidatedTokensUpdate({
71
- data: Object.fromEntries(tokens.map(token => [token.value, token.status === ValidationState.Valid ? ValidationState.Valid : ValidationState.Invalid]))
72
- });
73
- updateTokenTitleMappings(tokens);
74
- })
75
- .catch(err => {
76
- console.error("Validation Error:", err);
77
- setHasValidationError(true);
78
- // We retry the validation after 30 seconds
79
- setTimeout(() => dispatchValidatedTokensUpdate({
80
- type: 'rollback',
81
- data: Object.fromEntries(tokensInNeedOfValidation.map(token => [token, ValidationState.Waiting]))
82
- }), 30_000);
83
- })
84
- .finally(() => {
85
- clearTimeout(timeoutId);
86
- dispatchLatestResolvedRequestUpdate(myValidationRequestId);
87
- });
88
- }, [validatedTokens]);
89
-
90
- useEffect(() => {
91
- updateCurrentTokens(currentValue.map<TokenItem>(token => {
92
- if (typeof token === 'string') {
93
- token = stringToTokenConverter(token);
94
- if (token.status === undefined || token.status === ValidationState.Validating) {
95
- const status = validatedTokens[token.value];
96
- if (status !== undefined && status !== ValidationState.Waiting) {
97
- token.status = status;
98
- }
99
- }
100
- }
101
- return token.title === undefined && token.value in tokenTitleMappings ? {...token, title: tokenTitleMappings[token.value]} : token;
102
- }));
103
- }, [currentValue]);
104
-
105
- // This is split out into a useEffect call in order to take advantage of the Reducer's excess-update-reduction logic.
106
- useEffect(() => {
107
- updateTokenTitleMappings(currentTokens);
108
- if (latestResolvedRequest > (initialValue.length ? 0 : 1)) {
109
- setComponentValue(currentTokens);
110
- }
111
- }, [currentTokens]);
112
- useEffect(() => {
113
- if (currentTokens.some(token => token.value in tokenTitleMappings && tokenTitleMappings[token.value] !== token.title)) {
114
- updateCurrentTokens(currentTokens.map(token => token.title === undefined && token.value in tokenTitleMappings
115
- ? {...token, title: tokenTitleMappings[token.value]} : token));
116
- }
117
- }, [tokenTitleMappings]);
118
-
119
- useEffect(() => {
120
- const potentiallyUpdatableTokens = currentTokens.map((token): [TokenItem, ValidationState|undefined] => [token, validatedTokens[token.value]]);
121
- // A token is worth updating if its status is undefined or validating and its new status is defined and neither waiting nor validating
122
- const hasTokenWorthUpdating = potentiallyUpdatableTokens.some(([token, nvs]) => {
123
- return (token.status === undefined || token.status === ValidationState.Validating)
124
- && nvs !== undefined && nvs !== ValidationState.Validating && nvs !== ValidationState.Waiting;
125
- });
126
- if (hasTokenWorthUpdating) {
127
- updateCurrentTokens(potentiallyUpdatableTokens.map(([token, status]) => {
128
- return !status || status === ValidationState.Waiting ? token : ({...token, status});
129
- }));
130
- }
131
- setIsValidating(latestResolvedRequest < validationRequestId.current);
132
- }, [latestResolvedRequest]);
133
-
134
- return {currentTokens, dispatchValueUpdate: setCurrentValue, hasValidationError, isInitializing: latestResolvedRequest === 0, isValidating, tokenTitleMappings};
135
- }
136
-
137
- type ValidationStateDictReductionUpdateAction = {type?: 'update'|'rollback', data: Dict<ValidationState>};
138
- function performValidationStateDictReductionUpdate(prevState: Dict<ValidationState>, action: ValidationStateDictReductionUpdateAction): Dict<ValidationState> {
139
- switch (action.type) {
140
- case 'rollback':
141
- return {...prevState, ...action.data};
142
- default:
143
- return {
144
- ...prevState, ...Object.fromEntries(Object.entries(action.data)
145
- .filter(([token, validationState]) => validationState !== ValidationState.Waiting || !(token in prevState)))
146
- };
147
- }
148
- }
149
- function currentTokensStateReducer(currentTokens: TokenItem[], newTokens: TokenItem[]): TokenItem[] {
150
- if (newTokens.length !== currentTokens.length) {
151
- return newTokens;
152
- }
153
- for (let i = 0; i < newTokens.length; i++) {
154
- if (currentTokens[i]!.value !== newTokens[i]!.value || currentTokens[i]!.status !== newTokens[i]!.status || currentTokens[i]!.title !== newTokens[i]!.title) {
155
- return newTokens;
156
- }
157
- }
158
- return currentTokens;
159
- }
160
- function tokenTitleMappingsUpdater(state: Dict<string>, tokens: TokenItem[]): Dict<string> {
161
- const titleWithIdRegex = /(.*)\(#([^)]+)\)$/;
162
- const novelTokens = tokens
163
- .map<[string, string|undefined]>(token => [token.value, token.title])
164
- .filter((token): token is [string, string] => token[1] !== undefined && !isNumeric(token[1]))
165
- .map<[string, string]>(token => {
166
- const res = titleWithIdRegex.exec(token[1]);
167
- return res && res[1] && res[1].trim().length < 30 ? token : [token[0], labelToDisplayableTitle(res?.[1] ?? token[1], res?.[2] ?? token[0])];
168
- })
169
- .filter(token => state[token[0]] !== token[1]);
170
- if (novelTokens.length === 0) {
171
- return state;
172
- }
173
- return {...state, ...Object.fromEntries(novelTokens)};
174
- }
175
- function labelToDisplayableTitle(label: string, id: string): string {
176
- return `${label.length > 30 ? `${label.substring(0, 30).trim()}…` : label.trim()} (#${id})`;
177
- }
@@ -1,24 +0,0 @@
1
- export {AsynchronousFormTokenField} from "./AsynchronousFormTokenField";
2
- export * from "./BaseSortableItemsControl";
3
- export * from "./ExtendedFormTokenField";
4
- export * from "./ExtendedPostPicker";
5
- export * from "./ExtendedRadioControl";
6
- export * from "./ExtendedTaxonomyPicker";
7
- export * from "./ExtendedTermPicker";
8
- export * from "./ExtendedTextareaControl";
9
- export * from "./ExtendedUserPicker";
10
- export * from "./FileControl";
11
- export * from "./FullSizeToggleControl";
12
- export * from "./ImageControl";
13
- export * from "./InspectorPanel";
14
- export * from "./LazySuggestionsComboboxControl";
15
- export * from "./MultiSelectControl";
16
- export * from "./PickOne";
17
- export * from "./PromisableComponent";
18
- export * from "./ProperLinkControl";
19
- export * from "./SimpleToggle";
20
- export * from "./SortableFlexibleItemsControl";
21
- export * from "./SortableItemsControl";
22
-
23
- export { requestPostsFromAPI } from "./shared";
24
- export type * from "./types";
@@ -1,50 +0,0 @@
1
- import apiFetch from "@wordpress/api-fetch";
2
- import {addQueryArgs} from "@wordpress/url";
3
-
4
- import type {ObjectNormalizedPickableOptions, PairNormalizedPickableOptions, PickableOptions} from "./types";
5
-
6
- export async function requestPostsFromAPI(data: {search?: string, ids?: string, postTypes?: string|undefined}) {
7
- return (await apiFetch<Array<{ id: number, title: string, type: string }>>({path: addQueryArgs("/plaudit/common/v1/post-table-search", data)}))
8
- .map(item => ({id: item.id.toString(), title: item.title, type: item.type}));
9
- }
10
-
11
- export function normalizePickableOptionsToObjects<V extends string|number, T extends Omit<object, 'text'|'value'> = {}>(
12
- options: PickableOptions<V, T>
13
- ): ObjectNormalizedPickableOptions<V, T> {
14
- return options.map(option => {
15
- if (Array.isArray(option)) {
16
- if (typeof option[1] === 'string') {
17
- return {value: option[0], label: option[1]} as ObjectNormalizedPickableOptions<V, T>[number];
18
- }
19
- const {text, ...rest} = option[1];
20
- return {value: option[0], label: text, ...rest} as unknown as ObjectNormalizedPickableOptions<V, T>[number];
21
- }
22
- return option;
23
- });
24
- }
25
- export function normalizePickableOptionsToPairs<V extends string|number, T extends Omit<object, 'text'|'value'|'tooltip'> = {}>(
26
- options: PickableOptions<V, T>
27
- ): PairNormalizedPickableOptions<V, T> {
28
- return options.map(option => {
29
- if (isPairPickableOption<V, T>(option)) {
30
- return option;
31
- }
32
- const {value, label, ...rest} = option;
33
- return [value, {text: label, ...rest}] as unknown as PairNormalizedPickableOptions<V, T>[number];
34
- });
35
- }
36
- export function getLabel(option: PickableOptions<any>[number]): string {
37
- return Array.isArray(option) ? (typeof option[1] === 'string' ? option[1] : option[1].text) : (option.label ?? option.value.toString());
38
- }
39
-
40
- function isPairPickableOption<V extends string|number, T extends Omit<object, 'text'|'value'|'tooltip'> = {}>(
41
- option: PickableOptions<V, T>[number]
42
- ): option is PairNormalizedPickableOptions<V, T>[number] {
43
- return Array.isArray(option) && option.length === 2;
44
- }
45
-
46
- export function isNumeric(str: string|number) {
47
- if (typeof str !== "string") return true; // we only process strings!
48
- return !isNaN(str as any) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)...
49
- !isNaN(parseFloat(str)) // ...and ensure strings of whitespace fail
50
- }
@@ -1,18 +0,0 @@
1
- import type {BlockEditProps} from "@wordpress/blocks";
2
-
3
- export type SpecificValueBlockProps<T extends string, V> = {
4
- attribute: T, attributes: BlockEditProps<Record<T, V>&Record<string, unknown>>['attributes'],
5
- setAttributes: BlockEditProps<any>['setAttributes']
6
- }
7
- export type SimpleBlockControlProps<T extends string, V> = SpecificValueBlockProps<T, V>&{
8
- label: string,
9
- help?: string
10
- };
11
-
12
- export type PairNormalizedPickableOptions<V extends string|number, T extends Omit<object, 'text'|'value'|'tooltip'> = {}> = Array<[V, string|({text: string, tooltip?: string|undefined}&T)]>;
13
- export type ObjectNormalizedPickableOptions<V extends string|number, T extends Omit<object, 'text'|'value'|'tooltip'> = {}> = Array<{value: V, label?: string|undefined, tooltip?: string|undefined}&T>;
14
- export type PickableOptions<V extends string|number, T extends Omit<object, 'text'|'value'|'tooltip'> = {}> = Array<PairNormalizedPickableOptions<V, T>[number]|ObjectNormalizedPickableOptions<V, T>[number]>;
15
-
16
- export type Dict<V> = {[key: string]: V};
17
-
18
- export type FlexibleItem<T extends string = string> = {type: T}&{[key: Exclude<string, 'type'>]: unknown};
@@ -1,60 +0,0 @@
1
- import {BlockControls, store as blockEditorStore, useBlockEditingMode} from "@wordpress/block-editor";
2
- import {store as blocksStore, createBlock} from "@wordpress/blocks";
3
- import {ToolbarButton, ToolbarGroup} from "@wordpress/components";
4
- import {dispatch, select, useSelect} from "@wordpress/data";
5
- import {__} from "@wordpress/i18n";
6
-
7
- import type {BlockName} from "@plaudit/gutenberg-api-extensions";
8
-
9
- import {memo} from "react";
10
-
11
- const emptyProps: Record<string, unknown> = {};
12
-
13
- type InsertSoleAllowedBlockShortcutProps = {
14
- clientId: string,
15
- targetBlockName: BlockName,
16
- addedBlockName: BlockName,
17
- isSelected: boolean,
18
- props?: Record<string, unknown>,
19
- buttonText?: string
20
- };
21
-
22
- export const InsertSiblingOrChildBlockShortcut = memo(function InsertSiblingOrChildBlockShortcut(properties: InsertSoleAllowedBlockShortcutProps) {
23
- return <BlockControls group="other">
24
- <ToolbarGroup>
25
- <InsertSiblingOrChildBlockShortcutButton {...properties} />
26
- </ToolbarGroup>
27
- </BlockControls>
28
- });
29
-
30
- export const InsertSiblingOrChildBlockShortcutButton = memo(function InsertSiblingOrChildBlockShortcutButton(properties: InsertSoleAllowedBlockShortcutProps) {
31
- const {clientId, targetBlockName, addedBlockName, isSelected, props = emptyProps, buttonText = "Add {blockName}"} = properties;
32
-
33
- const targetClientId = useSelect((select) => {
34
- const blockEditorSelect = select(blockEditorStore);
35
- if (blockEditorSelect.getBlock(clientId)?.name === targetBlockName) {
36
- return clientId;
37
- }
38
- return blockEditorSelect.getBlockParentsByBlockName(clientId, targetBlockName, true)[0];
39
- }, [clientId, targetBlockName]);
40
-
41
- if (useBlockEditingMode() === "contentOnly" || !isSelected || !targetClientId) {
42
- return;
43
- }
44
-
45
- const buttonChildren = __(buttonText).replaceAll("{blockName}", select(blocksStore).getBlockType(addedBlockName)?.title ?? __(`${clientId === targetClientId ? "child" : "sibling"} block`));
46
- return (
47
- <ToolbarButton onClick={() => {
48
- let insertionIndex: number;
49
- const blockEditorSelect = select(blockEditorStore);
50
- if (clientId && clientId !== targetClientId) {
51
- const closestClients = blockEditorSelect.getBlockParents(clientId, true);
52
- const targetIndex = closestClients.indexOf(targetClientId);
53
- insertionIndex = blockEditorSelect.getBlockIndex(closestClients[targetIndex - 1] ?? clientId, targetClientId);
54
- } else {
55
- insertionIndex = -1;
56
- }
57
- dispatch(blockEditorStore).insertBlock(createBlock(addedBlockName, props), insertionIndex > -1 ? insertionIndex + 1 : undefined, targetClientId);
58
- }} children={buttonChildren} />
59
- );
60
- });
@@ -1,51 +0,0 @@
1
- import { store as blockEditorStore } from "@wordpress/block-editor";
2
- import { createHigherOrderComponent } from "@wordpress/compose";
3
- import { select } from "@wordpress/data";
4
- import { addFilter } from "@wordpress/hooks";
5
-
6
- import type { FunctionComponent, ReactNode } from "react";
7
-
8
- import { InsertSiblingOrChildBlockShortcut } from "./insert-sibling-or-child-block-shortcut";
9
- import type { ActualBlockEditProps, BlockName, RegisterBlockAttrs } from "../lib/useful-types";
10
-
11
- export function installInsertSoleAllowedBlockShortcutSupport() {
12
- const namespace = 'plaudit/gutenberg-api-extensions/inject-insert-sole-allowed-block-support';
13
- const blocksWithInsertSoleAllowedBlockShortcut: Record<BlockName, BlockName | undefined> = {};
14
- const blocksThatMightBeAbleToInsertSiblings: Record<BlockName, BlockName> = {};
15
- addFilter('blocks.registerBlockType', namespace, (atts: RegisterBlockAttrs) => {
16
- if (atts.supports?.plaudit?.insertSoleAllowedBlockShortcut) {
17
- const target = atts.allowedBlocks?.[0];
18
- if (target) {
19
- blocksWithInsertSoleAllowedBlockShortcut[atts.name] = target;
20
- blocksThatMightBeAbleToInsertSiblings[target] = atts.name;
21
- }
22
- }
23
- return atts;
24
- }, 1_000 /* We have this run late to allow for dynamic injection of the supports flag */);
25
-
26
- addFilter('editor.BlockEdit', namespace,
27
- createHigherOrderComponent((BlockEdit: FunctionComponent<ActualBlockEditProps>) => {
28
- return function InsertSoleAllowedBlockShortcutSupport(props: ActualBlockEditProps) {
29
- const potentialAddedBlockName = blocksWithInsertSoleAllowedBlockShortcut[props.name];
30
- const toolbarItems: ReactNode[] = [];
31
- if (potentialAddedBlockName) {
32
- toolbarItems.push(
33
- <InsertSiblingOrChildBlockShortcut
34
- key="add-own-sole-allowed-block" clientId={props.clientId} targetBlockName={props.name} addedBlockName={potentialAddedBlockName}
35
- isSelected={select(blockEditorStore).getSelectedBlockClientId() === props.clientId}
36
- />
37
- );
38
- }
39
- const potentialTargetBlockName = blocksThatMightBeAbleToInsertSiblings[props.name];
40
- if (potentialTargetBlockName) {
41
- toolbarItems.push(
42
- <InsertSiblingOrChildBlockShortcut
43
- key="add-ancestor-sole-allowed-block" clientId={props.clientId} targetBlockName={potentialTargetBlockName} addedBlockName={props.name}
44
- isSelected={select(blockEditorStore).getSelectedBlockClientId() === props.clientId}
45
- />
46
- );
47
- }
48
- return <><BlockEdit {...props} /> {toolbarItems}</>;
49
- };
50
- }, 'plaudit/gutenberg-api-extensions/insert-sole-allowed-block-support'));
51
- }
@@ -1,31 +0,0 @@
1
- import apiFetch from "@wordpress/api-fetch";
2
- import {dispatch, useSuspenseSelect} from "@wordpress/data";
3
-
4
- import {installSimpleGutenbergApisSupport, store as endpointsStore} from "../editor/simple-gutenberg-endpoints-impl";
5
-
6
- export type ApiCallArgs = [string, any][]|Record<string, any>|URLSearchParams|string|undefined;
7
- export function registerSimpleGutenbergApiEndpoint<T>(
8
- name: string, endpoint: ((args: ApiCallArgs, key: string) => Promise<T>)|Parameters<typeof apiFetch<T>>[0],
9
- options?: {transformer?: (response: Awaited<T>, args: ApiCallArgs, key: string) => unknown, maxCachedResults?: number}
10
- ): void {
11
- installSimpleGutenbergApisSupport();
12
- dispatch(endpointsStore).registerEndpoint(name, endpoint, options);
13
- }
14
-
15
- /**
16
- * @param name the name of the endpoint
17
- * @param args MUST be a value derived from useDeferredValue()
18
- * @param config shared configuration logic
19
- */
20
- export function useEndpointResolver<R>(
21
- name: string, args: ApiCallArgs, config: {immediateReturnOnEmpty?: boolean, dependencies?: readonly unknown[]} = {}
22
- ) {
23
- const {immediateReturnOnEmpty = true, dependencies = []} = config;
24
- return useSuspenseSelect(select => {
25
- if (!args && immediateReturnOnEmpty) {
26
- return undefined;
27
- }
28
- const selector = select(endpointsStore);
29
- return selector.get(name, args) as R|undefined;
30
- }, [args, immediateReturnOnEmpty, ...dependencies]);
31
- }
@@ -1,126 +0,0 @@
1
- import apiFetch from "@wordpress/api-fetch";
2
- import {type createReduxStore, register} from "@wordpress/data";
3
- import {applyFilters} from "@wordpress/hooks";
4
-
5
- import {clone, TemporalLRUCache} from "../lib/helpers";
6
- import {use, useImmediately, useToPromise, type WrappedPromise, wrapPromise} from "../lib/suspense/promise-handlers";
7
- import type {ApiCallArgs} from "./simple-gutenberg-endpoints-api";
8
-
9
- type ResponseCacheEntry = {args: ApiCallArgs, promise: WrappedPromise<unknown>};
10
-
11
- type EndpointOptions<P, R> = {transformer?: (response: P, args: ApiCallArgs, name: string) => R, maxCachedResults?: number};
12
- type ApiEndpointConfig<T> = {
13
- endpoint: ((args: ApiCallArgs, name: string) => Promise<T>)|Parameters<typeof apiFetch<T, boolean>>[0],
14
- responseCache: TemporalLRUCache<string, ResponseCacheEntry>
15
- }&ApiEndpointOptions<T>;
16
- export type ApiEndpointOptions<T> = {transformer?: (response: Awaited<T>, args: ApiCallArgs, name: string) => unknown, maxCachedResults?: number};
17
-
18
- function argsToKey(args: ApiCallArgs) {
19
- return args instanceof URLSearchParams || typeof args !== 'object' ? args?.toString() ?? "" : JSON.stringify(args);
20
- }
21
-
22
- export function installSimpleGutenbergApisSupport() {
23
- if (!installSimpleGutenbergApisSupport.called) {
24
- installSimpleGutenbergApisSupport.called = true;
25
- register(store);
26
- }
27
- }
28
- export namespace installSimpleGutenbergApisSupport {
29
- export let called: boolean = false;
30
- }
31
-
32
- type Selectors = {
33
- get(name: string, args?: ApiCallArgs): unknown,
34
- resolve(name: string, args?: ApiCallArgs): Promise<unknown>,
35
- registeredEndpoints(): string[]
36
- };
37
- type Actions = {
38
- registerEndpoint(
39
- name: string, endpoint: ((args: ApiCallArgs, name: string) => Promise<unknown>)|Parameters<typeof apiFetch>[0],
40
- options?: EndpointOptions<any, unknown>
41
- ): void
42
- };
43
- type AddBSStateArg<S extends {[name in string]: (...args: any[]) => any}> = {[name in keyof S]: (state: any, ...args: Parameters<S[name]>) => ReturnType<S[name]>};
44
- export const store: ReturnType<typeof createReduxStore<{}, Actions, AddBSStateArg<Selectors>>> = {
45
- name: 'plaudit/simple-gutenberg-apis',
46
- instantiate: () => {
47
- const listeners = new Set<() => void>();
48
- const endpoints: {[name in string]: ApiEndpointConfig<any>} = {};
49
-
50
- function storeChanged() {
51
- for (const listener of listeners) {
52
- listener();
53
- }
54
- }
55
-
56
- function handleCacheMiss(name: string, args?: ApiCallArgs) {
57
- const endpoint = endpoints[name];
58
- if (!endpoint) {
59
- throw new Error(`Attempted to fetch data from an unmapped endpoint: ${name}`);
60
- }
61
- const key = argsToKey(args);
62
- const existingPromise = endpoint.responseCache.get(key)?.promise;
63
- if (existingPromise) {
64
- return existingPromise;
65
- }
66
- const res = wrapPromise(
67
- (typeof endpoint.endpoint === 'function' ? endpoint.endpoint(args, name) : apiFetch(endpoint.endpoint))
68
- .then(rawResponse => {
69
- return applyFilters(`plaudit-common.simple-gutenberg-apis.endpoint.${name}.response`,
70
- endpoint.transformer?.(rawResponse, args, name) ?? rawResponse, args, name);
71
- })
72
- );
73
- res.finally(() => storeChanged());
74
- endpoint.responseCache.set(key, {args, promise: res});
75
- return res;
76
- }
77
-
78
- const actions: Actions = {
79
- registerEndpoint(name, endpoint, options?) {
80
- if (endpoints[name]?.responseCache instanceof TemporalLRUCache) {
81
- endpoints[name].responseCache.cancelTimeout();
82
- }
83
- endpoints[name] = {
84
- endpoint: clone(endpoint),
85
- ...options,
86
- responseCache: new TemporalLRUCache(options?.maxCachedResults ?? Number.MAX_SAFE_INTEGER)
87
- };
88
- storeChanged();
89
- }
90
- };
91
-
92
- const selectors: Selectors = {
93
- get(name, args?) {
94
- return useImmediately(handleCacheMiss(name, args));
95
- },
96
- resolve(name, args?) {
97
- return handleCacheMiss(name, args);
98
- },
99
- registeredEndpoints(): string[] {
100
- return Object.keys(endpoints);
101
- }
102
- };
103
- const suspendSelectors: Selectors = {
104
- get(name, args?) {
105
- return use(handleCacheMiss(name, args));
106
- },
107
- resolve(name, args?) {
108
- return useToPromise(handleCacheMiss(name, args));
109
- },
110
- registeredEndpoints(): string[] {
111
- return Object.keys(endpoints);
112
- }
113
- };
114
-
115
- // noinspection JSUnusedGlobalSymbols
116
- return {
117
- getSelectors: () => selectors,
118
- getSuspendSelectors: () => suspendSelectors, // This adds support for useSuspenseSelect. WordPress' typedefs are incomplete.
119
- getActions: (): any => actions, // This is correctly typed, but WordPress' typedefs are broken.
120
- subscribe: (listener: () => void) => {
121
- listeners.add(listener);
122
- return () => listeners.delete(listener);
123
- }
124
- };
125
- }
126
- };
package/src/index.ts DELETED
@@ -1,30 +0,0 @@
1
- import {installSimpleNativePropertiesSupport} from "./blocks/simple-native-property-impl";
2
- import {installInsertSoleAllowedBlockShortcutSupport} from "./editor/install-insert-sole-allowed-block-shortcut-support";
3
- import {installSimpleGutenbergApisSupport, store as endpointsStore} from "./editor/simple-gutenberg-endpoints-impl";
4
- import {registerStore, store as apiExtensionsStore} from "./lib/gutenberg-api-extensions-state";
5
-
6
- export * from "./blocks";
7
- export * from "./controls";
8
- export * from "./editor/simple-gutenberg-endpoints-api";
9
- export * from "./editor/insert-sibling-or-child-block-shortcut";
10
- export {TemporalLRUCache, useAdoptedStyleSheet} from "./lib/helpers";
11
- export * from "./lib/modified-fast-deep-equals";
12
- export * from "./lib/plaudit-icons";
13
- export * as SectionedCacheStore from "./lib/sectioned-cache-store";
14
- export * from "./lib/suspense";
15
- export * from "./lib/useful-types";
16
-
17
- export {apiExtensionsStore, endpointsStore};
18
-
19
- export function installGutenbergExtensions() {
20
- if (!installGutenbergExtensions.called) {
21
- installGutenbergExtensions.called = true;
22
- registerStore();
23
- installSimpleNativePropertiesSupport();
24
- installSimpleGutenbergApisSupport();
25
- installInsertSoleAllowedBlockShortcutSupport();
26
- }
27
- }
28
- export namespace installGutenbergExtensions {
29
- export let called: boolean = false;
30
- }
@@ -1,21 +0,0 @@
1
- import {DbSource} from "./useful-types";
2
-
3
- declare module '@wordpress/blocks' {
4
- type BaseRegisterBlockBindingsSourceFunctionArgs = {
5
- bindings?: {[key: string]: string|{[key in string]: unknown}}, clientId?: string,
6
- context: {[key in string]: any}, select: unknown
7
- };
8
- type ExtendedRegisterBlockBindingsSourceFunctionArgs = BaseRegisterBlockBindingsSourceFunctionArgs&{dispatch: {[key in string]: (...args: unknown[]) => unknown}};
9
-
10
- type RegisterBlockBindingsSourceArgs = {
11
- name: string, label: string, usesContext?: string[],
12
- getValues?(args: BaseRegisterBlockBindingsSourceFunctionArgs): unknown,
13
- setValues?: unknown,
14
- getFieldsList?(args: BaseRegisterBlockBindingsSourceFunctionArgs): unknown,
15
- canUserEditValue(args: BaseRegisterBlockBindingsSourceFunctionArgs): boolean,
16
- dbSources?: DbSource[]
17
- };
18
- function registerBlockBindingsSource(args: RegisterBlockBindingsSourceArgs): void;
19
-
20
- function getBlockBindingsSources(): {[key: string]: RegisterBlockBindingsSourceArgs};
21
- }
@@ -1,35 +0,0 @@
1
- import {registerBlockBindingsSource} from "@wordpress/blocks";
2
-
3
- import {produce} from "immer";
4
-
5
- import type {DbSource} from "../../blocks";
6
- import type {WorkableUnknownAction} from "../useful-types";
7
-
8
- export type CustomBlockBindingsSupportLogicActions = {type: 'ADD_CUSTOM_BLOCK_BINDINGS_SOURCE', name: string, value: {dbSources?: DbSource[]}};
9
- export type CustomBlockBindingsSupportLogicState = {
10
- customBlockBindingsSupport: {[name in string]?: {dbSources?: DbSource[]}}
11
- };
12
-
13
- export const customBlockBindingsSupportLogicActions = {
14
- registerCustomBlockBindingsSource(args: Parameters<typeof registerBlockBindingsSource>[0]&{dbSources?: DbSource[]}) {
15
- const {dbSources, ...actualArgs} = args;
16
- registerBlockBindingsSource('getValue' in actualArgs ? actualArgs : {...actualArgs, getValue: () => ({})});
17
- return {type: 'ADD_CUSTOM_BLOCK_BINDINGS_SOURCE', name: actualArgs.name, value: {dbSources}};
18
- }
19
- };
20
- export const customBlockBindingsSupportLogicSelectors = {
21
- customBlockBindingsSource(state: CustomBlockBindingsSupportLogicState, name: string) {
22
- return state.customBlockBindingsSupport[name];
23
- }
24
- };
25
-
26
- export function customBlockBindingsSupportLogic(
27
- state: CustomBlockBindingsSupportLogicState, action: CustomBlockBindingsSupportLogicActions|WorkableUnknownAction
28
- ): typeof state {
29
- if (action.type === "ADD_CUSTOM_BLOCK_BINDINGS_SOURCE") {
30
- return produce(state, (draft: typeof state) => {
31
- draft.customBlockBindingsSupport[action.name] = action.value;
32
- });
33
- }
34
- return state;
35
- }