PluginProbe
Extendify / 3.1.5
Extendify v3.1.5
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / Agent / workflows / block-selector / tools / find-blocks.js

find-blocks.js in Extendify 3.1.5, at src/Agent/workflows/block-selector/tools/find-blocks.js

84 lines 2.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { IGNORED_BLOCKS } from '@agent/lib/classify-block-edit';
2 import { buildSubtreeManifest } from '@agent/lib/subtree-manifest';
3
4 // Mirrors TagBlocks::$ignored — a patch here would rewrite every loop item.
5 const LOOP_BLOCKS = new Set([
6 'core/query',
7 'core/post-template',
8 'core/post-content',
9 'core/comments',
10 'core/comment-template',
11 'woocommerce/product-collection',
12 'woocommerce/product-template',
13 ]);
14
15 const MAX_CANDIDATES = 25;
16
17 const BLOCK_ID_ATTR = 'data-extendify-agent-block-id';
18
19 // An item can't add a sibling, nor stand in for the whole set.
20 export const CONTAINER_TYPES = new Set([
21 'core/columns',
22 'core/list',
23 'core/group',
24 ]);
25
26 const addressable = (type) =>
27 !LOOP_BLOCKS.has(type) && !IGNORED_BLOCKS.has(type);
28
29 const containersFor = (matches, byId) => {
30 const found = new Map();
31 for (const { blockId } of matches) {
32 let node = document.querySelector(
33 `[${BLOCK_ID_ATTR}="${CSS.escape(String(blockId))}"]`,
34 )?.parentElement;
35 while (node) {
36 const id = node.getAttribute?.(BLOCK_ID_ATTR);
37 const entry = id ? byId.get(id) : null;
38 if (entry && CONTAINER_TYPES.has(entry.type)) {
39 found.set(id, entry);
40 break;
41 }
42 node = node.parentElement;
43 }
44 }
45 return [...found.values()];
46 };
47
48 const matchesText = (blockText, text) =>
49 !text || (blockText ?? '').toLowerCase().includes(text.toLowerCase());
50
51 const matchesType = (type, blockTypes) =>
52 !blockTypes?.length || blockTypes.includes(type);
53
54 export default ({ blockTypes, text } = {}) => {
55 const root = document.querySelector('.wp-site-blocks') ?? document.body;
56 const all = buildSubtreeManifest(root).filter(({ type }) =>
57 addressable(type),
58 );
59 const byId = new Map(all.map((entry) => [entry.blockId, entry]));
60 const byType = all.filter(({ type }) => matchesType(type, blockTypes));
61 const matches = byType.filter(({ text: blockText }) =>
62 matchesText(blockText, text),
63 );
64 // "hero" is a position, not text: AND-ing it would match nothing.
65 const textIgnored =
66 Boolean(text) &&
67 Boolean(blockTypes?.length) &&
68 !matches.length &&
69 byType.length > 0;
70 const found = textIgnored ? byType : matches;
71 // Cap first, or a long match list truncates the containers away.
72 const shown = found.slice(0, MAX_CANDIDATES);
73 const containers = containersFor(shown, byId).filter(
74 (entry) => !shown.some(({ blockId }) => blockId === entry.blockId),
75 );
76 return {
77 blockSearch: {
78 total: found.length,
79 ...(textIgnored && { ignoredText: text }),
80 candidates: [...shown, ...containers],
81 },
82 };
83 };
84