macron-cms 3.0.0 → 5.0.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +338 -25
  3. data/app/assets/stylesheets/macron/cms/application.css +1 -1
  4. data/app/controllers/concerns/macron/cms/api_serialization.rb +79 -0
  5. data/app/controllers/concerns/macron/cms/slug_redirectable.rb +27 -0
  6. data/app/controllers/macron/cms/api/v1/article_contents_controller.rb +221 -0
  7. data/app/controllers/macron/cms/api/v1/articles_controller.rb +140 -66
  8. data/app/controllers/macron/cms/api/v1/content_types_controller.rb +3 -1
  9. data/app/controllers/macron/cms/article_contents_controller.rb +44 -10
  10. data/app/controllers/macron/cms/articles_controller.rb +4 -3
  11. data/app/controllers/macron/cms/content_types_controller.rb +2 -0
  12. data/app/controllers/macron/cms/dashboard_controller.rb +1 -1
  13. data/app/helpers/macron/cms/content_helper.rb +1 -1
  14. data/app/helpers/macron/cms/fields_helper.rb +14 -0
  15. data/app/models/macron/cms/article.rb +45 -15
  16. data/app/models/macron/cms/article_content.rb +24 -0
  17. data/app/models/macron/cms/content.rb +27 -0
  18. data/app/services/macron/cms/inline_asset_attacher.rb +70 -0
  19. data/app/services/macron/cms/remote_asset_attacher.rb +16 -6
  20. data/app/views/macron/cms/articles/_content_blocks_section.html.erb +494 -199
  21. data/app/views/macron/cms/articles/_form.html.erb +10 -2
  22. data/app/views/macron/cms/articles/_table.html.erb +3 -3
  23. data/app/views/macron/cms/articles/show.html.erb +3 -1
  24. data/app/views/macron/cms/content_types/index.html.erb +30 -0
  25. data/app/views/macron/cms/content_types/show.html.erb +34 -0
  26. data/config/routes.rb +12 -1
  27. data/db/migrate/20261029000001_add_convertible_to_to_macron_cms_contents.rb +5 -0
  28. data/db/migrate/20261029120001_add_featured_image_to_macron_cms_contents.rb +5 -0
  29. data/db/migrate/20261029120002_add_featured_image_blob_to_macron_cms_articles.rb +8 -0
  30. data/db/migrate/20261029140000_create_friendly_id_slugs.rb +26 -0
  31. data/db/migrate/20261029150000_remove_name_from_macron_cms_articles.rb +6 -0
  32. data/db/migrate/20261029160000_add_published_at_to_macron_cms_articles.rb +14 -0
  33. data/db/seeds.rb +3 -2
  34. data/lib/macron/cms/version.rb +1 -1
  35. data/lib/tasks/macron/cms_tasks.rake +6 -0
  36. metadata +11 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 774dc3fc574e2fcd3d5e200b370bd6886718539fbab312cfe17632d1b18d153b
4
- data.tar.gz: 003dbe77a32e518917f3f2c342319a27c701bad690d9a6acfbf009efaef91ce2
3
+ metadata.gz: d33f2ffc21a60f2fed407e0b1aac793f203a304203a6d58c0a5c87680e7d33e3
4
+ data.tar.gz: 27faeca2f7e1f4dc031e9786ec1739f19b59e2d088a0a7f7f5cdcc1f43bdb9b6
5
5
  SHA512:
6
- metadata.gz: 75063868fbbbb013c613f307de1c6d790066eb146f9add543dbb3961df14f6ba98502a81a433e1adbd27bbd9304f774dac0dfc592564d92883b66bd7ea1e9b0a
7
- data.tar.gz: 66ffde9ab34f2cf35255183e7a396f29db811100e858ee56aa6d85a1e565ab13dbe30f66d18e1ea61edfa82312b6bbb0b4d7ed25464674f2c1a0c3c882bbee05
6
+ metadata.gz: 16aabb8fa8b2be75dc01a49f0c5e2267d85266a2c80566d6730095e8300df6cdb544962a1184a07fae8632145893f9a6307395762a1e16396b6fabcb402fef4b
7
+ data.tar.gz: 8ce4de32d67626c86c5025bc78a07f1598a8cfc26d5ce3e980cf00581b8484b6976f0e9dfea91b8ea97f3df17c7542779c319151f186664256a9accdcc5c804d
data/README.md CHANGED
@@ -1,7 +1,10 @@
1
1
  # MacronCMS
2
2
 
3
- [![Gem Version](https://img.shields.io/badge/version-3.0.0-emerald.svg)](#version)
3
+ [![Gem Version](https://img.shields.io/badge/version-5.0.0-emerald.svg)](#version)
4
4
  [![Rails 8+](https://img.shields.io/badge/rails-8%2B-red.svg)](https://rubyonrails.org)
5
+ [![React](https://img.shields.io/badge/React-20232A?logo=react&logoColor=61DAFB)](https://react.dev)
6
+ [![Three.js](https://img.shields.io/badge/Three.js-black?logo=three.js&logoColor=white)](https://threejs.org)
7
+ [![MCP Server](https://img.shields.io/badge/MCP-Server-purple.svg)](https://github.com/macron1-automations/macron-cms-mcp)
5
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](MIT-LICENSE)
6
9
 
7
10
  A simple, flexible CMS engine for Ruby on Rails, designed for building highly interactive content with dynamic content blocks, declarative schemas, and ViewComponent widgets.
@@ -18,13 +21,16 @@ A simple, flexible CMS engine for Ruby on Rails, designed for building highly in
18
21
 
19
22
  ### 🛠️ Dedicated Admin Dashboard (`/cms`)
20
23
  - **Modern Zinc Aesthetic**: Clean, responsive monochrome layout with sticky sidebar navigation, top breadcrumbs, metric cards, quick-action buttons, and flash notification banners.
21
- - **Article CRUD & Friendly URLs**: Manage articles (`Macron::Cms::Article`) out-of-the-box with dedicated create, edit, show, and delete interfaces. Automatic slug generation via `friendly_id` produces clean SEO URLs.
24
+ - **Article CRUD & Canonical Slugs**: Manage articles (`Macron::Cms::Article`) out-of-the-box with dedicated create, edit, show, and delete interfaces. Automatic slug generation via `friendly_id` produces clean SEO URLs.
25
+ - **Publication Scheduling & Custom Chronology**: Configure exact `published_at` timestamps with an integrated datetime picker. Articles default to descending publication order (`published_at: :desc, created_at: :desc`), enabling scheduled publishing and custom sorting.
26
+ - **Slug History & Canonical 301 Redirects**: Automatic FriendlyId slug history tracking. Host controllers use the `Macron::Cms::SlugRedirectable` concern to automatically issue permanent 301 redirects when an article is accessed via a previous slug, maintaining SEO equity and preventing 404s.
22
27
  - **Deletion Safeguards**: Browser confirmation dialogs on delete actions with article titles to prevent accidental deletion.
23
28
  - **Host App Integration**: Quick links between the CMS admin console and public-facing host views.
24
29
 
25
30
  ### 🧱 Dynamic Article Content Blocks & In-Place Inserters
26
31
  - **Multiple Blocks Per Article**: Associate an arbitrary series of content blocks with an article (`Macron::Cms::ArticleContent`). Add multiple blocks of the same content type (e.g. headings, markdown blocks, images) with distinct values.
27
32
  - **In-Place Block Inserters**: Add new blocks directly above, between, or below existing blocks on the Article management screen with one click.
33
+ - **Declarative In-Place Block Conversion**: Convert blocks between compatible content types directly in-place (e.g. `image` <-> `three_js_image`) via the `convertible_to` schema definition. Conversions update records in place without duplicating Active Storage file attachments, and preserve existing and dormant JSONB `payload` attributes non-destructively.
28
34
  - **Inline Turbo Stream Updates**: Add, edit, and reorder content blocks with instantaneous Turbo Stream updates—no full-page reloads.
29
35
  - **Dynamic Field Generation**: Field controls are generated dynamically based on the content type schema and validated before saving into the block's `payload` JSONB column.
30
36
  - **8 Extensible Field Kinds**:
@@ -38,6 +44,11 @@ A simple, flexible CMS engine for Ruby on Rails, designed for building highly in
38
44
  - `latex`: CodeMirror LaTeX editor with `stex` syntax mode and KaTeX mathematical equation preview.
39
45
  - **File Attachment Purging**: Remove or replace existing Active Storage attachments directly from the block editor with a dedicated purge checkbox, keeping payloads clean without recreating blocks.
40
46
 
47
+ ### 🖼️ Automatic Featured Image Bubbling
48
+ - **Declarative Tagging**: Tag image-bearing content types with `featured_image: true` in their YAML schemas (`config/content_types/*.yml`).
49
+ - **Write-Time Image Bubbling**: Active Storage image attachments on eligible blocks automatically bubble up to `article.featured_image_blob` upon saving or deleting blocks, pointing to the first attached image in block sort order.
50
+ - **Zero-Join Editorial Streams**: Eager-load and render article feeds or homepage streams (`@articles = Macron::Cms::Article.includes(:featured_image_blob)`) and access `article.featured_image` without expensive multi-table joins across heterogeneous content blocks.
51
+
41
52
  ### 🎨 Article Background & Typography Customization
42
53
  - **Custom Background Colors & Images**: Configure an optional background color or attach a background image directly to any article via Active Storage, with live image previews and one-click purge.
43
54
  - **CSS Background Display Controls**: Fine-tune background display settings per article:
@@ -52,12 +63,15 @@ A simple, flexible CMS engine for Ruby on Rails, designed for building highly in
52
63
  - **CodeMirror Markdown & LaTeX Editors**: Integrated CodeMirror editors in the CMS admin for writing Markdown content and raw LaTeX expressions with syntax highlighting (`stex` mode).
53
64
  - **Fast KaTeX Math Rendering**: Embedded KaTeX engine renders complex LaTeX equations, mathematical notation, and symbols instantaneously in both admin previews and front-end layouts.
54
65
  - **Formatted Markdown Previews**: Formatted GitHub Flavored Markdown (GFM) previews rendered directly on the article management page.
55
- - **Sandboxed Embeds**: Render third-party media and raw HTML snippets safely inside isolated `<iframe>` elements using `srcdoc` to prevent Active Storage download blocks and script collisions.
66
+ - **Sandboxed Embeds with Configurable Heights**: Render third-party media and raw HTML snippets safely inside isolated `<iframe>` elements using `srcdoc` to prevent Active Storage download blocks and script collisions. Configure display heights (`sm`: 240px, `md`: 320px, `lg`: 480px, `xl`: 640px, `2xl`: 800px) alongside responsive widths (`25%` to `100%`).
67
+ - **Native Fullscreen Maximize**: Embed containers feature a floating toggle button powered by the HTML5 Fullscreen API (`widgets/embed.js`), allowing users to expand embedded presentations, charts, or maps to full screen.
56
68
  - **Responsive Media Controls**: Configure image and embed widgets with responsive width options (`25%`, `33%`, `50%`, `66%`, `75%`, `100%`) and alignment (`left`, `center`, `right`).
57
69
 
58
- ### ↕️ Drag-and-Drop & Button Reordering
70
+ ### ↕️ Drag-and-Drop, Compact View & Batch Reordering
59
71
  - **HTML5 Drag-and-Drop**: Easily re-arrange article content blocks on the Article show page with immediate position updates and asynchronous persistence.
60
72
  - **Accessible Move Up / Down Buttons**: Keyboard- and mobile-friendly reordering buttons on each block card as a built-in fallback.
73
+ - **High-Density Compact View**: Segmented toggle (`?view=compact`) for rapid content reordering across long articles. Compact mode hides in-between inserters, displays the primary field defined in the schema (`primary_field`), and clamps text output to two lines for streamlined scanning.
74
+ - **Shift+Click Multi-Selection & Multi-Drag**: Select multiple blocks in compact view using checkboxes or Shift+Click contiguous range selection with an active selection count badge. Dragging any selected card moves the entire group contiguously to the drop target.
61
75
 
62
76
  ### 🧩 Declarative Content Types & Generators
63
77
  - **YAML-Defined Schemas**: Define structured content types with typed fields (`name:kind`) stored directly in source control (`config/content_types/*.yml`).
@@ -69,16 +83,27 @@ A simple, flexible CMS engine for Ruby on Rails, designed for building highly in
69
83
  - **Seamless Rendering Helpers**: Render all article content blocks ordered by `sort_order` with `<%= cms_render_article_contents(@article) %>`, or render individual blocks via `<%= cms_render_widget(article_content) %>`.
70
84
  - **Informative Error Handling**: Raises `Macron::Cms::MissingWidgetError` if a content type does not have a corresponding `Widgets::<Name>Component` in the host application.
71
85
 
86
+ ### ⚡ High-Performance Preloading & O(1) Queries
87
+ - **Zero N+1 Query Overhead**: Built-in `ArticleContent.for_display` scope combines Active Storage's `with_attached_files` with `.includes(:content)` to preload associations in constant-time batches.
88
+ - **Bounded Query Execution**: Admin show views, high-frequency Turbo Stream inserts/reorders, and public host rendering (`cms_render_article_contents`) run in bounded O(1) database queries regardless of article length or attached media count.
89
+
72
90
  ### 🔒 Devise Authentication & Authorization
73
91
  - **Configurable Access Guard**: Protect the `/cms` engine namespace behind host application authentication.
74
92
  - **Role Customization**: Configure custom user models (`user_class`) and admin flags (`admin_flag`) via initializers.
75
93
 
76
- ### 🤖 REST API & AI Content Ingestion (`/cms/api/v1`)
77
- - **Atomic Nested Import**: Create or update articles and complete sequences of typed content blocks in a single atomic transaction.
78
- - **Automated Remote Asset Ingestion**: Remote image URLs (e.g. Substack, Medium, WordPress) are automatically downloaded, validated, and attached to Active Storage blobs.
79
- - **Idempotent Upsert Support**: Re-run imports safely using `upsert: true` without creating duplicate articles.
94
+ ### 🤖 Granular REST API & Headless Operations (`/cms/api/v1`)
95
+ - **Granular Block-Level Operations**: Full REST control over article blocks under `/cms/api/v1/articles/:article_id/content_blocks`—create with relative positioning (`insert_at`), update payloads, move blocks (`PATCH :id/move`), convert types in place (`PATCH :id/convert`), delete blocks, and batch reorder (`PATCH reorder`).
96
+ - **Inline & Remote Asset Ingestion**: Attach files directly using base64 data payloads (`file_upload`) or provide remote URLs (`image_url` / `upload_url`) with automatic MIME detection and background Active Storage attachment.
97
+ - **Atomic Nested Import & Upsert**: Create or update articles and complete sequences of typed content blocks in a single atomic transaction with `upsert: true`.
98
+ - **Article Lifecycle Control**: Programmatically manage article metadata, publishing timestamps (`published_at`), and deletion via `PATCH` and `DELETE /cms/api/v1/articles/:id`.
99
+ - **Historical Slug Resolution**: Requests referencing previous slugs resolve with `200 OK` and return the current canonical slug in the response, preventing integration breaks.
80
100
  - **Content Type Schema Introspection**: Query `GET /cms/api/v1/content_types` dynamically so AI agents know exact fields and widget types available.
81
101
  - **Bearer Token Auth**: Secure endpoints with `Authorization: Bearer <token>` or `X-API-Key`.
102
+ - **Full API Documentation**: Complete endpoint specifications, payload schemas, and response models in [`docs/api.md`](docs/api.md).
103
+
104
+ ### 🧠 Model Context Protocol (MCP) Server
105
+ - **Turnkey AI Pair-Programming**: Connect Claude Desktop, Cursor, Antigravity, or autonomous agents to MacronCMS via the official [macron-cms-mcp](https://github.com/macron1-automations/macron-cms-mcp) server.
106
+ - **Native AI Tool Calling**: Enables LLMs to dynamically inspect schemas, draft articles, insert/edit blocks, reorder sections, and upload media assets natively using structured tool definitions.
82
107
 
83
108
  ### 🎨 Self-Contained Tailwind CSS v4
84
109
  - **Zero Host Setup Required**: MacronCMS packages its own standalone, precompiled Tailwind CSS v4 bundle (`app/assets/stylesheets/macron/cms/application.css`). Host applications do not need Tailwind installed to display the CMS admin interface.
@@ -165,7 +190,7 @@ This generates:
165
190
 
166
191
  ---
167
192
 
168
- ### 2. Supported Field Kinds
193
+ ### 2. Supported Field Kinds & Schema Options
169
194
 
170
195
  | Kind | Description | Config Options | Accessor in Widget |
171
196
  |---|---|---|---|
@@ -178,6 +203,18 @@ This generates:
178
203
  | `json` | Formatted JSON editor | — | `field_name` |
179
204
  | `latex` | Mathematical formula editor with CodeMirror & KaTeX | — | `field_name` |
180
205
 
206
+ #### Schema Configuration Options
207
+
208
+ In addition to fields, content type schemas support top-level configuration options:
209
+
210
+ | Option | Type | Description |
211
+ |---|---|---|
212
+ | `name` | String | Human-readable name of the content type |
213
+ | `description` | String | Detailed description displayed in the CMS admin dashboard |
214
+ | `featured_image` | Boolean | Set to `true` to declare this block as an image source for article featured image bubbling |
215
+ | `convertible_to` | Array of strings | Target content types eligible for declarative in-place conversion |
216
+ | `primary_field` | String | Designation of the field displayed as the primary preview in compact block view |
217
+
181
218
  ---
182
219
 
183
220
  ### 3. Schema Examples
@@ -215,6 +252,9 @@ fields:
215
252
  ```yaml
216
253
  name: Image
217
254
  description: Image with configurable responsive width, alignment, and optional caption
255
+ featured_image: true
256
+ convertible_to:
257
+ - three_js_image
218
258
  fields:
219
259
  - name: file
220
260
  kind: file
@@ -253,6 +293,19 @@ fields:
253
293
  - 66%
254
294
  - 75%
255
295
  - 100%
296
+ - name: height
297
+ kind: dropdown
298
+ options:
299
+ - name: Small (240px)
300
+ value: sm
301
+ - name: Medium (320px)
302
+ value: md
303
+ - name: Large (480px)
304
+ value: lg
305
+ - name: Extra Large (640px)
306
+ value: xl
307
+ - name: Very Large (800px)
308
+ value: 2xl
256
309
  - name: align
257
310
  kind: dropdown
258
311
  options:
@@ -346,6 +399,72 @@ fields:
346
399
  value: strong
347
400
  ```
348
401
 
402
+ #### Interactive 3D Image Effects (`config/content_types/three_js_image.yml`)
403
+ ```yaml
404
+ featured_image: true
405
+ convertible_to:
406
+ - image
407
+ fields:
408
+ - name: upload
409
+ kind: file
410
+ - name: size
411
+ kind: dropdown
412
+ options:
413
+ - name: Small (33%)
414
+ value: sm
415
+ - name: Medium (50%)
416
+ value: md
417
+ - name: Large (75%)
418
+ value: lg
419
+ - name: Full Width (100%)
420
+ value: full
421
+ - name: effect
422
+ kind: dropdown
423
+ options:
424
+ - name: Water Ripple
425
+ value: ripple
426
+ - name: Underwater Wave
427
+ value: wave
428
+ - name: RGB Glitch
429
+ value: glitch
430
+ - name: animation_speed
431
+ kind: dropdown
432
+ options:
433
+ - name: Slow
434
+ value: slow
435
+ - name: Normal
436
+ value: normal
437
+ - name: Fast
438
+ value: fast
439
+ - name: intensity
440
+ kind: dropdown
441
+ options:
442
+ - name: Subtle
443
+ value: subtle
444
+ - name: Medium
445
+ value: medium
446
+ - name: Strong
447
+ value: strong
448
+ - name: canvas_height
449
+ kind: dropdown
450
+ options:
451
+ - name: Small (240px)
452
+ value: sm
453
+ - name: Medium (320px)
454
+ value: md
455
+ - name: Large (480px)
456
+ value: lg
457
+ - name: Extra Large (640px)
458
+ value: xl
459
+ - name: fit_mode
460
+ kind: dropdown
461
+ options:
462
+ - name: Cover (Fill & Crop)
463
+ value: cover
464
+ - name: Contain (Fit Inside)
465
+ value: contain
466
+ ```
467
+
349
468
  ---
350
469
 
351
470
  ### 4. Importing Content Types
@@ -368,6 +487,54 @@ The import task is idempotent and creates or updates existing records. Once impo
368
487
 
369
488
  ### 5. Front-End Integration & ViewComponent Widgets
370
489
 
490
+ #### Host Application Controller (`app/controllers/articles_controller.rb`)
491
+
492
+ Include `Macron::Cms::SlugRedirectable` in your host controller to automatically issue canonical 301 permanent redirects if a visitor or search engine arrives via an outdated historical slug:
493
+
494
+ ```ruby
495
+ class ArticlesController < ApplicationController
496
+ include Macron::Cms::SlugRedirectable
497
+
498
+ def show
499
+ @article = Macron::Cms::Article.friendly.find(params[:id])
500
+ redirect_to_canonical_slug_if_needed(@article)
501
+ end
502
+ end
503
+ ```
504
+
505
+ #### Editorial Stream & Featured Images (`app/views/pages/index.html.erb`)
506
+
507
+ When listing articles on a homepage or editorial index, eager-load `:featured_image_blob` to display automatically bubbled featured images with zero N+1 database queries:
508
+
509
+ ```ruby
510
+ # app/controllers/pages_controller.rb
511
+ class PagesController < ApplicationController
512
+ def index
513
+ # Default scope orders chronologically by published_at DESC, created_at DESC
514
+ @articles = Macron::Cms::Article.includes(:featured_image_blob)
515
+ end
516
+ end
517
+ ```
518
+
519
+ ```erb
520
+ <%# app/views/pages/index.html.erb %>
521
+ <div class="space-y-6">
522
+ <% @articles.each do |article| %>
523
+ <div class="flex items-start gap-4">
524
+ <% if article.featured_image.present? %>
525
+ <%= link_to article_path(article), class: "shrink-0" do %>
526
+ <%= image_tag article.featured_image, class: "w-24 h-24 object-cover rounded-lg border border-zinc-200" %>
527
+ <% end %>
528
+ <% end %>
529
+ <div>
530
+ <h2 class="text-xl font-bold"><%= link_to article.title, article_path(article) %></h2>
531
+ <p class="text-sm text-zinc-600 line-clamp-2"><%= article.seo_description %></p>
532
+ </div>
533
+ </div>
534
+ <% end %>
535
+ </div>
536
+ ```
537
+
371
538
  #### Host Application View (`app/views/articles/show.html.erb`)
372
539
 
373
540
  Use `cms_seo_meta` for SEO metadata and `cms_article_background_style` to apply custom background colors, images, and text colors directly to the layout:
@@ -411,6 +578,8 @@ In your application layout (`app/views/layouts/application.html.erb`), apply the
411
578
  <%= javascript_include_tag "vendor/three_text_bundle", "data-turbo-track": "reload", defer: true %>
412
579
  <%= javascript_include_tag "vendor/three_fonts_bundle", "data-turbo-track": "reload", defer: true %>
413
580
  <%= javascript_include_tag "widgets/three_js_quote", "data-turbo-track": "reload", defer: true %>
581
+ <%= javascript_include_tag "widgets/three_js_image", "data-turbo-track": "reload", defer: true %>
582
+ <%= javascript_include_tag "widgets/embed", "data-turbo-track": "reload", defer: true %>
414
583
  </head>
415
584
 
416
585
  <body class="min-h-screen text-zinc-900 <%= 'bg-zinc-50' unless content_for?(:body_style) %>"
@@ -522,8 +691,17 @@ end
522
691
  # app/components/widgets/embed_component.rb
523
692
  module Widgets
524
693
  class EmbedComponent < Macron::Cms::WidgetComponent
694
+ HEIGHT_CLASSES = {
695
+ "sm" => "h-60 sm:h-72",
696
+ "md" => "h-72 sm:h-80",
697
+ "lg" => "h-96 sm:h-[480px]",
698
+ "xl" => "h-[540px] sm:h-[640px]",
699
+ "2xl" => "h-[640px] sm:h-[800px]"
700
+ }.freeze
701
+ DEFAULT_HEIGHT = "lg"
702
+
525
703
  def embed_html
526
- field_value(:html).to_s
704
+ field_value(:html).presence || (upload.present? ? upload.download.force_encoding("UTF-8") : nil)
527
705
  end
528
706
 
529
707
  def width_class
@@ -544,18 +722,42 @@ module Widgets
544
722
  else "mx-auto"
545
723
  end
546
724
  end
725
+
726
+ def height_class
727
+ h = field_value(:height).to_s.strip.downcase
728
+ HEIGHT_CLASSES[h] || HEIGHT_CLASSES[DEFAULT_HEIGHT]
729
+ end
547
730
  end
548
731
  end
549
732
  ```
550
733
 
551
734
  ```erb
552
735
  <%# app/components/widgets/embed_component.html.erb %>
553
- <div class="<%= width_class %> <%= align_class %> my-6">
736
+ <div class="relative group my-6 <%= width_class %> <%= align_class %> overflow-hidden rounded-xl border border-zinc-200 bg-white shadow-xs [&:fullscreen]:m-0 [&:fullscreen]:w-screen [&:fullscreen]:h-screen [&:fullscreen]:rounded-none" data-embed-card>
737
+ <!-- Fullscreen Toggle Button -->
738
+ <button
739
+ type="button"
740
+ class="absolute top-3 right-3 z-10 flex h-8 w-8 items-center justify-center rounded-lg bg-zinc-900/60 text-white backdrop-blur-xs transition hover:bg-zinc-900/90 focus:outline-none opacity-70 group-hover:opacity-100 cursor-pointer"
741
+ data-embed-fullscreen-btn
742
+ aria-label="Maximize"
743
+ title="Maximize"
744
+ >
745
+ <svg class="h-4 w-4" data-icon-enter-fullscreen fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
746
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" />
747
+ </svg>
748
+ <svg class="h-4 w-4 hidden" data-icon-exit-fullscreen fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
749
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 9V4.5M9 9H4.5M9 9 3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5 5.25 5.25" />
750
+ </svg>
751
+ </button>
752
+
554
753
  <iframe
555
754
  srcdoc="<%= embed_html %>"
755
+ class="w-full <%= height_class %> border-0 [&.is-fullscreen-iframe]:h-full"
556
756
  sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
557
- class="w-full border-0 rounded overflow-hidden"
558
- loading="lazy">
757
+ allowfullscreen
758
+ allow="fullscreen"
759
+ loading="lazy"
760
+ title="Embedded content">
559
761
  </iframe>
560
762
  </div>
561
763
  ```
@@ -704,6 +906,9 @@ end
704
906
 
705
907
  MacronCMS provides a JSON REST API under `/cms/api/v1/` designed for programmatic ingestion, automated scripts, and autonomous AI agents migrating content from external platforms (such as Substack, WordPress, or Medium).
706
908
 
909
+ > [!TIP]
910
+ > For the complete endpoint reference, request/response payload schemas, parameter definitions, and status codes, see the dedicated [**REST API Documentation (`docs/api.md`)**](docs/api.md). If you are building an automated ingestion pipeline or AI migration agent, refer to the [**AI Article Importer Guide (`docs/ai_importer_guide.md`)**](docs/ai_importer_guide.md).
911
+
707
912
  ### Authentication
708
913
 
709
914
  API endpoints are secured using a Bearer token or API key header:
@@ -724,16 +929,24 @@ Configure your key in an initializer (`config.api_key`) or via the `MACRON_CMS_A
724
929
 
725
930
  | Method | Endpoint | Description |
726
931
  |---|---|---|
727
- | `POST` | `/cms/api/v1/articles` | Create or upsert an article with nested content blocks |
728
- | `GET` | `/cms/api/v1/articles/:id` | Retrieve article metadata and ordered content blocks (JSON) |
729
- | `PATCH` | `/cms/api/v1/articles/:id` | Update article attributes and synchronize content blocks |
730
- | `GET` | `/cms/api/v1/content_types` | Introspect available content types and schema field requirements |
932
+ | `GET` | [`/cms/api/v1/content_types`](docs/api.md#1-list-content-types-get-cmsapiv1content_types) | Introspect available content types and schema field requirements |
933
+ | `GET` | [`/cms/api/v1/articles`](docs/api.md#2-list-articles-get-cmsapiv1articles) | List articles with metadata, slugs, and publication dates |
934
+ | `POST` | [`/cms/api/v1/articles`](docs/api.md#3-create-or-upsert-article-post-cmsapiv1articles) | Create or upsert an article with nested content blocks |
935
+ | `GET` | [`/cms/api/v1/articles/:id`](docs/api.md#4-retrieve-article-get-cmsapiv1articlesid) | Retrieve article metadata and ordered content blocks (JSON) |
936
+ | `PATCH` | [`/cms/api/v1/articles/:id`](docs/api.md#5-update-article-patch-cmsapiv1articlesid) | Update article metadata and synchronize content blocks |
937
+ | `DELETE` | [`/cms/api/v1/articles/:id`](docs/api.md#6-delete-article-delete-cmsapiv1articlesid) | Delete an article and its associated content blocks |
938
+ | `POST` | [`/cms/api/v1/articles/:article_id/content_blocks`](docs/api.md#7-content-block-operations) | Insert a content block (supports `insert_at` and `file_upload`) |
939
+ | `PATCH` | [`/cms/api/v1/articles/:article_id/content_blocks/:id`](docs/api.md#7-content-block-operations) | Update payload attributes, replace files, or purge attachments |
940
+ | `DELETE` | [`/cms/api/v1/articles/:article_id/content_blocks/:id`](docs/api.md#7-content-block-operations) | Delete a content block and re-normalize sequence order |
941
+ | `PATCH` | [`/cms/api/v1/articles/:article_id/content_blocks/:id/move`](docs/api.md#7-content-block-operations) | Move block position (`up`, `down`, or target position) |
942
+ | `PATCH` | [`/cms/api/v1/articles/:article_id/content_blocks/:id/convert`](docs/api.md#7-content-block-operations) | Convert block in-place to compatible schema type |
943
+ | `PATCH` | [`/cms/api/v1/articles/:article_id/content_blocks/reorder`](docs/api.md#7-content-block-operations) | Batch reorder blocks via array of ordered IDs |
731
944
 
732
945
  ---
733
946
 
734
947
  ### 1. Ingesting Articles with Nested Content Blocks
735
948
 
736
- You can import an entire article—including its metadata, SEO tags, colors, and an ordered list of typed content blocks—in a single atomic transaction:
949
+ You can import an entire article—including its metadata, SEO tags, colors, publication timestamp (`published_at`), and an ordered list of typed content blocks—in a single atomic transaction:
737
950
 
738
951
  ```bash
739
952
  curl -X POST http://localhost:3000/cms/api/v1/articles \
@@ -743,7 +956,8 @@ curl -X POST http://localhost:3000/cms/api/v1/articles \
743
956
  "upsert": true,
744
957
  "article": {
745
958
  "title": "The Myth of Your Limitations",
746
- "name": "the-myth-of-your-limitations",
959
+ "slug": "the-myth-of-your-limitations",
960
+ "published_at": "2026-10-29T19:30:00Z",
747
961
  "meta_title": "The Myth of Your Limitations | Mindset Guide",
748
962
  "meta_description": "How a simple mindset shift can prove you wrong.",
749
963
  "background_color": "#ffffff",
@@ -776,7 +990,7 @@ curl -X POST http://localhost:3000/cms/api/v1/articles \
776
990
  When an `image_url` (or `upload_url`) is provided in a content block, MacronCMS automatically downloads the asset from the remote source (e.g. Substack CDN), verifies content size and type, and attaches it as an Active Storage blob on the content block. If the remote server is unreachable, it gracefully falls back to storing the direct URL in the block payload.
777
991
 
778
992
  #### Idempotent Upserting
779
- When `"upsert": true` is specified, the API searches for an existing article matching `name`, `slug`, or `title`. If found, it updates the article attributes and atomically replaces its content blocks with the incoming sequence, making bulk migrations safe to re-run.
993
+ When `"upsert": true` is specified, the API searches for an existing article matching `slug` or `title`. If found, it updates the article attributes and atomically replaces its content blocks with the incoming sequence, making bulk migrations safe to re-run.
780
994
 
781
995
  ---
782
996
 
@@ -794,20 +1008,119 @@ AI agents can query `/cms/api/v1/content_types` before generating payloads to dy
794
1008
  { "name": "title", "kind": "string" },
795
1009
  { "name": "level", "kind": "dropdown", "options": ["h1", "h2", "h3", "h4", "h5", "h6"] },
796
1010
  { "name": "color", "kind": "color" }
797
- ]
1011
+ ],
1012
+ "convertible_to": [],
1013
+ "featured_image": false
798
1014
  },
799
1015
  {
800
1016
  "id": 2,
801
- "name": "markdown",
1017
+ "name": "image",
802
1018
  "fields": [
803
- { "name": "content", "kind": "markdown" }
804
- ]
1019
+ { "name": "upload", "kind": "file" },
1020
+ { "name": "size", "kind": "dropdown", "options": ["sm", "md", "lg", "full"] }
1021
+ ],
1022
+ "convertible_to": [
1023
+ "three_js_image"
1024
+ ],
1025
+ "featured_image": true
805
1026
  }
806
1027
  ]
807
1028
  }
808
1029
  ```
809
1030
 
810
- For complete mapping rules from Substack HTML tags and a ready-to-use prompt template for AI agents, see [`docs/ai_importer_guide.md`](docs/ai_importer_guide.md).
1031
+ ---
1032
+
1033
+ ### 3. Granular Content Block Operations
1034
+
1035
+ MacronCMS supports fine-grained REST manipulation of article blocks, allowing agents and scripts to make targeted edits without replacing the entire article:
1036
+
1037
+ #### Add Block with Inline Base64 Asset
1038
+ ```bash
1039
+ curl -X POST http://localhost:3000/cms/api/v1/articles/the-myth-of-your-limitations/content_blocks \
1040
+ -H "Content-Type: application/json" \
1041
+ -H "Authorization: Bearer test-secret-key" \
1042
+ -d '{
1043
+ "content_type": "image",
1044
+ "insert_at": 2,
1045
+ "payload": { "size": "md" },
1046
+ "file_upload": {
1047
+ "filename": "diagram.png",
1048
+ "content_type": "image/png",
1049
+ "data_base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
1050
+ }
1051
+ }'
1052
+ ```
1053
+
1054
+ #### In-Place Block Conversion
1055
+ ```bash
1056
+ curl -X PATCH http://localhost:3000/cms/api/v1/articles/the-myth-of-your-limitations/content_blocks/42/convert \
1057
+ -H "Content-Type: application/json" \
1058
+ -H "Authorization: Bearer test-secret-key" \
1059
+ -d '{ "target_content_type": "three_js_image" }'
1060
+ ```
1061
+
1062
+ #### Reordering and Relative Moves
1063
+ ```bash
1064
+ # Shift block up or down relative to its current position
1065
+ curl -X PATCH http://localhost:3000/cms/api/v1/articles/the-myth-of-your-limitations/content_blocks/42/move \
1066
+ -H "Content-Type: application/json" \
1067
+ -H "Authorization: Bearer test-secret-key" \
1068
+ -d '{ "direction": "up" }'
1069
+
1070
+ # Or batch reorder by an explicit list of block IDs
1071
+ curl -X PATCH http://localhost:3000/cms/api/v1/articles/the-myth-of-your-limitations/content_blocks/reorder \
1072
+ -H "Content-Type: application/json" \
1073
+ -H "Authorization: Bearer test-secret-key" \
1074
+ -d '{ "ordered_ids": [45, 42, 43, 44] }'
1075
+ ```
1076
+
1077
+ For complete endpoint specifications, payload schemas, and response formats, see [`docs/api.md`](docs/api.md). For Substack HTML mapping rules and a ready-to-use prompt template for AI agents, see [`docs/ai_importer_guide.md`](docs/ai_importer_guide.md).
1078
+
1079
+ ---
1080
+
1081
+ ## 🤖 Model Context Protocol (MCP) Server
1082
+
1083
+ MacronCMS includes an official **Model Context Protocol (MCP) Server**: [**`macron-cms-mcp`**](https://github.com/macron1-automations/macron-cms-mcp).
1084
+
1085
+ The MCP server connects AI coding assistants and autonomous agents—including Claude Desktop, Cursor, and Antigravity—directly to MacronCMS, exposing rich tool-calling capabilities to manage and pair-program content directly from within your agent workflow.
1086
+
1087
+ ### Available MCP Tools
1088
+
1089
+ | Tool | Description |
1090
+ |---|---|
1091
+ | `list_content_types` | Introspect all registered YAML content types, field specifications, and type converters |
1092
+ | `list_articles` | Retrieve articles with metadata, canonical slugs, and publication timestamps |
1093
+ | `get_article` | Fetch article details and its ordered content blocks |
1094
+ | `create_article` | Scaffold a new article with optional nested blocks and publication scheduling |
1095
+ | `update_article_metadata` | Update article title, slug, publication date, text/background styling, or SEO tags |
1096
+ | `delete_article` | Delete an article and its content blocks |
1097
+ | `add_content_block` | Insert a new content block at a specific position with payload or Base64 attachments |
1098
+ | `update_content_block` | Modify block payload attributes, replace attachments, or purge files |
1099
+ | `delete_content_block` | Remove a block and automatically re-normalize sequence ordering |
1100
+ | `move_content_block` | Shift block position up, down, or to an explicit target index |
1101
+ | `reorder_content_blocks` | Batch reorder all blocks on an article |
1102
+ | `convert_content_block` | In-place convert a block between compatible content types (e.g. `image` ↔ `three_js_image`) |
1103
+
1104
+ ### AI Agent / Client Configuration
1105
+
1106
+ Add the server to your MCP client configuration (such as `claude_desktop_config.json` or your Antigravity / Cursor MCP configuration):
1107
+
1108
+ ```json
1109
+ {
1110
+ "mcpServers": {
1111
+ "macron-cms": {
1112
+ "command": "npx",
1113
+ "args": ["-y", "@macron1/cms-mcp"],
1114
+ "env": {
1115
+ "MACRON_CMS_BASE_URL": "http://localhost:3000/cms/api/v1",
1116
+ "MACRON_CMS_API_KEY": "<your-api-key>"
1117
+ }
1118
+ }
1119
+ }
1120
+ }
1121
+ ```
1122
+
1123
+ For full installation and tool usage documentation, visit the [**macron-cms-mcp repository on GitHub**](https://github.com/macron1-automations/macron-cms-mcp).
811
1124
 
812
1125
  ---
813
1126