Skip to content

feat(YfmHtmlBlock): add editable preview mode with templates#1146

Draft
makhnatkin wants to merge 3 commits into
mainfrom
feat/yfm-html-block-templates-and-preview
Draft

feat(YfmHtmlBlock): add editable preview mode with templates#1146
makhnatkin wants to merge 3 commits into
mainfrom
feat/yfm-html-block-templates-and-preview

Conversation

@makhnatkin

@makhnatkin makhnatkin commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

Summary by Sourcery

Add editable preview and reusable HTML templates support to YfmHtmlBlock, including UI, persistence, and parsing utilities.

New Features:

  • Introduce an editable preview mode that allows inline editing of HTML block content directly inside the iframe.
  • Add configurable HTML templates support for YfmHtmlBlock, including a templates popup UI with search, selection, and optional adding of new templates stored per user.
  • Expose template configuration options on the YfmHtmlBlock extension and wire them into the demo playground with default templates.

Enhancements:

  • Refactor YfmHtmlBlock view constants into a shared const module and reuse them across components.
  • Integrate local i18n keyset for YfmHtmlBlock UI strings in both English and Russian.

Tests:

  • Add tests for HTML template storage, including localStorage handling and template merging semantics.
  • Add tests for parsing HTML templates from raw input strings into structured template objects.

makhnatkin and others added 3 commits June 14, 2026 19:35
Add a templates button inside the YfmHtmlBlock node that opens a popup
with a searchable list of templates and an "add" action.

- templates are merged from plugin options and localStorage (localStorage
  overrides by id); adding writes only to localStorage, never to markup
- the add form accepts several <template id title> blocks at once
- selecting a template overwrites the block srcdoc
- new `templates` plugin option: { items, showButton, allowAdd }

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds an editable inline preview mode for YfmHtmlBlock along with a template system (including popup UI, storage, parsing, and i18n) and wires it into the demo playground.

File-Level Changes

Change Details Files
Enable inline editing inside the YfmHtmlBlock iframe preview and keep events cleaned up across reloads.
  • Extend YfmHtmlBlockView props/options with editablePreview and onInlineSave callbacks.
  • Track inline editing state via a ref, toggling iframe body contentEditable on double-click when editablePreview is enabled.
  • Persist edited HTML back into node attrs via onInlineSave only when content actually changes.
  • Attach dblclick and blur listeners to the iframe document/body on load and remove them on unload, alongside existing anchor-link handlers.
packages/editor/src/extensions/additional/YfmHtmlBlock/YfmHtmlBlockNodeView/YfmHtmlBlockView.tsx
Introduce HTML template support with a popup UI, localStorage-backed persistence, and parsing utilities, and integrate it into YfmHtmlBlock.
  • Add HtmlTemplate/YfmHtmlBlockTemplatesOptions types and export templates utilities from a new templates module.
  • Create TemplatesPopup React component with search, add-from-textarea, and apply interactions, using new i18n keyset and styling.
  • Implement localStorage-backed read/save of templates plus mergeTemplatesById with tests to safely handle malformed data and unavailable storage.
  • Implement parseTemplates to read one or more blocks or fallback to treating raw input as a single template, with UUID-based ids.
  • Wire templates options into YfmHtmlBlockView to compute effective templates (plugin + stored), show a templates button, and apply selected or newly added templates to srcdoc.
  • packages/editor/src/extensions/additional/YfmHtmlBlock/index.ts
    packages/editor/src/extensions/additional/YfmHtmlBlock/YfmHtmlBlockNodeView/YfmHtmlBlockView.tsx
    packages/editor/src/extensions/additional/YfmHtmlBlock/YfmHtmlBlockNodeView/TemplatesPopup.tsx
    packages/editor/src/extensions/additional/YfmHtmlBlock/YfmHtmlBlockNodeView/TemplatesPopup.scss
    packages/editor/src/extensions/additional/YfmHtmlBlock/templates/types.ts
    packages/editor/src/extensions/additional/YfmHtmlBlock/templates/index.ts
    packages/editor/src/extensions/additional/YfmHtmlBlock/templates/storage.ts
    packages/editor/src/extensions/additional/YfmHtmlBlock/templates/storage.test.ts
    packages/editor/src/extensions/additional/YfmHtmlBlock/templates/parse.ts
    packages/editor/src/extensions/additional/YfmHtmlBlock/templates/parse.test.ts
    packages/editor/src/i18n/yfm-html-block/index.ts
    packages/editor/src/i18n/yfm-html-block/en.json
    packages/editor/src/i18n/yfm-html-block/ru.json Refactor shared constants and wire new templates/editablePreview behaviour into the demo playground.
    • Extract cnYfmHtmlBlock and STOP_EVENT_CLASSNAME into a dedicated const module and re-export from view for reuse in TemplatesPopup and controls.
    • Update Playground demo to provide default templates, enable the templates button and inline editable preview.
    • Add default HTML templates used in the demo to showcase the feature.
    packages/editor/src/extensions/additional/YfmHtmlBlock/YfmHtmlBlockNodeView/YfmHtmlBlockView.tsx
    packages/editor/src/extensions/additional/YfmHtmlBlock/YfmHtmlBlockNodeView/const.ts
    demo/src/components/Playground.tsx
    demo/src/defaults/html-templates.ts
    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

@gravity-ui

gravity-ui Bot commented Jun 14, 2026

Copy link
Copy Markdown

Storybook Deployed

@gravity-ui

gravity-ui Bot commented Jun 14, 2026

Copy link
Copy Markdown

🎭 Playwright Report

@makhnatkin

Copy link
Copy Markdown
Collaborator Author
<template id="solid-header-white" title="Solid header · white cards">
  <div style="display: grid; gap: 12px; padding: 12px; grid-template-columns: repeat(3, 1fr);">
    <div style="grid-column: 1 / -1; padding: 28px 32px; font-size: 30px; font-weight: 800; line-height: 1.1; letter-spacing: -0.5px; color: #fff; background: #2563eb; border-radius: 14px;">Main heading</div>
    <div style="padding: 20px; color: #1f2937; background: #fff; border: 1px solid #e5e7eb; border-radius: 12px;">First</div>
    <div style="padding: 20px; color: #1f2937; background: #fff; border: 1px solid #e5e7eb; border-radius: 12px;">Second</div>
    <div style="padding: 20px; color: #1f2937; background: #fff; border: 1px solid #e5e7eb; border-radius: 12px;">Third</div>
  </div>
</template>

<template id="long-columns" title="Long columns">
  <div style="display: grid; gap: 12px; padding: 12px; grid-template-columns: repeat(3, 1fr); grid-auto-rows: 320px;">
    <div style="padding: 24px; min-height: 320px; color: #0f172a; background: #f8fafc; border-top: 4px solid #0ea5e9; border-radius: 10px;">Column one</div>
    <div style="padding: 24px; min-height: 320px; color: #0f172a; background: #f8fafc; border-top: 4px solid #6366f1; border-radius: 10px;">Column two</div>
    <div style="padding: 24px; min-height: 320px; color: #0f172a; background: #f8fafc; border-top: 4px solid #ec4899; border-radius: 10px;">Column three</div>
  </div>
</template>

<template id="dark" title="Dark theme">
  <div style="display: grid; gap: 12px; padding: 12px; grid-template-columns: repeat(3, 1fr); background: #0b1120; border-radius: 14px;">
    <div style="grid-column: 1 / -1; padding: 24px; font-size: 24px; font-weight: 700; color: #f8fafc; background: #1e293b; border: 1px solid #334155; border-radius: 12px;">Header</div>
    <div style="padding: 20px; color: #cbd5e1; background: #111827; border: 1px solid #334155; border-radius: 12px;">One</div>
    <div style="padding: 20px; color: #cbd5e1; background: #111827; border: 1px solid #334155; border-radius: 12px;">Two</div>
    <div style="padding: 20px; color: #cbd5e1; background: #111827; border: 1px solid #334155; border-radius: 12px;">Three</div>
  </div>
</template>

<template id="festive" title="Festive theme">
  <div style="display: grid; gap: 12px; padding: 12px; grid-template-columns: repeat(3, 1fr); background: #5b0a0a; border: 4px double #ffd700; border-radius: 16px;">
    <div style="grid-column: 1 / -1; padding: 22px; font-size: 26px; font-weight: 800; text-align: center; letter-spacing: 1px; color: #ffd700; text-shadow: 0 1px 0 #7a0000; background: linear-gradient(135deg, #8b0000, #b8860b); border: 3px double #ffd700; border-radius: 12px;">✦ Праздник ✦</div>
    <div style="padding: 18px; text-align: center; color: #fff6d5; background: #7a0000; border: 2px dashed #ffd700; border-radius: 12px;">❖ Узор</div>
    <div style="padding: 18px; text-align: center; color: #7a0000; background: #ffd700; border: 2px solid #8b0000; border-radius: 12px;">❖ Орнамент</div>
    <div style="padding: 18px; text-align: center; color: #fff6d5; background: #7a0000; border: 2px dashed #ffd700; border-radius: 12px;">❖ Золото</div>
    <div style="grid-column: 1 / -1; padding: 14px; text-align: center; font-weight: 700; color: #5b0a0a; background: linear-gradient(90deg, #ffd700, #f0c000, #ffd700); border-radius: 12px;">✦ Festive footer ✦</div>
  </div>
</template>

<template id="minimal" title="Minimal">
  <div style="display: grid; gap: 12px; padding: 12px; grid-template-columns: repeat(2, 1fr); gap: 24px; padding: 24px;">
    <div style="padding: 8px 0 24px; color: #111; background: transparent; border-bottom: 1px solid #111;">Left</div>
    <div style="padding: 8px 0 24px; color: #111; background: transparent; border-bottom: 1px solid #111;">Right</div>
  </div>
</template>

<template id="pastel" title="Pastel cards">
  <div style="display: grid; gap: 12px; padding: 12px; grid-template-columns: repeat(2, 1fr);">
    <div style="padding: 22px; color: #7c2d12; background: #ffedd5; border-radius: 16px;">Peach</div>
    <div style="padding: 22px; color: #065f46; background: #d1fae5; border-radius: 16px;">Mint</div>
    <div style="padding: 22px; color: #5b21b6; background: #ede9fe; border-radius: 16px;">Lavender</div>
    <div style="padding: 22px; color: #075985; background: #e0f2fe; border-radius: 16px;">Sky</div>
  </div>
</template>

<template id="gradient" title="Gradient cards">
  <div style="display: grid; gap: 12px; padding: 12px; grid-template-columns: repeat(3, 1fr);">
    <div style="padding: 26px; color: #fff; font-weight: 600; background: linear-gradient(135deg, #f97316, #db2777); border-radius: 16px;">Sunset</div>
    <div style="padding: 26px; color: #fff; font-weight: 600; background: linear-gradient(135deg, #0ea5e9, #6366f1); border-radius: 16px;">Ocean</div>
    <div style="padding: 26px; color: #fff; font-weight: 600; background: linear-gradient(135deg, #22c55e, #0d9488); border-radius: 16px;">Forest</div>
  </div>
</template>

<template id="neon" title="Neon">
  <div style="display: grid; gap: 12px; padding: 12px; grid-template-columns: repeat(2, 1fr); background: #05010f; border-radius: 14px;">
    <div style="grid-column: 1 / -1; padding: 22px; text-align: center; font-size: 24px; font-weight: 800; letter-spacing: 4px; color: #22d3ee; background: #0a0a1a; border: 2px solid #22d3ee; border-radius: 12px; box-shadow: 0 0 16px rgba(34,211,238,0.7);">CYBER</div>
    <div style="padding: 22px; color: #f0abfc; background: #0a0a1a; border: 2px solid #e879f9; border-radius: 12px; box-shadow: 0 0 14px rgba(232,121,249,0.6);">NODE 01</div>
    <div style="padding: 22px; color: #5eead4; background: #0a0a1a; border: 2px solid #2dd4bf; border-radius: 12px; box-shadow: 0 0 14px rgba(45,212,191,0.6);">NODE 02</div>
  </div>
</template>

<template id="newspaper" title="Newspaper">
  <div style="display: grid; gap: 12px; padding: 12px; grid-template-columns: 2fr 1fr; gap: 0; background: #f4ecd8; border: 2px solid #3a2f1b;">
    <div style="grid-column: 1 / -1; padding: 18px; text-align: center; font-family: Georgia, serif; font-size: 30px; font-weight: 700; letter-spacing: 2px; color: #2b2317; background: #f4ecd8; border-bottom: 3px double #3a2f1b;">THE DAILY GRID</div>
    <div style="padding: 18px; font-family: Georgia, serif; color: #2b2317; background: #f4ecd8; border-right: 1px solid #3a2f1b;">Lead story</div>
    <div style="padding: 18px; font-family: Georgia, serif; color: #2b2317; background: #efe6cf;">Sidebar</div>
  </div>
</template>

<template id="areas-header-footer" title="Header · 3 cols · footer (areas)">
  <div style="display: grid; gap: 12px; padding: 12px; grid-template-columns: repeat(3, 1fr); grid-template-areas: &quot;head head head&quot; &quot;c1 c2 c3&quot; &quot;foot foot foot&quot;;">
    <div style="grid-area: head; padding: 22px; font-size: 22px; font-weight: 700; color: #fff; background: #4338ca; border-radius: 12px;">Header</div>
    <div style="grid-area: c1; padding: 18px; color: #312e81; background: #eef2ff; border-radius: 12px;">One</div>
    <div style="grid-area: c2; padding: 18px; color: #312e81; background: #eef2ff; border-radius: 12px;">Two</div>
    <div style="grid-area: c3; padding: 18px; color: #312e81; background: #eef2ff; border-radius: 12px;">Three</div>
    <div style="grid-area: foot; padding: 16px; color: #fff; background: #6366f1; border-radius: 12px;">Footer</div>
  </div>
</template>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant