@uniweb/semantic-parser 1.1.4 → 1.1.6
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.
- package/AGENTS.md +8 -11
- package/README.md +3 -160
- package/package.json +2 -5
- package/src/index.js +1 -2
- package/src/processors/groups.js +16 -15
- package/docs/api.md +0 -350
- package/docs/entity-consolidation.md +0 -470
- package/docs/file-structure.md +0 -50
- package/docs/guide.md +0 -206
- package/docs/mapping-patterns.md +0 -928
- package/docs/text-component-reference.md +0 -515
- package/reference/README.md +0 -195
- package/reference/Text.js +0 -188
- package/src/mappers/accessor.js +0 -312
- package/src/mappers/extractors.js +0 -416
- package/src/mappers/helpers.js +0 -234
- package/src/mappers/index.js +0 -28
- package/src/mappers/types.js +0 -495
- package/src/processors/groups_backup.js +0 -379
- package/src/processors/groups_doc.md +0 -179
- package/src/processors/sequence_backup.js +0 -402
- package/src/processors_old/byType.js +0 -129
- package/src/processors_old/groups.js +0 -240
- package/src/processors_old/sequence.js +0 -140
|
@@ -1,515 +0,0 @@
|
|
|
1
|
-
# Text Component Reference
|
|
2
|
-
|
|
3
|
-
A reference implementation of a smart typography component for rendering content from the semantic parser. This component is designed to handle the common patterns of rendering headings, paragraphs, and rich text content.
|
|
4
|
-
|
|
5
|
-
> **📦 Ready-to-use implementation:** [`reference/Text.js`](../reference/Text.js)
|
|
6
|
-
> **Installation guide:** [`reference/README.md`](../reference/README.md)
|
|
7
|
-
|
|
8
|
-
This is a **complete, production-ready implementation** that you can copy directly into your React project. See the [Installation](#installation) section below.
|
|
9
|
-
|
|
10
|
-
## Installation
|
|
11
|
-
|
|
12
|
-
**1. Copy the component to your project:**
|
|
13
|
-
```bash
|
|
14
|
-
cp reference/Text.js src/components/Text.js
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
**2. No additional dependencies needed** - Just React
|
|
18
|
-
|
|
19
|
-
**3. Sanitize at engine level** - See [Sanitization Tools](#sanitization-tools) below
|
|
20
|
-
|
|
21
|
-
**4. Use in your components:**
|
|
22
|
-
```jsx
|
|
23
|
-
import Text, { H1, P } from './components/Text';
|
|
24
|
-
import { parseContent, mappers } from '@uniweb/semantic-parser';
|
|
25
|
-
|
|
26
|
-
const parsed = parseContent(doc);
|
|
27
|
-
const hero = mappers.extractors.hero(parsed);
|
|
28
|
-
|
|
29
|
-
<H1 text={hero.title} />
|
|
30
|
-
<P text={hero.description} />
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
See [`reference/README.md`](../reference/README.md) for TypeScript setup and customization options.
|
|
34
|
-
|
|
35
|
-
## Overview
|
|
36
|
-
|
|
37
|
-
The Text component provides a unified interface for rendering text content, whether it's plain text, rich HTML, single strings, or arrays of paragraphs. It handles the complexities of:
|
|
38
|
-
|
|
39
|
-
- Rendering paragraph arrays with proper spacing
|
|
40
|
-
- Supporting rich HTML formatting (bold, italic, color marks)
|
|
41
|
-
- Semantic heading structures
|
|
42
|
-
- Empty content filtering
|
|
43
|
-
|
|
44
|
-
## Architecture Decision: Where to Sanitize
|
|
45
|
-
|
|
46
|
-
**Recommended: Sanitize at the engine level, not in the component.**
|
|
47
|
-
|
|
48
|
-
The semantic parser works with TipTap/ProseMirror editors that use schema-controlled HTML. The parser extracts and transforms this content, and your **engine** (the application layer that prepares data for components) should handle sanitization.
|
|
49
|
-
|
|
50
|
-
### Why Engine-Level Sanitization?
|
|
51
|
-
|
|
52
|
-
1. **Performance** - Sanitize once during data preparation, not on every render
|
|
53
|
-
2. **Context-aware** - Engine knows if content is from trusted TipTap or external sources
|
|
54
|
-
3. **Cacheable** - Sanitized content can be memoized
|
|
55
|
-
4. **Clear responsibility** - Engine owns the data pipeline
|
|
56
|
-
|
|
57
|
-
### Data Flow
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
TipTap Editor (schema-controlled)
|
|
61
|
-
↓
|
|
62
|
-
Parser (extraction + transformation)
|
|
63
|
-
↓
|
|
64
|
-
Engine (PRIMARY SANITIZATION HERE)
|
|
65
|
-
↓
|
|
66
|
-
Components (trust the data, just render)
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
The parser provides sanitization utilities (see [Sanitization Tools](#sanitization-tools)), but doesn't enforce their use. Your engine decides when and how to sanitize based on your security requirements.
|
|
70
|
-
|
|
71
|
-
## Implementation
|
|
72
|
-
|
|
73
|
-
### Basic Text Component
|
|
74
|
-
|
|
75
|
-
```jsx
|
|
76
|
-
import React from 'react';
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Text - A smart typography component for rendering content from semantic parser
|
|
80
|
-
*
|
|
81
|
-
* @param {Object} props
|
|
82
|
-
* @param {string|string[]} props.text - Content to render (string or array of paragraphs)
|
|
83
|
-
* @param {string} [props.as='p'] - HTML tag for wrapper/primary element
|
|
84
|
-
* @param {string} [props.className] - CSS class for styling
|
|
85
|
-
* @param {string} [props.lineAs] - Tag for array items (default: 'div' for headings, 'p' for others)
|
|
86
|
-
*/
|
|
87
|
-
const Text = ({ text, as = 'p', className, lineAs }) => {
|
|
88
|
-
const isArray = Array.isArray(text);
|
|
89
|
-
const Tag = as;
|
|
90
|
-
const isHeading = as === 'h1' || as === 'h2' || as === 'h3' || as === 'h4' || as === 'h5' || as === 'h6';
|
|
91
|
-
|
|
92
|
-
// Single string
|
|
93
|
-
if (!isArray) {
|
|
94
|
-
if (!text || text.trim() === '') return null;
|
|
95
|
-
return (
|
|
96
|
-
<Tag
|
|
97
|
-
className={className}
|
|
98
|
-
dangerouslySetInnerHTML={{ __html: text }}
|
|
99
|
-
/>
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Array of strings - filter empty content
|
|
104
|
-
const filteredText = text.filter(
|
|
105
|
-
(item) => typeof item === 'string' && item.trim() !== ''
|
|
106
|
-
);
|
|
107
|
-
if (filteredText.length === 0) return null;
|
|
108
|
-
|
|
109
|
-
const LineTag = lineAs || (isHeading ? 'div' : 'p');
|
|
110
|
-
|
|
111
|
-
// Headings: wrap all lines in one heading tag
|
|
112
|
-
if (isHeading) {
|
|
113
|
-
return (
|
|
114
|
-
<Tag className={className}>
|
|
115
|
-
{filteredText.map((line, i) => (
|
|
116
|
-
<LineTag
|
|
117
|
-
key={i}
|
|
118
|
-
dangerouslySetInnerHTML={{ __html: line }}
|
|
119
|
-
/>
|
|
120
|
-
))}
|
|
121
|
-
</Tag>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Non-headings: render each line as separate element
|
|
126
|
-
return (
|
|
127
|
-
<>
|
|
128
|
-
{filteredText.map((line, i) => (
|
|
129
|
-
<LineTag
|
|
130
|
-
key={i}
|
|
131
|
-
className={className}
|
|
132
|
-
dangerouslySetInnerHTML={{ __html: line }}
|
|
133
|
-
/>
|
|
134
|
-
))}
|
|
135
|
-
</>
|
|
136
|
-
);
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
export default Text;
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Semantic Wrapper Components
|
|
143
|
-
|
|
144
|
-
For better developer experience, create semantic shortcuts:
|
|
145
|
-
|
|
146
|
-
```jsx
|
|
147
|
-
// Heading components
|
|
148
|
-
export const H1 = (props) => <Text {...props} as="h1" />;
|
|
149
|
-
export const H2 = (props) => <Text {...props} as="h2" />;
|
|
150
|
-
export const H3 = (props) => <Text {...props} as="h3" />;
|
|
151
|
-
export const H4 = (props) => <Text {...props} as="h4" />;
|
|
152
|
-
export const H5 = (props) => <Text {...props} as="h5" />;
|
|
153
|
-
export const H6 = (props) => <Text {...props} as="h6" />;
|
|
154
|
-
|
|
155
|
-
// Paragraph component
|
|
156
|
-
export const P = (props) => <Text {...props} as="p" />;
|
|
157
|
-
|
|
158
|
-
// Div wrapper for flexible content
|
|
159
|
-
export const Div = (props) => <Text {...props} as="div" />;
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
## Usage with Semantic Parser
|
|
163
|
-
|
|
164
|
-
### Basic Examples
|
|
165
|
-
|
|
166
|
-
```jsx
|
|
167
|
-
import { parseContent } from '@uniweb/semantic-parser';
|
|
168
|
-
import { extractors } from '@uniweb/semantic-parser/mappers';
|
|
169
|
-
import { H1, P, Text } from './components/Text';
|
|
170
|
-
|
|
171
|
-
// Parse content
|
|
172
|
-
const parsed = parseContent(document);
|
|
173
|
-
|
|
174
|
-
// Extract hero data
|
|
175
|
-
const hero = extractors.hero(parsed);
|
|
176
|
-
|
|
177
|
-
// Render with Text components
|
|
178
|
-
<>
|
|
179
|
-
<H1 text={hero.title} />
|
|
180
|
-
{hero.subtitle && <H2 text={hero.subtitle} />}
|
|
181
|
-
<P text={hero.description} />
|
|
182
|
-
</>
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### Handling Arrays vs Strings
|
|
186
|
-
|
|
187
|
-
The parser's extractors now return paragraph arrays by default:
|
|
188
|
-
|
|
189
|
-
```jsx
|
|
190
|
-
// hero.description is an array: ["Para 1", "Para 2"]
|
|
191
|
-
<P text={hero.description} />
|
|
192
|
-
// Renders: <p>Para 1</p><p>Para 2</p>
|
|
193
|
-
|
|
194
|
-
// If you need a single string, use joinParagraphs helper
|
|
195
|
-
import { joinParagraphs } from '@uniweb/semantic-parser/mappers/helpers';
|
|
196
|
-
|
|
197
|
-
<P text={joinParagraphs(hero.description)} />
|
|
198
|
-
// Renders: <p>Para 1 Para 2</p>
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### Multi-line Headings
|
|
202
|
-
|
|
203
|
-
```jsx
|
|
204
|
-
// heading.title might be an array for multi-line headings
|
|
205
|
-
<H1 text={heading.title} />
|
|
206
|
-
|
|
207
|
-
// Example with array: ["Welcome to", "Our Platform"]
|
|
208
|
-
// Renders: <h1><div>Welcome to</div><div>Our Platform</div></h1>
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Color Marks Support
|
|
212
|
-
|
|
213
|
-
The parser supports color marks for headings using `<mark>` or `<span>` tags:
|
|
214
|
-
|
|
215
|
-
```jsx
|
|
216
|
-
// Content with color mark
|
|
217
|
-
const title = "Welcome to <mark class='brand'>Our Platform</mark>";
|
|
218
|
-
|
|
219
|
-
<H1 text={title} />
|
|
220
|
-
// Renders with mark tag preserved (if sanitized properly)
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
**Sanitization Configuration for Color Marks:**
|
|
224
|
-
|
|
225
|
-
```javascript
|
|
226
|
-
// In your engine, when sanitizing
|
|
227
|
-
import { sanitizeHtml } from '@uniweb/semantic-parser/mappers/types';
|
|
228
|
-
|
|
229
|
-
const safeTitleContent = sanitizeHtml(titleContent, {
|
|
230
|
-
allowedTags: ['strong', 'em', 'mark', 'span'],
|
|
231
|
-
allowedAttr: ['class', 'data-variant']
|
|
232
|
-
});
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
### Empty Content Handling
|
|
236
|
-
|
|
237
|
-
The component automatically filters empty content:
|
|
238
|
-
|
|
239
|
-
```jsx
|
|
240
|
-
<P text={["Valid content", "", " ", "More content"]} />
|
|
241
|
-
// Renders: <p>Valid content</p><p>More content</p>
|
|
242
|
-
|
|
243
|
-
<P text={[]} />
|
|
244
|
-
// Renders: null (nothing)
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
## Integration Patterns
|
|
248
|
-
|
|
249
|
-
### With Extractors
|
|
250
|
-
|
|
251
|
-
```jsx
|
|
252
|
-
import { parseContent, mappers } from '@uniweb/semantic-parser';
|
|
253
|
-
const { extractors, helpers } = mappers;
|
|
254
|
-
|
|
255
|
-
const parsed = parseContent(doc);
|
|
256
|
-
const card = extractors.card(parsed);
|
|
257
|
-
|
|
258
|
-
function Card({ data }) {
|
|
259
|
-
return (
|
|
260
|
-
<div className="card">
|
|
261
|
-
<H3 text={data.title} />
|
|
262
|
-
<P text={data.description} />
|
|
263
|
-
{data.image && <img src={data.image} alt={data.imageAlt} />}
|
|
264
|
-
</div>
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
<Card data={card} />
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
### With Custom Schemas
|
|
272
|
-
|
|
273
|
-
```jsx
|
|
274
|
-
import { getByPath, extractBySchema } from '@uniweb/semantic-parser/mappers/accessor';
|
|
275
|
-
|
|
276
|
-
const schema = {
|
|
277
|
-
title: { path: 'groups.main.title' },
|
|
278
|
-
subtitle: { path: 'groups.main.subtitle' },
|
|
279
|
-
content: { path: 'groups.main.paragraphs' }
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
const data = extractBySchema(parsed, schema);
|
|
283
|
-
|
|
284
|
-
<>
|
|
285
|
-
<H1 text={data.title} />
|
|
286
|
-
<H2 text={data.subtitle} />
|
|
287
|
-
<P text={data.content} />
|
|
288
|
-
</>
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### Rendering Lists
|
|
292
|
-
|
|
293
|
-
```jsx
|
|
294
|
-
const features = extractors.features(parsed);
|
|
295
|
-
|
|
296
|
-
<div className="features">
|
|
297
|
-
{features.map((feature, i) => (
|
|
298
|
-
<div key={i} className="feature">
|
|
299
|
-
<H3 text={feature.title} />
|
|
300
|
-
<P text={feature.description} />
|
|
301
|
-
</div>
|
|
302
|
-
))}
|
|
303
|
-
</div>
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
## Styling
|
|
307
|
-
|
|
308
|
-
The component is unstyled by default. Add your own CSS:
|
|
309
|
-
|
|
310
|
-
```css
|
|
311
|
-
/* Paragraph spacing */
|
|
312
|
-
p + p {
|
|
313
|
-
margin-top: 1.5rem;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/* Multi-line headings */
|
|
317
|
-
h1 > div + div {
|
|
318
|
-
margin-top: 0.25rem;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/* Color marks */
|
|
322
|
-
mark.brand {
|
|
323
|
-
background: linear-gradient(120deg, var(--brand-color) 0%, var(--brand-color) 100%);
|
|
324
|
-
background-repeat: no-repeat;
|
|
325
|
-
background-size: 100% 40%;
|
|
326
|
-
background-position: 0 85%;
|
|
327
|
-
color: inherit;
|
|
328
|
-
}
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
## Sanitization Tools
|
|
332
|
-
|
|
333
|
-
The parser exports sanitization utilities for use in your engine:
|
|
334
|
-
|
|
335
|
-
```javascript
|
|
336
|
-
import { sanitizeHtml, stripMarkup } from '@uniweb/semantic-parser/mappers/types';
|
|
337
|
-
|
|
338
|
-
// Sanitize HTML content
|
|
339
|
-
const safe = sanitizeHtml(content, {
|
|
340
|
-
allowedTags: ['strong', 'em', 'mark', 'span', 'a'],
|
|
341
|
-
allowedAttr: ['href', 'class', 'data-variant']
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
// Strip all HTML (for plain text)
|
|
345
|
-
const plain = stripMarkup(content);
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### When to Sanitize
|
|
349
|
-
|
|
350
|
-
**Always sanitize** when:
|
|
351
|
-
- Content comes from external sources
|
|
352
|
-
- Content is user-generated
|
|
353
|
-
- You're unsure of the source
|
|
354
|
-
|
|
355
|
-
**Optional sanitization** when:
|
|
356
|
-
- Content is from your controlled TipTap editor
|
|
357
|
-
- TipTap schema is locked down
|
|
358
|
-
- You trust the content pipeline
|
|
359
|
-
|
|
360
|
-
**Never needed** when:
|
|
361
|
-
- Content is hard-coded in your app
|
|
362
|
-
- Content is from your CMS with known schemas
|
|
363
|
-
|
|
364
|
-
## Advanced Customizations
|
|
365
|
-
|
|
366
|
-
### Custom Line Spacing
|
|
367
|
-
|
|
368
|
-
Add a `spacing` prop for different paragraph spacing:
|
|
369
|
-
|
|
370
|
-
```jsx
|
|
371
|
-
const Text = React.memo(({ text, as = 'p', className, lineAs, spacing = 'normal' }) => {
|
|
372
|
-
const spacingClass = spacing !== 'normal' ? `spacing-${spacing}` : '';
|
|
373
|
-
const combinedClass = [className, spacingClass].filter(Boolean).join(' ');
|
|
374
|
-
|
|
375
|
-
// ... rest of implementation using combinedClass
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
// Usage
|
|
379
|
-
<P text={paragraphs} spacing="comfortable" />
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
```css
|
|
383
|
-
.spacing-compact p + p { margin-top: 0.75rem; }
|
|
384
|
-
.spacing-comfortable p + p { margin-top: 1.5rem; }
|
|
385
|
-
.spacing-relaxed p + p { margin-top: 2rem; }
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### Plain Text Mode
|
|
389
|
-
|
|
390
|
-
Add an opt-out for HTML rendering:
|
|
391
|
-
|
|
392
|
-
```jsx
|
|
393
|
-
const Text = React.memo(({ text, as = 'p', className, lineAs, plainText = false }) => {
|
|
394
|
-
// ... existing code
|
|
395
|
-
|
|
396
|
-
if (plainText) {
|
|
397
|
-
// Render without dangerouslySetInnerHTML
|
|
398
|
-
return <Tag className={className}>{text}</Tag>;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// ... rest of implementation
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
// Usage
|
|
405
|
-
<Text text="Show <tags> literally" plainText={true} />
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
## Best Practices
|
|
409
|
-
|
|
410
|
-
### 1. Sanitize at Engine Level
|
|
411
|
-
|
|
412
|
-
```javascript
|
|
413
|
-
// ✅ Good - sanitize during data preparation
|
|
414
|
-
function prepareHeroData(parsed) {
|
|
415
|
-
const hero = extractors.hero(parsed);
|
|
416
|
-
return {
|
|
417
|
-
...hero,
|
|
418
|
-
title: sanitizeHtml(hero.title),
|
|
419
|
-
description: hero.description.map(p => sanitizeHtml(p))
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const heroData = prepareHeroData(parsed);
|
|
424
|
-
<H1 text={heroData.title} />
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
```javascript
|
|
428
|
-
// ❌ Avoid - sanitizing in component on every render
|
|
429
|
-
function Hero({ data }) {
|
|
430
|
-
const safeTitle = sanitizeHtml(data.title); // Runs every render!
|
|
431
|
-
return <H1 text={safeTitle} />;
|
|
432
|
-
}
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
### 2. Handle Empty Content
|
|
436
|
-
|
|
437
|
-
```javascript
|
|
438
|
-
// ✅ Good - component handles it
|
|
439
|
-
<P text={description} />
|
|
440
|
-
|
|
441
|
-
// ❌ Avoid - manual checks everywhere
|
|
442
|
-
{description && description.length > 0 && <P text={description} />}
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
### 3. Use Semantic Wrappers
|
|
446
|
-
|
|
447
|
-
```javascript
|
|
448
|
-
// ✅ Good - clear intent
|
|
449
|
-
<H1 text={title} />
|
|
450
|
-
<P text={content} />
|
|
451
|
-
|
|
452
|
-
// ❌ Avoid - verbose
|
|
453
|
-
<Text text={title} as="h1" />
|
|
454
|
-
<Text text={content} as="p" />
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
### 4. Preserve Arrays When Possible
|
|
458
|
-
|
|
459
|
-
```javascript
|
|
460
|
-
// ✅ Good - preserves paragraph structure
|
|
461
|
-
<P text={hero.description} />
|
|
462
|
-
|
|
463
|
-
// ⚠️ Consider if you really need this
|
|
464
|
-
<P text={joinParagraphs(hero.description)} />
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
## TypeScript Support
|
|
468
|
-
|
|
469
|
-
```typescript
|
|
470
|
-
interface TextProps {
|
|
471
|
-
text: string | string[];
|
|
472
|
-
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'div' | 'span';
|
|
473
|
-
className?: string;
|
|
474
|
-
lineAs?: string;
|
|
475
|
-
spacing?: 'compact' | 'normal' | 'comfortable' | 'relaxed';
|
|
476
|
-
plainText?: boolean;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const Text: React.FC<TextProps> = ({ ... }) => { ... };
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
## Performance Considerations
|
|
483
|
-
|
|
484
|
-
1. **Sanitize once** - At engine level, not in component
|
|
485
|
-
2. **Memoize data** - Cache parsed/extracted data at the engine level with `useMemo`
|
|
486
|
-
3. **Filter early** - Remove empty content during extraction if possible
|
|
487
|
-
4. **Use proper keys** - In lists, use stable unique keys (not array indices)
|
|
488
|
-
5. **Batch updates** - Prepare all data before rendering
|
|
489
|
-
|
|
490
|
-
**Note:** The Text component itself is simple and fast. No need for `React.memo` unless profiling proves it's a bottleneck.
|
|
491
|
-
|
|
492
|
-
## Browser Support
|
|
493
|
-
|
|
494
|
-
- Works in all modern browsers (Chrome, Firefox, Safari, Edge)
|
|
495
|
-
- Uses `dangerouslySetInnerHTML` (supported in all React versions)
|
|
496
|
-
- Server-side rendering compatible
|
|
497
|
-
|
|
498
|
-
## Security Notes
|
|
499
|
-
|
|
500
|
-
1. **Trust your pipeline** - If engine sanitizes, component can trust the data
|
|
501
|
-
2. **DOMPurify recommended** - Use in engine for sanitization
|
|
502
|
-
3. **TipTap content** - Generally safe due to schema control
|
|
503
|
-
4. **External content** - Always sanitize before rendering
|
|
504
|
-
5. **Color marks** - Ensure `class` and `data-variant` attributes are allowed
|
|
505
|
-
|
|
506
|
-
## Summary
|
|
507
|
-
|
|
508
|
-
- **Component is simple** - Just renders, doesn't sanitize
|
|
509
|
-
- **Engine sanitizes** - Once during data preparation
|
|
510
|
-
- **Parser provides tools** - Utilities available but not enforced
|
|
511
|
-
- **Flexible** - Handles strings, arrays, plain and rich text
|
|
512
|
-
- **Semantic** - Smart defaults for headings vs paragraphs
|
|
513
|
-
- **Performant** - Memoized, filters empty content automatically
|
|
514
|
-
|
|
515
|
-
Copy this implementation and adapt it to your needs. The key is keeping the component simple and moving complexity to your engine layer where you have full context and control.
|
package/reference/README.md
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
# Reference Implementations
|
|
2
|
-
|
|
3
|
-
This folder contains production-ready reference implementations for common patterns when working with the semantic parser. These are **not** part of the published npm package but are provided for you to copy and adapt to your project.
|
|
4
|
-
|
|
5
|
-
## Available Components
|
|
6
|
-
|
|
7
|
-
### Text.js
|
|
8
|
-
|
|
9
|
-
A complete, production-ready React component for rendering content extracted by the semantic parser.
|
|
10
|
-
|
|
11
|
-
**Features:**
|
|
12
|
-
- Handles single strings or arrays of paragraphs
|
|
13
|
-
- Smart semantic defaults (headings, paragraphs, divs)
|
|
14
|
-
- Automatic empty content filtering
|
|
15
|
-
- Semantic wrapper components (H1-H6, P, PlainText, Div)
|
|
16
|
-
- Support for color marks and rich formatting
|
|
17
|
-
- **Trusts engine-sanitized data** - No component-level sanitization
|
|
18
|
-
- Simple and lightweight - no performance overhead
|
|
19
|
-
|
|
20
|
-
**Security Model:**
|
|
21
|
-
This component assumes content is **already sanitized by your engine**. It does NOT sanitize HTML itself. See the [Sanitization](#sanitization) section below.
|
|
22
|
-
|
|
23
|
-
**Installation:**
|
|
24
|
-
|
|
25
|
-
1. **Copy the file to your project:**
|
|
26
|
-
```bash
|
|
27
|
-
cp reference/Text.js src/components/Text.js
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
2. **No additional dependencies needed** - Just React
|
|
31
|
-
|
|
32
|
-
3. **Sanitize at engine level** (see [Sanitization](#sanitization))
|
|
33
|
-
|
|
34
|
-
4. **Use in your components:**
|
|
35
|
-
```jsx
|
|
36
|
-
import Text, { H1, P } from './components/Text';
|
|
37
|
-
import { parseContent, mappers } from '@uniweb/semantic-parser';
|
|
38
|
-
|
|
39
|
-
function MyComponent({ document }) {
|
|
40
|
-
const parsed = parseContent(document);
|
|
41
|
-
const hero = mappers.extractors.hero(parsed);
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
<>
|
|
45
|
-
<H1 text={hero.title} />
|
|
46
|
-
<P text={hero.description} />
|
|
47
|
-
</>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
**TypeScript Support:**
|
|
53
|
-
|
|
54
|
-
If using TypeScript, add this type definition file:
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
// Text.d.ts
|
|
58
|
-
import { ReactElement } from 'react';
|
|
59
|
-
|
|
60
|
-
interface TextProps {
|
|
61
|
-
text: string | string[];
|
|
62
|
-
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'div' | 'span';
|
|
63
|
-
html?: boolean;
|
|
64
|
-
className?: string;
|
|
65
|
-
lineAs?: string;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
declare const Text: React.FC<TextProps>;
|
|
69
|
-
export default Text;
|
|
70
|
-
|
|
71
|
-
export const H1: React.FC<Omit<TextProps, 'as'>>;
|
|
72
|
-
export const H2: React.FC<Omit<TextProps, 'as'>>;
|
|
73
|
-
export const H3: React.FC<Omit<TextProps, 'as'>>;
|
|
74
|
-
export const H4: React.FC<Omit<TextProps, 'as'>>;
|
|
75
|
-
export const H5: React.FC<Omit<TextProps, 'as'>>;
|
|
76
|
-
export const H6: React.FC<Omit<TextProps, 'as'>>;
|
|
77
|
-
export const P: React.FC<Omit<TextProps, 'as'>>;
|
|
78
|
-
export const PlainText: React.FC<Omit<TextProps, 'html'>>;
|
|
79
|
-
export const Div: React.FC<Omit<TextProps, 'as'>>;
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Sanitization
|
|
83
|
-
|
|
84
|
-
**IMPORTANT:** This component does NOT sanitize HTML. Sanitization happens at the **engine level**.
|
|
85
|
-
|
|
86
|
-
### Why Engine-Level Sanitization?
|
|
87
|
-
|
|
88
|
-
1. **Performance** - Sanitize once during data preparation, not on every render
|
|
89
|
-
2. **Context-aware** - Engine knows if content is from trusted TipTap or external sources
|
|
90
|
-
3. **Cacheable** - Sanitized content can be memoized
|
|
91
|
-
4. **Clear responsibility** - Engine owns the data pipeline
|
|
92
|
-
|
|
93
|
-
### How to Sanitize
|
|
94
|
-
|
|
95
|
-
Use the parser's built-in utilities in your engine:
|
|
96
|
-
|
|
97
|
-
```javascript
|
|
98
|
-
import { sanitizeHtml } from '@uniweb/semantic-parser/mappers/types';
|
|
99
|
-
import { parseContent, mappers } from '@uniweb/semantic-parser';
|
|
100
|
-
|
|
101
|
-
// In your engine (NOT in the component)
|
|
102
|
-
function prepareHeroData(document) {
|
|
103
|
-
const parsed = parseContent(document);
|
|
104
|
-
const hero = mappers.extractors.hero(parsed);
|
|
105
|
-
|
|
106
|
-
// Sanitize here, before passing to component
|
|
107
|
-
return {
|
|
108
|
-
...hero,
|
|
109
|
-
title: sanitizeHtml(hero.title, {
|
|
110
|
-
allowedTags: ['strong', 'em', 'mark', 'span'],
|
|
111
|
-
allowedAttr: ['class', 'data-variant']
|
|
112
|
-
}),
|
|
113
|
-
description: hero.description.map(p => sanitizeHtml(p))
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Component receives clean data
|
|
118
|
-
const heroData = prepareHeroData(doc);
|
|
119
|
-
<H1 text={heroData.title} /> {/* Already sanitized */}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### When to Sanitize
|
|
123
|
-
|
|
124
|
-
- **Always**: External content, user-generated content
|
|
125
|
-
- **Optional**: Trusted TipTap editor with locked schema
|
|
126
|
-
- **Never needed**: Hard-coded content in your app
|
|
127
|
-
|
|
128
|
-
See [docs/text-component-reference.md](../docs/text-component-reference.md#sanitization-tools) for detailed sanitization guidance.
|
|
129
|
-
|
|
130
|
-
## Customization
|
|
131
|
-
|
|
132
|
-
These reference implementations are designed to be copied and customized for your needs:
|
|
133
|
-
|
|
134
|
-
### Add Custom Styling Props
|
|
135
|
-
|
|
136
|
-
```jsx
|
|
137
|
-
// Add a spacing prop
|
|
138
|
-
const Text = React.memo(({ text, as = 'p', className, spacing = 'normal', ... }) => {
|
|
139
|
-
const spacingClass = spacing !== 'normal' ? `spacing-${spacing}` : '';
|
|
140
|
-
const combinedClass = [className, spacingClass].filter(Boolean).join(' ');
|
|
141
|
-
|
|
142
|
-
// Use combinedClass in rendering
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// Usage
|
|
146
|
-
<P text={paragraphs} spacing="comfortable" />
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### Remove Features You Don't Need
|
|
150
|
-
|
|
151
|
-
If you don't need certain features, simplify the component:
|
|
152
|
-
|
|
153
|
-
- Remove sanitization if you sanitize at engine level
|
|
154
|
-
- Remove wrapper components if you don't use them
|
|
155
|
-
- Remove HTML support if you only render plain text
|
|
156
|
-
- Remove array support if you always use strings
|
|
157
|
-
|
|
158
|
-
## Why Reference Implementations?
|
|
159
|
-
|
|
160
|
-
The semantic parser is a **data transformation library**, not a UI component library. It focuses on parsing and structuring content.
|
|
161
|
-
|
|
162
|
-
However, rendering that content requires common patterns that most projects need. Rather than forcing specific implementations, we provide battle-tested reference code that you can:
|
|
163
|
-
|
|
164
|
-
1. **Copy as-is** - Use immediately without modification
|
|
165
|
-
2. **Customize** - Adapt to your specific needs
|
|
166
|
-
3. **Learn from** - Understand best practices
|
|
167
|
-
4. **Replace** - Use your own implementations
|
|
168
|
-
|
|
169
|
-
This approach:
|
|
170
|
-
- ✅ Keeps the parser lightweight and focused
|
|
171
|
-
- ✅ Gives you full control over rendering
|
|
172
|
-
- ✅ Avoids forcing UI framework choices
|
|
173
|
-
- ✅ Provides working code, not just documentation
|
|
174
|
-
|
|
175
|
-
## Documentation
|
|
176
|
-
|
|
177
|
-
For detailed usage guides, see:
|
|
178
|
-
- [Text Component Reference](../docs/text-component-reference.md) - Complete documentation
|
|
179
|
-
- [Mapping Patterns Guide](../docs/mapping-patterns.md) - Integration examples
|
|
180
|
-
- [API Reference](../docs/api.md) - Parser API documentation
|
|
181
|
-
|
|
182
|
-
## Contributing
|
|
183
|
-
|
|
184
|
-
If you develop improved versions or new reference implementations, consider contributing them back to help other users.
|
|
185
|
-
|
|
186
|
-
Common additions that would be valuable:
|
|
187
|
-
- Vue.js version of Text component
|
|
188
|
-
- Svelte version of Text component
|
|
189
|
-
- Image component for handling image data
|
|
190
|
-
- Link component for handling link objects
|
|
191
|
-
- Video component for media handling
|
|
192
|
-
|
|
193
|
-
## License
|
|
194
|
-
|
|
195
|
-
These reference implementations are provided under the same license as the semantic parser (GPL-3.0-or-later) and can be freely used in your projects.
|