{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "article-editor-toolbar",
  "title": "Article Editor Toolbar",
  "type": "registry:page",
  "files": [
    {
      "path": "packages/registry-angular/blocks/article-editor-toolbar/article-editor-toolbar.component.ts",
      "content": "// Medium and Substack style rich text formatting toolbar and article drafting canvas with\n// typography controls, syntax highlighted code blocks, callouts, and publishing drawer.\n// Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, DestroyRef, Input, inject, signal } from '@angular/core'\nimport {\n  AlignCenter,\n  AlignLeft,\n  AlignRight,\n  AlertTriangle,\n  ArrowLeft,\n  ArrowUpRight,\n  Bold,\n  Bookmark,\n  BookOpen,\n  Check,\n  CheckCheck,\n  ChevronDown,\n  Cloud,\n  Code,\n  Copy,\n  ExternalLink,\n  Eye,\n  FileCode,\n  Heart,\n  HelpCircle,\n  ImageIcon,\n  Info,\n  Italic,\n  Layers,\n  Lightbulb,\n  Link2,\n  List,\n  ListOrdered,\n  LucideAngularModule,\n  Maximize2,\n  MessageSquare,\n  Minus,\n  MoreHorizontal,\n  PenLine,\n  Plus,\n  Quote,\n  Redo2,\n  RotateCcw,\n  Send,\n  Share2,\n  Sparkles,\n  Strikethrough,\n  Tag,\n  Terminal,\n  Type,\n  Underline,\n  Undo2,\n  Upload,\n  Volume2,\n  X,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardFooterComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiInputComponent } from '@/ui/input/input.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\nimport { UiTextareaComponent } from '@/ui/textarea/textarea.component'\n\nexport type ArticleTextStyle = 'paragraph' | 'h1' | 'h2' | 'h3'\nexport type ArticleCalloutType = 'tip' | 'warning' | 'info'\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-article-editor-toolbar, [ui-article-editor-toolbar]',\n  standalone: true,\n  // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardFooterComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiInputComponent,\n    UiSeparatorComponent,\n    UiTextareaComponent,\n  ],\n  template: `\n    <div data-slot=\"article-editor-toolbar\" [class]=\"rootClass\">\n      <!-- Top Sticky Application Header Bar -->\n      <header class=\"border-border bg-background/95 sticky top-0 z-40 border-b backdrop-blur-md\">\n        <div class=\"mx-auto flex h-14 max-w-6xl items-center justify-between gap-2 overflow-x-auto px-4 sm:px-6\">\n          <!-- Left: Publication & Save Status -->\n          <div class=\"flex items-center gap-3\">\n            <div class=\"flex items-center gap-2\">\n              <div class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n                <lucide-icon [img]=\"BookOpen\" class=\"size-4\" />\n              </div>\n              <div class=\"hidden min-w-0 truncate whitespace-nowrap sm:block\">\n                <span class=\"text-foreground text-sm font-semibold tracking-tight\">{{ publicationName }}</span>\n                <span class=\"text-muted-foreground ml-1.5 text-xs\">/ Editor</span>\n              </div>\n            </div>\n\n            <div ui-separator orientation=\"vertical\" class=\"hidden h-4 sm:block\"></div>\n\n            <button\n              type=\"button\"\n              class=\"hover:bg-muted/60 text-muted-foreground hover:text-foreground flex min-h-6 items-center gap-1.5 rounded-md px-2 py-1 text-xs transition-colors\"\n              (click)=\"triggerManualSave()\"\n              title=\"Click to save now\"\n            >\n              @if (!isAutosaving()) {\n                <lucide-icon [img]=\"Cloud\" class=\"text-success size-3.5\" />\n              } @else {\n                <lucide-icon [img]=\"RotateCcw\" class=\"text-warning size-3.5 animate-spin\" />\n              }\n              <span class=\"hidden md:inline\">{{ lastSavedText() }}</span>\n              <span class=\"md:hidden\">Saved</span>\n            </button>\n          </div>\n\n          <!-- Center: Live Story Statistics -->\n          <div class=\"text-muted-foreground hidden items-center gap-2 text-xs md:flex\">\n            <span ui-badge variant=\"outline\" class=\"font-normal\">{{ wordCount().toLocaleString() }} words</span>\n            <span>·</span>\n            <span>{{ readingTimeMinutes() }} min read</span>\n          </div>\n\n          <!-- Right: Actions & Publishing Controls -->\n          <div class=\"flex items-center gap-2\">\n            <div class=\"hidden items-center gap-0.5 sm:flex\">\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-8\"\n                (click)=\"showToast('Undo action')\"\n                title=\"Undo (⌘Z)\"\n              >\n                <lucide-icon [img]=\"Undo2\" class=\"size-4\" />\n              </button>\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-8\"\n                (click)=\"showToast('Redo action')\"\n                title=\"Redo (⇧⌘Z)\"\n              >\n                <lucide-icon [img]=\"Redo2\" class=\"size-4\" />\n              </button>\n            </div>\n\n            <div ui-separator orientation=\"vertical\" class=\"hidden h-4 sm:block\"></div>\n\n            <button\n              ui-button\n              variant=\"outline\"\n              size=\"sm\"\n              [class]=\"previewToggleClass\"\n              (click)=\"isPreviewMode.set(!isPreviewMode())\"\n            >\n              <lucide-icon [img]=\"Eye\" class=\"size-3.5\" />\n              <span>{{ isPreviewMode() ? 'Edit Story' : 'Preview Article' }}</span>\n            </button>\n\n            <button\n              ui-button\n              aria-label=\"Close publish modal\"\n              size=\"sm\"\n              class=\"h-8 gap-1.5 text-xs font-semibold shadow-xs\"\n              (click)=\"isPublishModalOpen.set(true)\"\n            >\n              <lucide-icon [img]=\"Send\" class=\"size-3.5\" />\n              <span>Publish Story</span>\n            </button>\n          </div>\n        </div>\n      </header>\n\n      <!-- Floating / Sticky Formatting Toolbar (Only in Editor Mode) -->\n      @if (!isPreviewMode()) {\n        <div class=\"sticky top-16 z-30 mx-auto mt-4 max-w-3xl px-4 transition-colors duration-200\">\n          <div\n            class=\"border-border bg-card/95 flex flex-wrap items-center justify-between gap-1 rounded-xl border p-1.5 shadow-sm backdrop-blur-md\"\n          >\n            <!-- Group 1: Typography Block Selector -->\n            <div class=\"flex items-center gap-1\">\n              <div class=\"relative\">\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  class=\"text-foreground hover:bg-muted h-8 gap-1 px-2 text-xs font-medium\"\n                  (click)=\"isStyleDropdownOpen.set(!isStyleDropdownOpen())\"\n                >\n                  <lucide-icon [img]=\"Type\" class=\"text-muted-foreground size-3.5\" />\n                  <span class=\"capitalize\">{{ styleLabel() }}</span>\n                  <lucide-icon [img]=\"ChevronDown\" class=\"text-muted-foreground size-3\" />\n                </button>\n\n                @if (isStyleDropdownOpen()) {\n                  <div\n                    class=\"border-border bg-popover absolute top-full left-0 z-50 mt-1.5 w-40 rounded-lg border p-1 shadow-md\"\n                  >\n                    <button type=\"button\" [class]=\"styleOptionClass('paragraph')\" (click)=\"pickTextStyle('paragraph')\">\n                      <span>Paragraph</span>\n                      <span class=\"text-muted-foreground text-xs uppercase\">Body</span>\n                    </button>\n                    <button type=\"button\" [class]=\"styleOptionClass('h1')\" (click)=\"pickTextStyle('h1')\">\n                      <span class=\"text-sm font-bold\">Heading 1</span>\n                      <span class=\"text-muted-foreground text-xs\">H1</span>\n                    </button>\n                    <button type=\"button\" [class]=\"styleOptionClass('h2')\" (click)=\"pickTextStyle('h2')\">\n                      <span class=\"text-xs font-semibold\">Heading 2</span>\n                      <span class=\"text-muted-foreground text-xs\">H2</span>\n                    </button>\n                    <button type=\"button\" [class]=\"styleOptionClass('h3')\" (click)=\"pickTextStyle('h3')\">\n                      <span class=\"text-xs font-medium\">Heading 3</span>\n                      <span class=\"text-muted-foreground text-xs\">H3</span>\n                    </button>\n                  </div>\n                }\n              </div>\n\n              <div ui-separator orientation=\"vertical\" class=\"mx-0.5 h-4\"></div>\n\n              <!-- Group 2: Inline Formatting -->\n              <div class=\"flex items-center gap-0.5\">\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  [class]=\"inlineToggleClass(isBoldActive(), 'bg-muted text-primary font-bold')\"\n                  (click)=\"toggleBold()\"\n                  title=\"Bold (⌘B)\"\n                >\n                  <lucide-icon [img]=\"Bold\" class=\"size-3.5\" />\n                </button>\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  [class]=\"inlineToggleClass(isItalicActive(), 'bg-muted text-primary')\"\n                  (click)=\"toggleItalic()\"\n                  title=\"Italic (⌘I)\"\n                >\n                  <lucide-icon [img]=\"Italic\" class=\"size-3.5\" />\n                </button>\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  [class]=\"inlineToggleClass(isUnderlineActive(), 'bg-muted text-primary')\"\n                  (click)=\"toggleUnderline()\"\n                  title=\"Underline (⌘U)\"\n                >\n                  <lucide-icon [img]=\"Underline\" class=\"size-3.5\" />\n                </button>\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  [class]=\"inlineToggleClass(isStrikeActive(), 'bg-muted text-primary')\"\n                  (click)=\"toggleStrike()\"\n                  title=\"Strikethrough (⇧⌘X)\"\n                >\n                  <lucide-icon [img]=\"Strikethrough\" class=\"size-3.5\" />\n                </button>\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  [class]=\"inlineToggleClass(isInlineCodeActive(), 'bg-muted text-primary font-mono')\"\n                  (click)=\"toggleInlineCode()\"\n                  title=\"Inline Code (⌘E)\"\n                >\n                  <lucide-icon [img]=\"Code\" class=\"size-3.5\" />\n                </button>\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  class=\"text-muted-foreground hover:text-foreground size-8\"\n                  (click)=\"isLinkModalOpen.set(true)\"\n                  title=\"Insert Link (⌘K)\"\n                >\n                  <lucide-icon [img]=\"Link2\" class=\"size-3.5\" />\n                </button>\n              </div>\n            </div>\n\n            <!-- Group 3: Block Elements & Media -->\n            <div class=\"flex items-center gap-1\">\n              <div ui-separator orientation=\"vertical\" class=\"mx-0.5 h-4\"></div>\n\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-8\"\n                (click)=\"showToast('Blockquote formatted')\"\n                title=\"Blockquote (⌘⇧.)\"\n              >\n                <lucide-icon [img]=\"Quote\" class=\"size-3.5\" />\n              </button>\n\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-8\"\n                (click)=\"cycleCallout()\"\n                title=\"Toggle Callout Box\"\n              >\n                <lucide-icon [img]=\"Lightbulb\" class=\"text-warning size-3.5\" />\n              </button>\n\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-8\"\n                (click)=\"showToast('Code block activated')\"\n                title=\"Code Block (\\`\\`\\`)\"\n              >\n                <lucide-icon [img]=\"FileCode\" class=\"size-3.5\" />\n              </button>\n\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-8\"\n                (click)=\"showToast('Bullet list toggled')\"\n                title=\"Bullet List (⌘⇧8)\"\n              >\n                <lucide-icon [img]=\"List\" class=\"size-3.5\" />\n              </button>\n\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-8\"\n                (click)=\"showToast('Numbered list toggled')\"\n                title=\"Numbered List (⌘⇧7)\"\n              >\n                <lucide-icon [img]=\"ListOrdered\" class=\"size-3.5\" />\n              </button>\n\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-8\"\n                (click)=\"showToast('Horizontal divider inserted')\"\n                title=\"Divider (---)\"\n              >\n                <lucide-icon [img]=\"Minus\" class=\"size-3.5\" />\n              </button>\n\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-8\"\n                (click)=\"showToast('Image upload dialog simulated')\"\n                title=\"Upload Media\"\n              >\n                <lucide-icon [img]=\"ImageIcon\" class=\"size-3.5\" />\n              </button>\n\n              <div ui-separator orientation=\"vertical\" class=\"mx-0.5 h-4\"></div>\n\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"sm\"\n                class=\"text-primary hover:bg-primary/10 h-8 gap-1 px-2 text-xs font-medium\"\n                (click)=\"handleAiAssist()\"\n                title=\"AI Writing Assistant\"\n              >\n                <lucide-icon [img]=\"Sparkles\" class=\"size-3.5\" />\n                <span class=\"hidden sm:inline\">AI Assist</span>\n              </button>\n            </div>\n          </div>\n        </div>\n      }\n\n      <!-- MAIN ARTICLE DRAFTING CANVAS / READER PREVIEW -->\n      <main class=\"mx-auto max-w-3xl px-4 py-8 sm:px-6 sm:py-12\">\n        @if (isPublishedSuccess()) {\n          <div\n            class=\"border-success/30 bg-success/10 text-success mb-8 flex items-center justify-between rounded-lg border p-4\"\n          >\n            <div class=\"flex items-center gap-3\">\n              <lucide-icon [img]=\"CheckCheck\" class=\"text-success size-5\" />\n              <div>\n                <p class=\"text-sm font-semibold\">Story Live on The Engineering Digest</p>\n                <p class=\"text-muted-foreground text-xs\">URL: {{ canonicalUrl() }}</p>\n              </div>\n            </div>\n            <button ui-button size=\"sm\" variant=\"outline\" class=\"h-8 text-xs\" (click)=\"isPublishedSuccess.set(false)\">\n              Dismiss\n            </button>\n          </div>\n        }\n\n        <article class=\"space-y-6\">\n          <div class=\"flex items-center justify-between\">\n            <span ui-badge variant=\"secondary\" class=\"gap-1 px-2.5 py-0.5 text-xs font-medium tracking-wide\">\n              <lucide-icon [img]=\"Tag\" class=\"text-muted-foreground size-3\" />\n              {{ topicTag() }}\n            </span>\n\n            @if (isPreviewMode()) {\n              <div class=\"flex items-center gap-1.5\">\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  [class]=\"readerFontToggleClass\"\n                  (click)=\"toggleReaderFontSize()\"\n                >\n                  <lucide-icon [img]=\"Type\" class=\"mr-1 size-3\" />\n                  {{ readerFontSize() === 'large' ? 'Standard text' : 'Larger text' }}\n                </button>\n              </div>\n            }\n          </div>\n\n          @if (!isPreviewMode()) {\n            <div class=\"space-y-2\">\n              <ui-textarea\n                [value]=\"articleTitle()\"\n                (valueChange)=\"articleTitle.set($event)\"\n                [rows]=\"2\"\n                class=\"text-foreground placeholder:text-muted-foreground/40 w-full resize-none border-0 bg-transparent p-0 text-3xl font-bold tracking-tight focus-visible:ring-0 focus-visible:outline-hidden sm:text-4xl lg:text-5xl\"\n                placeholder=\"Title...\"\n              />\n            </div>\n          } @else {\n            <h1 [class]=\"previewTitleClass\">{{ articleTitle() }}</h1>\n          }\n\n          @if (!isPreviewMode()) {\n            <div>\n              <ui-textarea\n                [value]=\"articleSubtitle()\"\n                (valueChange)=\"articleSubtitle.set($event)\"\n                [rows]=\"2\"\n                class=\"text-muted-foreground placeholder:text-muted-foreground/40 w-full resize-none border-0 bg-transparent p-0 text-lg font-normal focus-visible:ring-0 focus-visible:outline-hidden sm:text-xl\"\n                placeholder=\"Add a subtitle...\"\n              />\n            </div>\n          } @else {\n            <p [class]=\"previewSubtitleClass\">{{ articleSubtitle() }}</p>\n          }\n\n          <!-- Author / Byline Bar -->\n          <div class=\"flex flex-wrap items-center justify-between gap-4 py-2\">\n            <div class=\"flex items-center gap-3\">\n              <div\n                class=\"bg-primary/15 text-primary flex size-10 items-center justify-center rounded-full text-sm font-semibold\"\n              >\n                AR\n              </div>\n              <div>\n                <div class=\"flex items-center gap-2\">\n                  <span class=\"text-foreground text-sm font-semibold\">{{ authorName }}</span>\n                  <span class=\"text-muted-foreground text-xs\">{{ authorHandle }}</span>\n                  @if (isPreviewMode()) {\n                    <button\n                      ui-button\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      class=\"text-primary hover:bg-primary/10 h-6 px-2 text-xs\"\n                      (click)=\"toggleFollow()\"\n                    >\n                      {{ isFollowing() ? 'Following' : 'Follow' }}\n                    </button>\n                  }\n                </div>\n                <p class=\"text-muted-foreground text-xs\">\n                  Published in <span class=\"text-foreground font-medium\">{{ publicationName }}</span> · Oct 24, 2024 ·\n                  {{ readingTimeMinutes() }} min read\n                </p>\n              </div>\n            </div>\n\n            <div class=\"flex items-center gap-1\">\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"sm\"\n                class=\"text-muted-foreground hover:text-foreground h-8 gap-1.5 text-xs\"\n                (click)=\"showToast('Audio version playing (6 min narration)')\"\n              >\n                <lucide-icon [img]=\"Volume2\" class=\"size-3.5\" />\n                <span class=\"hidden sm:inline\">Listen</span>\n              </button>\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                [class]=\"bookmarkClass\"\n                (click)=\"toggleBookmark()\"\n                title=\"Bookmark story\"\n              >\n                <lucide-icon [img]=\"Bookmark\" class=\"size-4\" />\n              </button>\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-8\"\n                (click)=\"showToast('Link copied to clipboard')\"\n                title=\"Share article\"\n              >\n                <lucide-icon [img]=\"Share2\" class=\"size-4\" />\n              </button>\n            </div>\n          </div>\n\n          <div ui-separator class=\"my-6\"></div>\n\n          <!-- ARTICLE BODY PROSE -->\n          <div [class]=\"proseClass\">\n            <p>\n              For nearly a decade, the standard recipe for building a frontend design system was predictable: spin up a\n              private or public npm monorepo, bundle fifty UI components with Rollup or Vite, and force every\n              application team to install\n              <code class=\"bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-xs\"\n                >&#64;acme/design-system</code\n              >\n              as a heavyweight runtime dependency.\n            </p>\n\n            <!-- Stylized Callout Box -->\n            <div [class]=\"calloutClass\">\n              <div class=\"flex items-start gap-3\">\n                <div class=\"mt-0.5 shrink-0\">\n                  @switch (calloutType()) {\n                    @case ('tip') {\n                      <lucide-icon [img]=\"Lightbulb\" class=\"text-warning size-5\" />\n                    }\n                    @case ('warning') {\n                      <lucide-icon [img]=\"AlertTriangle\" class=\"text-destructive size-5\" />\n                    }\n                    @default {\n                      <lucide-icon [img]=\"Info\" class=\"text-info size-5\" />\n                    }\n                  }\n                </div>\n                <div class=\"space-y-1\">\n                  <div class=\"flex items-center justify-between\">\n                    <h4 class=\"text-sm font-semibold tracking-tight\">{{ calloutTitle() }}</h4>\n                    @if (!isPreviewMode()) {\n                      <div class=\"flex items-center gap-1\">\n                        <button type=\"button\" [class]=\"calloutTabClass('tip')\" (click)=\"setCalloutType('tip')\">\n                          Tip\n                        </button>\n                        <button type=\"button\" [class]=\"calloutTabClass('warning')\" (click)=\"setCalloutType('warning')\">\n                          Warn\n                        </button>\n                        <button type=\"button\" [class]=\"calloutTabClass('info')\" (click)=\"setCalloutType('info')\">\n                          Info\n                        </button>\n                      </div>\n                    }\n                  </div>\n                  <p class=\"text-muted-foreground text-sm leading-normal\">\n                    When UI primitives are distributed via semver npm packages, updating a single button variant\n                    requires a patch release, a dependency bump, and a potential cascade of peer-dependency conflicts\n                    across twenty consuming micro-frontends.\n                  </p>\n                </div>\n              </div>\n            </div>\n\n            <h2 class=\"text-foreground pt-4 text-2xl font-bold tracking-tight sm:text-3xl\">\n              1. The \"Own Your Code\" Revolution\n            </h2>\n\n            <p>\n              The\n              <a\n                [href]=\"linkUrl()\"\n                target=\"_blank\"\n                rel=\"noreferrer\"\n                class=\"text-primary hover:text-primary/80 font-medium underline underline-offset-4 transition-colors\"\n              >\n                {{ linkText() }}\n              </a>\n              inverts this dynamic completely. Instead of consuming a closed npm black box, developers pull atomic,\n              clean TypeScript source files directly into their repository via CLI tools like\n              <code class=\"bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-xs\">shadcn-vue</code> or\n              <code class=\"bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-xs\">uipkge</code>.\n            </p>\n\n            <!-- Syntax-Highlighted Code Block -->\n            <div class=\"border-border bg-muted/40 my-6 overflow-hidden rounded-xl border font-mono text-xs shadow-xs\">\n              <div class=\"border-border bg-card flex items-center justify-between border-b px-4 py-2\">\n                <div class=\"flex items-center gap-2\">\n                  <button type=\"button\" [class]=\"codeTabClass('json')\" (click)=\"activeCodeTab.set('json')\">\n                    <lucide-icon [img]=\"FileCode\" class=\"text-warning size-3.5\" />\n                    components.json\n                  </button>\n                  <button type=\"button\" [class]=\"codeTabClass('bash')\" (click)=\"activeCodeTab.set('bash')\">\n                    <lucide-icon [img]=\"Terminal\" class=\"text-success size-3.5\" />\n                    Terminal CLI\n                  </button>\n                </div>\n\n                <div class=\"flex items-center gap-2\">\n                  <span ui-badge variant=\"outline\" class=\"h-5 px-1.5 font-mono text-xs uppercase\">\n                    {{ activeCodeTab() === 'json' ? 'JSON' : 'BASH' }}\n                  </span>\n                  <button\n                    ui-button\n                    variant=\"ghost\"\n                    size=\"sm\"\n                    class=\"text-muted-foreground hover:text-foreground h-7 gap-1 px-2 text-xs\"\n                    (click)=\"copyCodeSnippet()\"\n                  >\n                    @if (isCodeBlockCopied()) {\n                      <lucide-icon [img]=\"Check\" class=\"text-success size-3\" />\n                    } @else {\n                      <lucide-icon [img]=\"Copy\" class=\"size-3\" />\n                    }\n                    <span>{{ isCodeBlockCopied() ? 'Copied' : 'Copy' }}</span>\n                  </button>\n                </div>\n              </div>\n\n              <div class=\"overflow-x-auto p-4 leading-relaxed\">\n                @if (activeCodeTab() === 'json') {\n                  <pre class=\"text-foreground/90\"><code><span class=\"text-muted-foreground\">{{ '{' }}</span>{{ '\n  ' }}<span class=\"text-primary font-semibold\">\"$schema\"</span>: <span class=\"text-success\">\"https://uipkge.dev/schema.json\"</span>,{{ '\n  ' }}<span class=\"text-primary font-semibold\">\"style\"</span>: <span class=\"text-success\">\"new-york\"</span>,{{ '\n  ' }}<span class=\"text-primary font-semibold\">\"rsc\"</span>: <span class=\"text-warning\">true</span>,{{ '\n  ' }}<span class=\"text-primary font-semibold\">\"aliases\"</span>: <span class=\"text-muted-foreground\">{{ '{' }}</span>{{ '\n    ' }}<span class=\"text-primary\">\"components\"</span>: <span class=\"text-success\">\"@/components/ui\"</span>,{{ '\n    ' }}<span class=\"text-primary\">\"utils\"</span>: <span class=\"text-success\">\"@/lib/utils\"</span>,{{ '\n    ' }}<span class=\"text-primary\">\"blocks\"</span>: <span class=\"text-success\">\"@/components/blocks\"</span>{{ '\n  ' }}<span class=\"text-muted-foreground\">{{ '}' }}</span>{{ '\n' }}<span class=\"text-muted-foreground\">{{ '}' }}</span></code></pre>\n                } @else {\n                  <pre\n                    class=\"text-foreground/90\"\n                  ><code><span class=\"text-muted-foreground\"># Add rich article drafting block to your project</span>{{ '\n' }}<span class=\"text-primary font-semibold\">npx</span> shadcn-vue@latest add https://uipkge.dev/r/vue/article-editor-toolbar.json <span class=\"text-success\">-y</span></code></pre>\n                }\n              </div>\n            </div>\n\n            <blockquote class=\"border-primary bg-muted/20 my-8 rounded-r-lg border-l-4 py-3 pr-4 pl-6 italic\">\n              <p class=\"text-foreground/90 text-base font-medium sm:text-lg\">\n                \"The component registry model inverts the dependency tree. Instead of depending on an external\n                maintainer's release cadence, your team owns every line of component markup, adapting it to your\n                product's exact accessibility and branding needs without vendor lock-in.\"\n              </p>\n              <footer class=\"text-muted-foreground mt-2 text-xs font-normal not-italic\">\n                — Guillermo Rauch, CEO at Vercel\n              </footer>\n            </blockquote>\n\n            <h2 class=\"text-foreground pt-4 text-2xl font-semibold tracking-tight sm:text-3xl\">\n              2. Core Architectural Advantages\n            </h2>\n\n            <p>\n              When evaluating the switch from packaged distributions to registry generation, teams consistently report\n              three major velocity unlocks:\n            </p>\n\n            <ol class=\"space-y-3 pl-1\">\n              <li class=\"flex items-start gap-3\">\n                <span\n                  class=\"bg-primary/10 text-primary flex size-6 shrink-0 items-center justify-center rounded-full text-xs font-semibold\"\n                >\n                  1\n                </span>\n                <div>\n                  <strong class=\"text-foreground font-semibold\">Zero Runtime Overhead:</strong>\n                  <span class=\"text-muted-foreground ml-1\">\n                    Unused component variants and dead code branches are automatically pruned during application\n                    tree-shaking.\n                  </span>\n                </div>\n              </li>\n              <li class=\"flex items-start gap-3\">\n                <span\n                  class=\"bg-primary/10 text-primary flex size-6 shrink-0 items-center justify-center rounded-full text-xs font-semibold\"\n                >\n                  2\n                </span>\n                <div>\n                  <strong class=\"text-foreground font-semibold\">Direct Source Control:</strong>\n                  <span class=\"text-muted-foreground ml-1\">\n                    Audit, modify, and patch WCAG accessibility tags directly inside your repository without waiting for\n                    upstream PR merges.\n                  </span>\n                </div>\n              </li>\n              <li class=\"flex items-start gap-3\">\n                <span\n                  class=\"bg-primary/10 text-primary flex size-6 shrink-0 items-center justify-center rounded-full text-xs font-semibold\"\n                >\n                  3\n                </span>\n                <div>\n                  <strong class=\"text-foreground font-semibold\">Native OKLCH Token Integration:</strong>\n                  <span class=\"text-muted-foreground ml-1\">\n                    Colors and elevation variables adapt directly across modern Tailwind CSS v4 design systems.\n                  </span>\n                </div>\n              </li>\n            </ol>\n\n            <!-- Embedded Media Figure -->\n            <div class=\"border-border bg-card my-8 overflow-hidden rounded-xl border shadow-xs\">\n              <div\n                class=\"from-primary/10 via-muted to-primary/5 relative flex h-52 items-center justify-center bg-gradient-to-br sm:h-64\"\n              >\n                <div class=\"space-y-2 p-6 text-center\">\n                  <div\n                    class=\"bg-card border-border text-primary inline-flex size-12 items-center justify-center rounded-xl border shadow-xs\"\n                  >\n                    <lucide-icon [img]=\"Layers\" class=\"size-6\" />\n                  </div>\n                  <h4 class=\"text-foreground text-sm font-semibold\">Unbundled Registry Architecture Flow</h4>\n                  <p class=\"text-muted-foreground max-w-sm text-xs\">\n                    Registry CLI pulls atomic SFC files directly into src/components/ui\n                  </p>\n                </div>\n                <div class=\"absolute top-3 right-3 flex items-center gap-1.5\">\n                  <span ui-badge variant=\"outline\" class=\"bg-card text-xs\">Figure 1.0</span>\n                  <button\n                    ui-button\n                    aria-label=\"Expand media view\"\n                    variant=\"ghost\"\n                    size=\"icon\"\n                    class=\"bg-card hover:bg-muted size-7 transition-[colors,transform] duration-150 active:scale-95\"\n                    (click)=\"showToast('Expanded media view')\"\n                  >\n                    <lucide-icon [img]=\"Maximize2\" class=\"size-3.5\" />\n                  </button>\n                </div>\n              </div>\n              <div class=\"border-border bg-card border-t p-3\">\n                <p class=\"text-muted-foreground text-center text-xs italic\">\n                  Figure 1: Monolithic npm package distribution vs. direct registry source ownership.\n                </p>\n              </div>\n            </div>\n\n            <div class=\"text-muted-foreground my-10 flex items-center justify-center gap-2\">\n              <span class=\"bg-border size-1 rounded-full\"></span>\n              <span class=\"bg-muted-foreground/40 size-1.5 rounded-full\"></span>\n              <span class=\"bg-border size-1 rounded-full\"></span>\n            </div>\n\n            <p>\n              The shift from monolithic npm packages to unbundled UI registries is not just a tooling trend; it\n              represents a fundamental re-alignment of software ownership, empowering engineering teams to build\n              resilient interfaces that evolve with their product.\n            </p>\n          </div>\n        </article>\n\n        @if (isPreviewMode()) {\n          <div\n            class=\"border-border bg-card/95 sticky bottom-6 z-30 mx-auto mt-12 max-w-lg rounded-full border p-2 shadow-lg backdrop-blur-md\"\n          >\n            <div class=\"flex items-center justify-between px-3\">\n              <div class=\"flex items-center gap-4\">\n                <button\n                  type=\"button\"\n                  class=\"text-foreground hover:text-primary flex min-h-6 items-center gap-1.5 text-xs font-semibold transition-colors\"\n                  (click)=\"clap()\"\n                >\n                  <lucide-icon [img]=\"Heart\" class=\"fill-destructive/20 text-destructive size-4\" />\n                  <span>{{ clapCount() }}</span>\n                </button>\n\n                <button\n                  type=\"button\"\n                  class=\"text-muted-foreground hover:text-foreground flex min-h-6 items-center gap-1.5 text-xs transition-colors\"\n                  (click)=\"showToast('Responses panel opened (24 responses)')\"\n                >\n                  <lucide-icon [img]=\"MessageSquare\" class=\"size-4\" />\n                  <span>24</span>\n                </button>\n              </div>\n\n              <div class=\"flex items-center gap-2\">\n                <button\n                  ui-button\n                  size=\"sm\"\n                  variant=\"ghost\"\n                  class=\"text-muted-foreground hover:text-foreground h-7 text-xs\"\n                  (click)=\"toggleBookmark()\"\n                >\n                  <lucide-icon [img]=\"Bookmark\" class=\"mr-1 size-3.5\" />\n                  <span>Bookmark</span>\n                </button>\n                <button\n                  ui-button\n                  aria-label=\"Close publish modal\"\n                  size=\"sm\"\n                  class=\"h-7 text-xs font-semibold\"\n                  (click)=\"isPublishModalOpen.set(true)\"\n                >\n                  Publish\n                </button>\n              </div>\n            </div>\n          </div>\n        }\n      </main>\n\n      <!-- PUBLISH SETTINGS MODAL / DIALOG -->\n      @if (isPublishModalOpen()) {\n        <div class=\"fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-xs\">\n          <div\n            ui-card\n            class=\"border-border bg-card animate-in fade-in-0 zoom-in-95 w-full max-w-xl shadow-sm duration-200\"\n          >\n            <div ui-card-header class=\"flex flex-row items-center justify-between pb-4\">\n              <div>\n                <h3 ui-card-title class=\"text-lg font-semibold\">Publish Story to Publication</h3>\n                <p ui-card-description class=\"text-xs\">\n                  Configure publication metadata, topic tags, and newsletter distribution.\n                </p>\n              </div>\n              <button\n                ui-button\n                aria-label=\"Close publish modal\"\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-8\"\n                (click)=\"isPublishModalOpen.set(false)\"\n              >\n                <lucide-icon [img]=\"X\" class=\"size-4\" />\n              </button>\n            </div>\n\n            <div ui-card-content class=\"space-y-4 pt-0 text-sm\">\n              <div class=\"border-border bg-muted/30 rounded-lg border p-3\">\n                <h4 class=\"text-foreground line-clamp-1 text-sm font-semibold\">{{ articleTitle() }}</h4>\n                <p class=\"text-muted-foreground mt-0.5 line-clamp-1 text-xs\">{{ articleSubtitle() }}</p>\n                <div class=\"text-muted-foreground mt-2 flex items-center gap-2 text-xs\">\n                  <span>Author: {{ authorName }}</span>\n                  <span>·</span>\n                  <span>{{ wordCount() }} words</span>\n                  <span>·</span>\n                  <span>{{ readingTimeMinutes() }} min read</span>\n                </div>\n              </div>\n\n              <div class=\"space-y-1.5\">\n                <label class=\"text-foreground text-xs font-medium\">Topic Tags (up to 5)</label>\n                <div class=\"border-input bg-background flex flex-wrap gap-1.5 rounded-lg border p-2\">\n                  @for (tag of publishTags(); track tag) {\n                    <span ui-badge variant=\"secondary\" class=\"gap-1 pr-1 text-xs\">\n                      {{ tag }}\n                      <button\n                        [attr.aria-label]=\"'Remove tag ' + tag\"\n                        type=\"button\"\n                        class=\"text-muted-foreground hover:text-foreground\"\n                        (click)=\"handleRemoveTag(tag)\"\n                      >\n                        <lucide-icon [img]=\"X\" class=\"size-3\" />\n                      </button>\n                    </span>\n                  }\n                  <input\n                    [value]=\"newTagInput()\"\n                    (input)=\"newTagInput.set($any($event.target).value)\"\n                    type=\"text\"\n                    class=\"placeholder:text-muted-foreground min-w-[100px] flex-1 border-0 bg-transparent p-0 text-xs focus:ring-0 focus:outline-hidden\"\n                    placeholder=\"Add a tag...\"\n                    (keydown.enter)=\"handleAddTagFromKeyboard($event)\"\n                  />\n                </div>\n              </div>\n\n              <div class=\"space-y-1.5\">\n                <div class=\"flex items-center justify-between\">\n                  <label class=\"text-foreground text-xs font-medium\">SEO Meta Description</label>\n                  <span class=\"text-muted-foreground text-xs\">{{ seoDescription().length }}/160</span>\n                </div>\n                <ui-textarea\n                  [value]=\"seoDescription()\"\n                  (valueChange)=\"seoDescription.set($event)\"\n                  [rows]=\"2\"\n                  class=\"resize-none text-xs\"\n                  placeholder=\"Brief summary for search engines and social cards...\"\n                />\n              </div>\n\n              <div class=\"space-y-1.5\">\n                <label class=\"text-foreground text-xs font-medium\">Canonical URL</label>\n                <ui-input\n                  [value]=\"canonicalUrl()\"\n                  (valueChange)=\"canonicalUrl.set($event)\"\n                  class=\"h-8 font-mono text-xs\"\n                />\n              </div>\n\n              <div class=\"border-border bg-muted/20 flex items-center justify-between rounded-lg border p-3\">\n                <div class=\"space-y-0.5\">\n                  <p class=\"text-foreground text-xs font-semibold\">Broadcast to Subscribers</p>\n                  <p class=\"text-muted-foreground text-xs\">\n                    Send as an instant newsletter email to 14,200 active readers.\n                  </p>\n                </div>\n                <input\n                  [checked]=\"sendNewsletter()\"\n                  (change)=\"sendNewsletter.set($any($event.target).checked)\"\n                  type=\"checkbox\"\n                  class=\"border-border accent-primary size-4 cursor-pointer rounded\"\n                />\n              </div>\n\n              <div class=\"flex items-center gap-4 text-xs\">\n                <label class=\"flex cursor-pointer items-center gap-2\">\n                  <input\n                    [checked]=\"publishSchedule() === 'now'\"\n                    (change)=\"publishSchedule.set('now')\"\n                    type=\"radio\"\n                    value=\"now\"\n                    name=\"schedule\"\n                    class=\"accent-primary cursor-pointer\"\n                  />\n                  <span class=\"text-foreground font-medium\">Publish Now</span>\n                </label>\n                <label class=\"flex cursor-pointer items-center gap-2\">\n                  <input\n                    [checked]=\"publishSchedule() === 'schedule'\"\n                    (change)=\"publishSchedule.set('schedule')\"\n                    type=\"radio\"\n                    value=\"schedule\"\n                    name=\"schedule\"\n                    class=\"accent-primary cursor-pointer\"\n                  />\n                  <span class=\"text-muted-foreground\">Schedule for later</span>\n                </label>\n              </div>\n            </div>\n\n            <div ui-card-footer class=\"border-border flex items-center justify-end gap-2 border-t pt-4\">\n              <button\n                ui-button\n                aria-label=\"Close publish modal\"\n                variant=\"outline\"\n                size=\"sm\"\n                class=\"text-xs\"\n                (click)=\"isPublishModalOpen.set(false)\"\n              >\n                Cancel\n              </button>\n              <button ui-button size=\"sm\" class=\"gap-1.5 text-xs font-semibold shadow-xs\" (click)=\"handlePublish()\">\n                <lucide-icon [img]=\"Send\" class=\"size-3.5\" />\n                <span>{{ publishSchedule() === 'now' ? 'Confirm & Publish Now' : 'Schedule Story' }}</span>\n              </button>\n            </div>\n          </div>\n        </div>\n      }\n\n      <!-- INSERT LINK MODAL -->\n      @if (isLinkModalOpen()) {\n        <div class=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 backdrop-blur-xs\">\n          <div ui-card class=\"border-border bg-card w-full max-w-sm shadow-xl\">\n            <div ui-card-header class=\"pb-3\">\n              <h3 ui-card-title class=\"text-sm font-semibold\">Insert Hyperlink</h3>\n              <p ui-card-description class=\"text-xs\">Add an external reference link</p>\n            </div>\n            <div ui-card-content class=\"space-y-3 pt-0\">\n              <div class=\"space-y-1\">\n                <label class=\"text-muted-foreground text-xs\">Link Text</label>\n                <ui-input [value]=\"linkText()\" (valueChange)=\"linkText.set($event)\" class=\"h-8 text-xs\" />\n              </div>\n              <div class=\"space-y-1\">\n                <label class=\"text-muted-foreground text-xs\">Destination URL</label>\n                <ui-input\n                  [value]=\"linkUrl()\"\n                  (valueChange)=\"linkUrl.set($event)\"\n                  class=\"h-8 font-mono text-xs\"\n                  placeholder=\"https://\"\n                />\n              </div>\n            </div>\n            <div ui-card-footer class=\"border-border flex items-center justify-end gap-2 border-t pt-3\">\n              <button ui-button variant=\"ghost\" size=\"sm\" class=\"h-8 text-xs\" (click)=\"isLinkModalOpen.set(false)\">\n                Cancel\n              </button>\n              <button\n                ui-button\n                size=\"sm\"\n                class=\"h-8 text-xs\"\n                (click)=\"isLinkModalOpen.set(false); showToast('Link inserted successfully!')\"\n              >\n                Apply Link\n              </button>\n            </div>\n          </div>\n        </div>\n      }\n\n      <!-- INTERACTIVE TOAST NOTIFICATION -->\n      @if (toastMessage()) {\n        <div\n          class=\"border-border bg-popover text-popover-foreground animate-in fade-in slide-in-from-bottom-2 fixed right-5 bottom-5 z-50 flex items-center gap-2 rounded-lg border px-3.5 py-2.5 text-xs font-medium shadow-lg duration-150\"\n        >\n          <lucide-icon [img]=\"Check\" class=\"text-primary size-3.5\" />\n          <span>{{ toastMessage() }}</span>\n        </div>\n      }\n    </div>\n  `,\n})\nexport class UiArticleEditorToolbarComponent {\n  protected readonly AlignCenter = AlignCenter\n  protected readonly AlignLeft = AlignLeft\n  protected readonly AlignRight = AlignRight\n  protected readonly AlertTriangle = AlertTriangle\n  protected readonly ArrowLeft = ArrowLeft\n  protected readonly ArrowUpRight = ArrowUpRight\n  protected readonly Bold = Bold\n  protected readonly Bookmark = Bookmark\n  protected readonly BookOpen = BookOpen\n  protected readonly Check = Check\n  protected readonly CheckCheck = CheckCheck\n  protected readonly ChevronDown = ChevronDown\n  protected readonly Cloud = Cloud\n  protected readonly Code = Code\n  protected readonly Copy = Copy\n  protected readonly ExternalLink = ExternalLink\n  protected readonly Eye = Eye\n  protected readonly FileCode = FileCode\n  protected readonly Heart = Heart\n  protected readonly HelpCircle = HelpCircle\n  protected readonly ImageIcon = ImageIcon\n  protected readonly Info = Info\n  protected readonly Italic = Italic\n  protected readonly Layers = Layers\n  protected readonly Lightbulb = Lightbulb\n  protected readonly Link2 = Link2\n  protected readonly List = List\n  protected readonly ListOrdered = ListOrdered\n  protected readonly Maximize2 = Maximize2\n  protected readonly MessageSquare = MessageSquare\n  protected readonly Minus = Minus\n  protected readonly MoreHorizontal = MoreHorizontal\n  protected readonly PenLine = PenLine\n  protected readonly Plus = Plus\n  protected readonly Quote = Quote\n  protected readonly Redo2 = Redo2\n  protected readonly RotateCcw = RotateCcw\n  protected readonly Send = Send\n  protected readonly Share2 = Share2\n  protected readonly Sparkles = Sparkles\n  protected readonly Strikethrough = Strikethrough\n  protected readonly Tag = Tag\n  protected readonly Terminal = Terminal\n  protected readonly Type = Type\n  protected readonly Underline = Underline\n  protected readonly Undo2 = Undo2\n  protected readonly Upload = Upload\n  protected readonly Volume2 = Volume2\n  protected readonly X = X\n\n  @Input('class') className?: string\n\n  // View Mode\n  readonly isPreviewMode = signal(false)\n\n  // Story Content State\n  readonly articleTitle = signal('The Death of the Monolithic Component Package')\n  readonly articleSubtitle = signal('Why the unbundled registry architecture is replacing npm packages for UI systems.')\n  readonly topicTag = signal('Architecture & UI')\n  readonly authorName = 'Alex Rivera'\n  readonly authorHandle = '@arivera_eng'\n  readonly publicationName = 'The Engineering Digest'\n  readonly lastSavedText = signal('Draft Saved 2m ago')\n  readonly isAutosaving = signal(false)\n\n  // Reader Engagement State\n  readonly clapCount = signal(342)\n  readonly isBookmarked = signal(false)\n  readonly isFollowing = signal(false)\n  readonly readerFontSize = signal<'normal' | 'large'>('normal')\n\n  // Toolbar Formatter State\n  readonly activeTextStyle = signal<ArticleTextStyle>('paragraph')\n  readonly isStyleDropdownOpen = signal(false)\n  readonly isBoldActive = signal(false)\n  readonly isItalicActive = signal(false)\n  readonly isUnderlineActive = signal(false)\n  readonly isStrikeActive = signal(false)\n  readonly isInlineCodeActive = signal(false)\n\n  // Active Block Elements in Canvas\n  readonly calloutType = signal<ArticleCalloutType>('tip')\n  readonly isCodeBlockCopied = signal(false)\n  readonly activeCodeTab = signal<'json' | 'bash'>('json')\n\n  // Modals / Drawers\n  readonly isPublishModalOpen = signal(false)\n  readonly isLinkModalOpen = signal(false)\n  readonly isPublishedSuccess = signal(false)\n  readonly linkUrl = signal('https://uipkge.dev')\n  readonly linkText = signal('unbundled registry architecture')\n\n  // Publish Settings State\n  readonly publishTags = signal(['Web Development', 'Design Systems', 'Vue', 'React', 'Frontend'])\n  readonly newTagInput = signal('')\n  readonly canonicalUrl = signal('https://theengineeringdigest.io/p/unbundled-ui-registries')\n  readonly seoDescription = signal(\n    'A deep dive into why unbundled UI component registries like shadcn and uipkge are displacing monolithic npm component libraries across engineering teams.',\n  )\n  readonly sendNewsletter = signal(true)\n  readonly publishSchedule = signal<'now' | 'schedule'>('now')\n\n  // Toast Feedback\n  readonly toastMessage = signal<string | null>(null)\n  private toastTimer: ReturnType<typeof setTimeout> | undefined\n\n  constructor() {\n    inject(DestroyRef).onDestroy(() => clearTimeout(this.toastTimer))\n  }\n\n  get rootClass(): string {\n    return cn('bg-background text-foreground min-h-screen', this.className)\n  }\n\n  wordCount(): number {\n    const fullText = `${this.articleTitle()} ${this.articleSubtitle()} For nearly a decade the standard recipe for building a frontend design system was predictable spin up a private or public npm monorepo bundle fifty UI components with Rollup or Vite and force every application team to install as a heavyweight runtime dependency The component registry model inverts this entire dynamic Instead of consuming a closed npm black box developers pull atomic clean TypeScript source files directly into their repository Zero Runtime Overhead Direct Accessibility Ownership Native Token Sync`\n    return fullText.trim().split(/\\s+/).filter(Boolean).length + 1380\n  }\n\n  readingTimeMinutes(): number {\n    return Math.max(1, Math.ceil(this.wordCount() / 220))\n  }\n\n  get previewToggleClass(): string {\n    return cn(\n      'h-8 gap-1.5 text-xs font-medium',\n      this.isPreviewMode() && 'bg-secondary text-secondary-foreground border-primary/40',\n    )\n  }\n\n  get bookmarkClass(): string {\n    return cn('text-muted-foreground hover:text-foreground size-8', this.isBookmarked() && 'text-primary')\n  }\n\n  get readerFontToggleClass(): string {\n    return cn('text-muted-foreground h-7 text-xs', this.readerFontSize() === 'large' && 'bg-muted text-foreground')\n  }\n\n  get previewTitleClass(): string {\n    return cn(\n      'text-foreground text-3xl leading-tight font-bold tracking-tight sm:text-4xl lg:text-5xl',\n      this.readerFontSize() === 'large' && 'text-4xl sm:text-5xl lg:text-6xl',\n    )\n  }\n\n  get previewSubtitleClass(): string {\n    return cn(\n      'text-muted-foreground text-lg leading-relaxed font-normal sm:text-xl',\n      this.readerFontSize() === 'large' && 'text-xl sm:text-2xl',\n    )\n  }\n\n  get proseClass(): string {\n    return cn(\n      'text-foreground space-y-6 leading-relaxed',\n      this.readerFontSize() === 'large' ? 'text-lg sm:text-xl' : 'text-base sm:text-lg',\n    )\n  }\n\n  get calloutClass(): string {\n    return cn(\n      'my-8 rounded-xl border p-4.5 transition-colors sm:p-5',\n      this.calloutType() === 'tip'\n        ? 'text-foreground border-warning/30 bg-warning/10'\n        : this.calloutType() === 'warning'\n          ? 'text-foreground border-destructive/30 bg-destructive/10'\n          : 'text-foreground border-info/30 bg-info/10',\n    )\n  }\n\n  calloutTitle(): string {\n    const t = this.calloutType()\n    return t === 'tip'\n      ? 'Key Architectural Takeaway'\n      : t === 'warning'\n        ? 'Dependency Fragility Warning'\n        : 'Registry Distribution Note'\n  }\n\n  calloutTabClass(type: ArticleCalloutType): string {\n    return cn(\n      'text-muted-foreground hover:text-foreground min-h-6 rounded px-1.5 py-0.5 text-xs',\n      this.calloutType() === type && 'text-foreground font-semibold',\n    )\n  }\n\n  codeTabClass(tab: 'json' | 'bash'): string {\n    return cn(\n      'flex min-h-6 items-center gap-1.5 rounded px-2 py-1 text-xs font-medium transition-colors',\n      this.activeCodeTab() === tab ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  styleLabel(): string {\n    const s = this.activeTextStyle()\n    return s === 'paragraph' ? 'Normal Text' : s.toUpperCase()\n  }\n\n  styleOptionClass(style: ArticleTextStyle): string {\n    return cn(\n      'hover:bg-muted flex w-full items-center justify-between rounded-md px-2.5 py-1.5 text-left text-xs transition-colors',\n      this.activeTextStyle() === style ? 'text-primary bg-muted/60 font-semibold' : 'text-popover-foreground',\n    )\n  }\n\n  inlineToggleClass(active: boolean, activeClass: string): string {\n    return cn('size-8', active ? activeClass : 'text-muted-foreground hover:text-foreground')\n  }\n\n  showToast(msg: string): void {\n    clearTimeout(this.toastTimer)\n    this.toastMessage.set(msg)\n    this.toastTimer = setTimeout(() => this.toastMessage.set(null), 3000)\n  }\n\n  triggerManualSave(): void {\n    this.isAutosaving.set(true)\n    this.lastSavedText.set('Saving changes...')\n    setTimeout(() => {\n      this.isAutosaving.set(false)\n      this.lastSavedText.set('Draft Saved just now')\n      this.showToast('All draft changes saved to cloud.')\n    }, 600)\n  }\n\n  handleAddTag(): void {\n    const trimmed = this.newTagInput().trim()\n    if (trimmed && !this.publishTags().includes(trimmed)) {\n      this.publishTags.update((prev) => [...prev, trimmed])\n      this.newTagInput.set('')\n    }\n  }\n\n  handleAddTagFromKeyboard(event: Event): void {\n    event.preventDefault()\n    this.handleAddTag()\n  }\n\n  handleRemoveTag(tagToRemove: string): void {\n    this.publishTags.update((prev) => prev.filter((t) => t !== tagToRemove))\n  }\n\n  copyCodeSnippet(): void {\n    this.isCodeBlockCopied.set(true)\n    this.showToast('Code snippet copied to clipboard!')\n    setTimeout(() => this.isCodeBlockCopied.set(false), 2000)\n  }\n\n  handlePublish(): void {\n    this.isPublishModalOpen.set(false)\n    this.isPublishedSuccess.set(true)\n    this.showToast('🎉 Story published successfully to The Engineering Digest!')\n  }\n\n  setCalloutType(type: ArticleCalloutType): void {\n    this.calloutType.set(type)\n  }\n\n  insertCallout(type: ArticleCalloutType): void {\n    this.calloutType.set(type)\n    this.showToast(`Applied ${type.toUpperCase()} callout formatting.`)\n  }\n\n  cycleCallout(): void {\n    const current = this.calloutType()\n    this.insertCallout(current === 'tip' ? 'warning' : current === 'warning' ? 'info' : 'tip')\n  }\n\n  pickTextStyle(style: ArticleTextStyle): void {\n    this.activeTextStyle.set(style)\n    this.isStyleDropdownOpen.set(false)\n  }\n\n  toggleBold(): void {\n    const next = !this.isBoldActive()\n    this.isBoldActive.set(next)\n    this.showToast(next ? 'Bold applied' : 'Bold removed')\n  }\n\n  toggleItalic(): void {\n    const next = !this.isItalicActive()\n    this.isItalicActive.set(next)\n    this.showToast(next ? 'Italic applied' : 'Italic removed')\n  }\n\n  toggleUnderline(): void {\n    const next = !this.isUnderlineActive()\n    this.isUnderlineActive.set(next)\n    this.showToast(next ? 'Underline applied' : 'Underline removed')\n  }\n\n  toggleStrike(): void {\n    this.isStrikeActive.update((v) => !v)\n    this.showToast('Strikethrough toggled')\n  }\n\n  toggleInlineCode(): void {\n    this.isInlineCodeActive.update((v) => !v)\n    this.showToast('Inline code toggled')\n  }\n\n  toggleBookmark(): void {\n    const next = !this.isBookmarked()\n    this.isBookmarked.set(next)\n    this.showToast(next ? 'Saved to bookmarks' : 'Removed from bookmarks')\n  }\n\n  toggleFollow(): void {\n    const next = !this.isFollowing()\n    this.isFollowing.set(next)\n    this.showToast(next ? 'Following Alex Rivera' : 'Unfollowed')\n  }\n\n  toggleReaderFontSize(): void {\n    this.readerFontSize.update((v) => (v === 'normal' ? 'large' : 'normal'))\n  }\n\n  clap(): void {\n    this.clapCount.update((v) => v + 1)\n    this.showToast(`Clapped! (${this.clapCount()} claps total)`)\n  }\n\n  handleAiAssist(): void {\n    this.showToast('✨ AI polished paragraph for conciseness and punchy cadence.')\n  }\n}\n",
      "type": "registry:page",
      "target": "~/src/app/components/blocks/article-editor-toolbar/article-editor-toolbar.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/article-editor-toolbar/index.ts",
      "content": "export {\n  UiArticleEditorToolbarComponent,\n  type ArticleCalloutType,\n  type ArticleTextStyle,\n} from './article-editor-toolbar.component'\n",
      "type": "registry:page",
      "target": "~/src/app/components/blocks/article-editor-toolbar/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/input.json",
    "https://uipkge.dev/r/angular/separator.json",
    "https://uipkge.dev/r/angular/textarea.json"
  ],
  "description": "Medium and Substack style rich text formatting toolbar and article drafting canvas with typography controls, syntax highlighted code blocks, callouts, and publishing drawer.",
  "categories": [
    "media",
    "app",
    "content"
  ]
}