PluginProbe
Tableberg – Simple Gutenberg Table Block / 1.1.5
Tableberg – Simple Gutenberg Table Block v1.1.5
1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 trunk 0.0.2 0.2.1 0.3.2 0.3.3 0.4.1 0.5.0 0.5.1 0.5.2 0.5.3 0.5.4 0.5.5 0.5.6 0.5.7 All 42 releases
tableberg / src / search.ts

search.ts in Tableberg – Simple Gutenberg Table Block 1.1.5, at src/search.ts

149 lines 3.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { Cell, CellElement, CellKey, TableConfig } from "./attributes";
2 import { getCellKey } from "./attributes";
3 import { getElementTextContent } from "./elements";
4
5 function getCellElements(
6 cells: Record<CellKey, Cell>,
7 row: number,
8 col: number
9 ): CellElement[] {
10 return cells[getCellKey(row, col)]?.elements || [];
11 }
12
13 function rowMatchesSearch(
14 row: number,
15 cols: number,
16 cells: Record<CellKey, Cell>,
17 searchTerm: string
18 ): boolean {
19 const normalizedSearch = searchTerm.toLowerCase().trim();
20 if (!normalizedSearch) return true;
21
22 for (let col = 0; col < cols; col++) {
23 const cellText = getCellElements(cells, row, col)
24 .map(element => getElementTextContent(element))
25 .join(" ")
26 .toLowerCase();
27
28 if (cellText.includes(normalizedSearch)) {
29 return true;
30 }
31 }
32
33 return false;
34 }
35
36 export function filterRowsBySearch(
37 cells: Record<CellKey, Cell>,
38 totalRows: number,
39 totalCols: number,
40 table: TableConfig,
41 searchTerm: string
42 ): number[] {
43 const { headerEnabled, footerEnabled } = table;
44
45 const normalizedSearch = searchTerm.toLowerCase().trim();
46
47 if (!normalizedSearch) {
48 return Array.from({ length: totalRows }, (_, i) => i);
49 }
50
51 const headerRow = headerEnabled ? 0 : -1;
52 const footerRow = footerEnabled ? totalRows - 1 : -1;
53
54 const result: number[] = [];
55
56 if (headerEnabled) {
57 result.push(0);
58 }
59
60 for (let row = 0; row < totalRows; row++) {
61 if (row === headerRow || row === footerRow) {
62 continue;
63 }
64
65 if (rowMatchesSearch(row, totalCols, cells, normalizedSearch)) {
66 result.push(row);
67 }
68 }
69
70 if (footerEnabled) {
71 result.push(totalRows - 1);
72 }
73
74 return result;
75 }
76
77 export function getMatchingRowCount(
78 cells: Record<CellKey, Cell>,
79 totalRows: number,
80 totalCols: number,
81 table: TableConfig,
82 searchTerm: string
83 ): number {
84 const filteredRows = filterRowsBySearch(
85 cells,
86 totalRows,
87 totalCols,
88 table,
89 searchTerm
90 );
91 let count = filteredRows.length;
92
93 if (table.headerEnabled) count--;
94 if (table.footerEnabled) count--;
95
96 return Math.max(0, count);
97 }
98
99 export interface HighlightSegment {
100 text: string;
101 highlighted: boolean;
102 }
103
104 export function getHighlightSegments(
105 content: string,
106 searchTerm: string
107 ): HighlightSegment[] {
108 const plainText = content.replace(/<[^>]*>/g, "");
109 const trimmedSearch = searchTerm.trim();
110
111 if (!trimmedSearch || !plainText) {
112 return [{ text: plainText, highlighted: false }];
113 }
114
115 const escapedTerm = trimmedSearch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
116 const regex = new RegExp(`(${escapedTerm})`, "gi");
117
118 const segments: HighlightSegment[] = [];
119 let lastIndex = 0;
120 let match: RegExpExecArray | null;
121
122 while ((match = regex.exec(plainText)) !== null) {
123 if (match.index > lastIndex) {
124 segments.push({
125 text: plainText.slice(lastIndex, match.index),
126 highlighted: false,
127 });
128 }
129
130 segments.push({
131 text: match[1],
132 highlighted: true,
133 });
134
135 lastIndex = regex.lastIndex;
136 }
137
138 if (lastIndex < plainText.length) {
139 segments.push({
140 text: plainText.slice(lastIndex),
141 highlighted: false,
142 });
143 }
144
145 return segments.length > 0
146 ? segments
147 : [{ text: plainText, highlighted: false }];
148 }
149