@pilotiq/tiptap 3.11.0 → 3.13.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.
package/dist/render.js CHANGED
@@ -26,8 +26,11 @@
26
26
  * / image.src + alt + title + width + height
27
27
  * / tableCell.colspan + rowspan + colwidth (also tableHeader)
28
28
  * / mergeTag.id / mention.id + label + trigger
29
- * custom blocks — render to `<div data-type="..." data-attrs="...">` so
30
- * consumers can replay or style by data-type.
29
+ * default blocks — the `pilotiqBlock` node's built-in types (faq / alert /
30
+ * summary / key-takeaways / pros-cons) render to semantic
31
+ * `<div class="pilotiq-...">` markup; consumers own the CSS.
32
+ * custom blocks — any other type renders to `<div data-type="..."
33
+ * data-attrs="...">` so consumers can replay or style by data-type.
31
34
  */
32
35
  /**
33
36
  * Render Tiptap content to HTML.
@@ -139,9 +142,20 @@ function renderNode(node, opts) {
139
142
  case 'detailsContent': return renderChildren(n, opts);
140
143
  case 'grid': return renderGrid(n, opts);
141
144
  case 'gridColumn': return wrap('div', n, opts);
145
+ case 'keyTakeaways': return labeledBlockHtml('pilotiq-key-takeaways', 'Key takeaways', n, opts);
146
+ case 'summary': return labeledBlockHtml('pilotiq-summary', 'Summary', n, opts);
147
+ case 'faq': return labeledBlockHtml('pilotiq-faq', 'FAQ', n, opts);
148
+ case 'faqItem': return `<div class="pilotiq-faq-item">${renderChildren(n, opts)}</div>`;
149
+ case 'faqQuestion': return `<div class="pilotiq-faq-question"><span class="pilotiq-faq-marker">Q</span><span class="pilotiq-faq-text">${renderChildren(n, opts)}</span></div>`;
150
+ case 'faqAnswer': return `<div class="pilotiq-faq-answer"><span class="pilotiq-faq-marker">A</span><div class="pilotiq-faq-body">${renderChildren(n, opts)}</div></div>`;
151
+ case 'alert': return renderAlertNode(n, opts);
152
+ case 'prosCons': return `<div class="pilotiq-pros-cons">${renderChildren(n, opts)}</div>`;
153
+ case 'prosColumn': return labeledBlockHtml('pilotiq-pros', 'Pros', n, opts);
154
+ case 'consColumn': return labeledBlockHtml('pilotiq-cons', 'Cons', n, opts);
142
155
  case 'mergeTag': return renderMergeTag(n, opts);
143
156
  case 'mention': return renderMention(n, opts);
144
157
  case 'text': return renderText(n);
158
+ case 'pilotiqBlock': return renderPilotiqBlock(n, opts);
145
159
  default:
146
160
  if (opts.renderBlock)
147
161
  return opts.renderBlock(n);
@@ -340,6 +354,28 @@ function clampGridColumnsForRender(raw) {
340
354
  const trunc = Math.trunc(n);
341
355
  return trunc === 3 ? 3 : 2;
342
356
  }
357
+ // ─── Inline content blocks (labelled editable nodes) ─────────────────
358
+ //
359
+ // Mirrors the editor's `renderHTML` (extensions/contentBlocks.ts): a small
360
+ // label above an editable body. Consumer owns the `pilotiq-*` CSS. Covers
361
+ // keyTakeaways / summary / faq / alert / prosCons (+ pros/cons columns).
362
+ function labeledBlockHtml(cssClass, label, n, opts) {
363
+ return (`<div class="${cssClass}">` +
364
+ `<div class="pilotiq-block-label">${escapeHtml(label)}</div>` +
365
+ `<div class="pilotiq-block-body">${renderChildren(n, opts)}</div>` +
366
+ `</div>`);
367
+ }
368
+ const ALERT_NODE_TYPES = new Set(['info', 'warning', 'success', 'tip']);
369
+ const ALERT_NODE_LABEL = { info: 'Info', warning: 'Warning', success: 'Success', tip: 'Tip' };
370
+ function renderAlertNode(n, opts) {
371
+ let type = String(n.attrs?.['type'] ?? '').trim().toLowerCase();
372
+ if (!ALERT_NODE_TYPES.has(type))
373
+ type = 'info';
374
+ return (`<div class="pilotiq-alert pilotiq-alert-${type}" role="note">` +
375
+ `<div class="pilotiq-block-label">${ALERT_NODE_LABEL[type]}</div>` +
376
+ `<div class="pilotiq-block-body">${renderChildren(n, opts)}</div>` +
377
+ `</div>`);
378
+ }
343
379
  // ─── Merge tags + mentions ───────────────────────────────────────────
344
380
  /**
345
381
  * Render a `mergeTag` atom — either substitute the value from
@@ -386,6 +422,104 @@ function renderCustomBlock(n) {
386
422
  const inner = n.content ? renderChildren(n, {}) : '';
387
423
  return `<div data-type="${escapeAttr(type)}"${dataAttrs}>${inner}</div>`;
388
424
  }
425
+ // ─── Default blocks (pilotiqBlock node, keyed on attrs.blockType) ─────
426
+ //
427
+ // The custom-block node (`pilotiqBlock`) carries `blockType` + `blockData`.
428
+ // The blocks shipped by default in `RichTextField` (FAQ / Alert / Summary /
429
+ // Key takeaways / Pros & cons) render to semantic HTML here; every other
430
+ // (host-defined) block type falls back to `opts.renderBlock` or the generic
431
+ // `data-attrs` div. Consumers own the CSS for the `pilotiq-*` classes.
432
+ function renderPilotiqBlock(n, opts) {
433
+ const attrs = (n.attrs ?? {});
434
+ const blockType = String(attrs['blockType'] ?? '');
435
+ let data = attrs['blockData'];
436
+ if (typeof data === 'string') {
437
+ try {
438
+ data = JSON.parse(data);
439
+ }
440
+ catch {
441
+ data = {};
442
+ }
443
+ }
444
+ const d = (data && typeof data === 'object' ? data : {});
445
+ switch (blockType) {
446
+ case 'faq': return renderFaqBlock(d);
447
+ case 'alert': return renderAlertBlock(d);
448
+ case 'summary': return renderSummaryBlock(d);
449
+ case 'key-takeaways': return renderKeyTakeawaysBlock(d);
450
+ case 'pros-cons': return renderProsConsBlock(d);
451
+ default:
452
+ if (opts.renderBlock)
453
+ return opts.renderBlock(n);
454
+ return renderCustomBlock(n);
455
+ }
456
+ }
457
+ /** Escape a plain-text field value and split blank-line-separated paragraphs. */
458
+ function blockParagraphs(raw) {
459
+ const s = String(raw ?? '').trim();
460
+ if (s === '')
461
+ return '';
462
+ return s
463
+ .split(/\n{2,}/)
464
+ .map((p) => `<p>${escapeHtml(p).replace(/\n/g, '<br>')}</p>`)
465
+ .join('');
466
+ }
467
+ /** Coerce a field value to a clean `string[]` (TagsInput stores an array). */
468
+ function blockStringList(raw) {
469
+ if (!Array.isArray(raw))
470
+ return [];
471
+ return raw.map((x) => String(x ?? '').trim()).filter((s) => s !== '');
472
+ }
473
+ function renderFaqBlock(d) {
474
+ const items = Array.isArray(d['items']) ? d['items'] : [];
475
+ const body = items
476
+ .map((it) => {
477
+ const q = escapeHtml(String(it['question'] ?? '').trim());
478
+ if (q === '')
479
+ return '';
480
+ const a = blockParagraphs(it['answer']);
481
+ return `<details class="pilotiq-faq-item"><summary>${q}</summary><div class="pilotiq-faq-answer">${a}</div></details>`;
482
+ })
483
+ .join('');
484
+ if (body === '')
485
+ return '';
486
+ return `<div class="pilotiq-faq">${body}</div>`;
487
+ }
488
+ const ALERT_TYPES = new Set(['info', 'warning', 'success', 'tip']);
489
+ function renderAlertBlock(d) {
490
+ let type = String(d['type'] ?? '').trim().toLowerCase();
491
+ if (!ALERT_TYPES.has(type))
492
+ type = 'info';
493
+ const content = blockParagraphs(d['content']);
494
+ return `<div class="pilotiq-alert pilotiq-alert-${type}" role="note">${content}</div>`;
495
+ }
496
+ function renderSummaryBlock(d) {
497
+ const content = blockParagraphs(d['content']);
498
+ if (content === '')
499
+ return '';
500
+ return (`<div class="pilotiq-summary">` +
501
+ `<div class="pilotiq-summary-label">Summary</div>` +
502
+ `<div class="pilotiq-summary-body">${content}</div>` +
503
+ `</div>`);
504
+ }
505
+ function renderKeyTakeawaysBlock(d) {
506
+ const points = blockStringList(d['points']);
507
+ if (points.length === 0)
508
+ return '';
509
+ const lis = points.map((p) => `<li>${escapeHtml(p)}</li>`).join('');
510
+ return (`<div class="pilotiq-key-takeaways">` +
511
+ `<div class="pilotiq-key-takeaways-label">Key takeaways</div>` +
512
+ `<ul>${lis}</ul>` +
513
+ `</div>`);
514
+ }
515
+ function renderProsConsBlock(d) {
516
+ const pros = blockStringList(d['pros']);
517
+ const cons = blockStringList(d['cons']);
518
+ if (pros.length === 0 && cons.length === 0)
519
+ return '';
520
+ const column = (label, cls, items) => `<div class="pilotiq-${cls}"><h4>${label}</h4><ul>${items.map((i) => `<li>${escapeHtml(i)}</li>`).join('')}</ul></div>`;
521
+ return `<div class="pilotiq-pros-cons">${column('Pros', 'pros', pros)}${column('Cons', 'cons', cons)}</div>`;
522
+ }
389
523
  // ─── Escapers + sanitizers ───────────────────────────────────────────
390
524
  function escapeHtml(s) {
391
525
  return s.replace(/[&<>"']/g, ch => HTML_ESCAPES[ch] ?? ch);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pilotiq/tiptap",
3
- "version": "3.11.0",
3
+ "version": "3.13.0",
4
4
  "description": "Tiptap rich-text editor adapter for @pilotiq/pilotiq — slash menu, draggable blocks, custom-block API",
5
5
  "license": "MIT",
6
6
  "repository": {