active_canvas 0.0.1
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +318 -0
- data/Rakefile +6 -0
- data/app/assets/javascripts/active_canvas/editor/ai_panel.js +1607 -0
- data/app/assets/javascripts/active_canvas/editor/asset_manager.js +498 -0
- data/app/assets/javascripts/active_canvas/editor/blocks.js +1083 -0
- data/app/assets/javascripts/active_canvas/editor/code_panel.js +572 -0
- data/app/assets/javascripts/active_canvas/editor/component_toolbar.js +394 -0
- data/app/assets/javascripts/active_canvas/editor/panels.js +460 -0
- data/app/assets/javascripts/active_canvas/editor/utils.js +56 -0
- data/app/assets/javascripts/active_canvas/editor.js +295 -0
- data/app/assets/stylesheets/active_canvas/application.css +15 -0
- data/app/assets/stylesheets/active_canvas/editor.css +2929 -0
- data/app/controllers/active_canvas/admin/ai_controller.rb +181 -0
- data/app/controllers/active_canvas/admin/application_controller.rb +56 -0
- data/app/controllers/active_canvas/admin/media_controller.rb +61 -0
- data/app/controllers/active_canvas/admin/page_types_controller.rb +57 -0
- data/app/controllers/active_canvas/admin/page_versions_controller.rb +23 -0
- data/app/controllers/active_canvas/admin/pages_controller.rb +133 -0
- data/app/controllers/active_canvas/admin/partials_controller.rb +88 -0
- data/app/controllers/active_canvas/admin/settings_controller.rb +256 -0
- data/app/controllers/active_canvas/application_controller.rb +20 -0
- data/app/controllers/active_canvas/pages_controller.rb +18 -0
- data/app/controllers/concerns/active_canvas/current_user.rb +12 -0
- data/app/controllers/concerns/active_canvas/rate_limitable.rb +75 -0
- data/app/controllers/concerns/active_canvas/tailwind_compilation.rb +39 -0
- data/app/helpers/active_canvas/application_helper.rb +4 -0
- data/app/jobs/active_canvas/application_job.rb +4 -0
- data/app/jobs/active_canvas/compile_tailwind_job.rb +64 -0
- data/app/mailers/active_canvas/application_mailer.rb +6 -0
- data/app/models/active_canvas/ai_model.rb +136 -0
- data/app/models/active_canvas/application_record.rb +5 -0
- data/app/models/active_canvas/media.rb +141 -0
- data/app/models/active_canvas/page.rb +85 -0
- data/app/models/active_canvas/page_type.rb +22 -0
- data/app/models/active_canvas/page_version.rb +80 -0
- data/app/models/active_canvas/partial.rb +73 -0
- data/app/models/active_canvas/setting.rb +292 -0
- data/app/services/active_canvas/ai_configuration.rb +40 -0
- data/app/services/active_canvas/ai_models.rb +128 -0
- data/app/services/active_canvas/ai_service.rb +289 -0
- data/app/services/active_canvas/content_sanitizer.rb +112 -0
- data/app/services/active_canvas/tailwind_compiler.rb +156 -0
- data/app/views/active_canvas/admin/media/index.html.erb +401 -0
- data/app/views/active_canvas/admin/media/show.html.erb +297 -0
- data/app/views/active_canvas/admin/page_types/_form.html.erb +25 -0
- data/app/views/active_canvas/admin/page_types/edit.html.erb +13 -0
- data/app/views/active_canvas/admin/page_types/index.html.erb +29 -0
- data/app/views/active_canvas/admin/page_types/new.html.erb +9 -0
- data/app/views/active_canvas/admin/page_types/show.html.erb +18 -0
- data/app/views/active_canvas/admin/page_versions/show.html.erb +469 -0
- data/app/views/active_canvas/admin/pages/_form.html.erb +62 -0
- data/app/views/active_canvas/admin/pages/content.html.erb +139 -0
- data/app/views/active_canvas/admin/pages/edit.html.erb +335 -0
- data/app/views/active_canvas/admin/pages/editor.html.erb +710 -0
- data/app/views/active_canvas/admin/pages/index.html.erb +149 -0
- data/app/views/active_canvas/admin/pages/new.html.erb +19 -0
- data/app/views/active_canvas/admin/pages/show.html.erb +258 -0
- data/app/views/active_canvas/admin/pages/versions.html.erb +333 -0
- data/app/views/active_canvas/admin/partials/edit.html.erb +182 -0
- data/app/views/active_canvas/admin/partials/editor.html.erb +703 -0
- data/app/views/active_canvas/admin/partials/index.html.erb +131 -0
- data/app/views/active_canvas/admin/settings/show.html.erb +1864 -0
- data/app/views/active_canvas/pages/no_homepage.html.erb +45 -0
- data/app/views/active_canvas/pages/show.html.erb +113 -0
- data/app/views/layouts/active_canvas/admin/application.html.erb +960 -0
- data/app/views/layouts/active_canvas/admin/editor.html.erb +826 -0
- data/app/views/layouts/active_canvas/application.html.erb +55 -0
- data/config/routes.rb +48 -0
- data/db/migrate/20260202000001_create_active_canvas_tables.rb +113 -0
- data/db/migrate/20260202000002_create_active_canvas_ai_models.rb +26 -0
- data/lib/active_canvas/configuration.rb +232 -0
- data/lib/active_canvas/engine.rb +44 -0
- data/lib/active_canvas/version.rb +3 -0
- data/lib/active_canvas.rb +26 -0
- data/lib/generators/active_canvas/install/install_generator.rb +263 -0
- data/lib/generators/active_canvas/install/templates/initializer.rb.tt +163 -0
- data/lib/tasks/active_canvas_tasks.rake +69 -0
- metadata +150 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
<% content_for :page_title, "Edit #{@page.title}" %>
|
|
2
|
+
|
|
3
|
+
<div class="page-header">
|
|
4
|
+
<div class="page-header-left">
|
|
5
|
+
<h2>Edit Page</h2>
|
|
6
|
+
<p class="page-header-subtitle">Update page settings, SEO, and social sharing options</p>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="page-header-actions">
|
|
9
|
+
<%= link_to editor_admin_page_path(@page), class: "btn btn-primary" do %>
|
|
10
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
11
|
+
<polygon points="12 2 2 7 12 12 22 7 12 2"/>
|
|
12
|
+
<polyline points="2 17 12 22 22 17"/>
|
|
13
|
+
<polyline points="2 12 12 17 22 12"/>
|
|
14
|
+
</svg>
|
|
15
|
+
Open Designer
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<%= form_with model: @page, url: admin_page_path(@page) do |form| %>
|
|
21
|
+
|
|
22
|
+
<% if @page.errors.any? %>
|
|
23
|
+
<div class="error-messages">
|
|
24
|
+
<strong><%= pluralize(@page.errors.count, "error") %> prohibited this page from being saved:</strong>
|
|
25
|
+
<ul>
|
|
26
|
+
<% @page.errors.full_messages.each do |message| %>
|
|
27
|
+
<li><%= message %></li>
|
|
28
|
+
<% end %>
|
|
29
|
+
</ul>
|
|
30
|
+
</div>
|
|
31
|
+
<% end %>
|
|
32
|
+
|
|
33
|
+
<!-- Page Details -->
|
|
34
|
+
<div class="card" style="margin-bottom: 1.5rem;">
|
|
35
|
+
<div class="card-header">
|
|
36
|
+
<span class="card-title">Page Details</span>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="card-body">
|
|
39
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem;">
|
|
40
|
+
<div class="form-group">
|
|
41
|
+
<%= form.label :title %>
|
|
42
|
+
<%= form.text_field :title, class: "form-control", placeholder: "Enter page title" %>
|
|
43
|
+
<p class="help-text">The title displayed in browsers and search results.</p>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="form-group">
|
|
47
|
+
<%= form.label :slug, "URL Slug" %>
|
|
48
|
+
<%= form.text_field :slug, class: "form-control", placeholder: "Leave blank for auto-generated" %>
|
|
49
|
+
<p class="help-text">The URL path for this page (e.g., "about-us").</p>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="form-group">
|
|
54
|
+
<%= form.label :page_type_id, "Page Type" %>
|
|
55
|
+
<%= form.collection_select :page_type_id, ActiveCanvas::PageType.order(:name), :id, :name, {}, class: "form-control" %>
|
|
56
|
+
<p class="help-text">Determines the layout template used for this page.</p>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem;">
|
|
60
|
+
<% if ActiveCanvas::Partial.table_exists? %>
|
|
61
|
+
<!-- Header & Footer -->
|
|
62
|
+
<div class="form-group">
|
|
63
|
+
<label style="font-weight: 500; margin-bottom: 0.5rem; display: block;">Header & Footer</label>
|
|
64
|
+
<div style="background: var(--bg-main, #f8fafc); padding: 1rem; border-radius: 0.5rem; border: 1px solid var(--border, #e2e8f0);">
|
|
65
|
+
<div style="display: flex; flex-direction: column; gap: 0.75rem;">
|
|
66
|
+
<div class="form-check" style="display: flex; align-items: center; gap: 0.5rem;">
|
|
67
|
+
<%= form.check_box :show_header, id: "page_show_header", checked: @page.show_header? %>
|
|
68
|
+
<label for="page_show_header" style="margin: 0;">Show Header</label>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="form-check" style="display: flex; align-items: center; gap: 0.5rem;">
|
|
71
|
+
<%= form.check_box :show_footer, id: "page_show_footer", checked: @page.show_footer? %>
|
|
72
|
+
<label for="page_show_footer" style="margin: 0;">Show Footer</label>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<p class="help-text"><%= link_to "Manage header & footer", admin_partials_path, style: "color: var(--primary);" %></p>
|
|
77
|
+
</div>
|
|
78
|
+
<% end %>
|
|
79
|
+
|
|
80
|
+
<!-- Publish Status -->
|
|
81
|
+
<div class="form-group">
|
|
82
|
+
<label style="font-weight: 500; margin-bottom: 0.5rem; display: block;">Status</label>
|
|
83
|
+
<div style="background: var(--bg-main, #f8fafc); padding: 1rem; border-radius: 0.5rem; border: 1px solid var(--border, #e2e8f0);">
|
|
84
|
+
<div class="form-check" style="display: flex; align-items: center; gap: 0.5rem;">
|
|
85
|
+
<%= form.check_box :published, id: "page_published" %>
|
|
86
|
+
<label for="page_published" style="font-weight: 500; margin: 0;">
|
|
87
|
+
Publish this page
|
|
88
|
+
</label>
|
|
89
|
+
</div>
|
|
90
|
+
<p class="help-text" style="margin-top: 0.5rem; margin-bottom: 0;">Published pages are visible to the public.</p>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<!-- Recommended SEO Fields -->
|
|
98
|
+
<div class="card" style="margin-bottom: 1.5rem;">
|
|
99
|
+
<div class="card-header">
|
|
100
|
+
<span class="card-title">
|
|
101
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: -2px; margin-right: 0.5rem; color: var(--success, #10b981);">
|
|
102
|
+
<circle cx="11" cy="11" r="8"/>
|
|
103
|
+
<path d="m21 21-4.3-4.3"/>
|
|
104
|
+
</svg>
|
|
105
|
+
SEO - Recommended
|
|
106
|
+
</span>
|
|
107
|
+
<span class="badge badge-success">Important</span>
|
|
108
|
+
</div>
|
|
109
|
+
<div class="card-body">
|
|
110
|
+
<div class="form-group">
|
|
111
|
+
<%= form.label :meta_title, "Meta Title" %>
|
|
112
|
+
<%= form.text_field :meta_title, class: "form-control", placeholder: "Custom title for search engines (defaults to page title)", maxlength: 60 %>
|
|
113
|
+
<p class="help-text">
|
|
114
|
+
The title that appears in search engine results and browser tabs. Keep it under 60 characters for best results.
|
|
115
|
+
<strong>If left empty, the page title will be used.</strong>
|
|
116
|
+
</p>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div class="form-group">
|
|
120
|
+
<%= form.label :meta_description, "Meta Description" %>
|
|
121
|
+
<%= form.text_area :meta_description, class: "form-control", rows: 3, placeholder: "Brief description of this page for search engines...", maxlength: 160, style: "min-height: 80px;" %>
|
|
122
|
+
<p class="help-text">
|
|
123
|
+
A brief summary that appears below the title in search results. Keep it between 150-160 characters.
|
|
124
|
+
This is your chance to convince users to click on your result.
|
|
125
|
+
</p>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<!-- Social Sharing - Recommended -->
|
|
131
|
+
<div class="card" style="margin-bottom: 1.5rem;">
|
|
132
|
+
<div class="card-header">
|
|
133
|
+
<span class="card-title">
|
|
134
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: -2px; margin-right: 0.5rem; color: #3b82f6;">
|
|
135
|
+
<circle cx="18" cy="5" r="3"/>
|
|
136
|
+
<circle cx="6" cy="12" r="3"/>
|
|
137
|
+
<circle cx="18" cy="19" r="3"/>
|
|
138
|
+
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/>
|
|
139
|
+
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>
|
|
140
|
+
</svg>
|
|
141
|
+
Social Sharing (Open Graph) - Recommended
|
|
142
|
+
</span>
|
|
143
|
+
<span class="badge badge-success">Important</span>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="card-body">
|
|
146
|
+
<p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1.25rem; padding: 0.75rem; background: var(--bg-main, #f8fafc); border-radius: 0.375rem;">
|
|
147
|
+
Open Graph tags control how your page appears when shared on Facebook, LinkedIn, and other social platforms.
|
|
148
|
+
If left empty, these will fall back to your SEO title and description.
|
|
149
|
+
</p>
|
|
150
|
+
|
|
151
|
+
<div class="form-group">
|
|
152
|
+
<%= form.label :og_title, "Social Title" %>
|
|
153
|
+
<%= form.text_field :og_title, class: "form-control", placeholder: "Title when shared on social media (defaults to meta title)" %>
|
|
154
|
+
<p class="help-text">The title displayed when this page is shared on Facebook, LinkedIn, etc.</p>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<div class="form-group">
|
|
158
|
+
<%= form.label :og_description, "Social Description" %>
|
|
159
|
+
<%= form.text_area :og_description, class: "form-control", rows: 2, placeholder: "Description when shared on social media...", style: "min-height: 60px;" %>
|
|
160
|
+
<p class="help-text">The description displayed in social media previews. Can be longer than the meta description.</p>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<div class="form-group">
|
|
164
|
+
<%= form.label :og_image, "Social Image URL" %>
|
|
165
|
+
<%= form.text_field :og_image, class: "form-control", placeholder: "https://example.com/image.jpg" %>
|
|
166
|
+
<p class="help-text">
|
|
167
|
+
The image displayed when shared on social media. Recommended size: 1200x630 pixels.
|
|
168
|
+
Use an absolute URL (starting with https://).
|
|
169
|
+
</p>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<!-- Optional SEO Fields -->
|
|
175
|
+
<details class="card" style="margin-bottom: 1.5rem;">
|
|
176
|
+
<summary class="card-header" style="cursor: pointer; list-style: none;">
|
|
177
|
+
<span class="card-title">
|
|
178
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: -2px; margin-right: 0.5rem; color: var(--text-muted);">
|
|
179
|
+
<circle cx="12" cy="12" r="3"/>
|
|
180
|
+
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
|
181
|
+
</svg>
|
|
182
|
+
Advanced SEO Options
|
|
183
|
+
</span>
|
|
184
|
+
<span class="badge badge-gray">Optional</span>
|
|
185
|
+
</summary>
|
|
186
|
+
<div class="card-body">
|
|
187
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem;">
|
|
188
|
+
<div class="form-group">
|
|
189
|
+
<%= form.label :canonical_url, "Canonical URL" %>
|
|
190
|
+
<%= form.text_field :canonical_url, class: "form-control", placeholder: "https://example.com/preferred-url" %>
|
|
191
|
+
<p class="help-text">
|
|
192
|
+
Use this if the same content exists on multiple URLs. Points search engines to the preferred version to avoid duplicate content issues.
|
|
193
|
+
</p>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<div class="form-group">
|
|
197
|
+
<%= form.label :meta_robots, "Robots Directive" %>
|
|
198
|
+
<%= form.select :meta_robots,
|
|
199
|
+
options_for_select([
|
|
200
|
+
["Default (index, follow)", ""],
|
|
201
|
+
["index, follow", "index, follow"],
|
|
202
|
+
["noindex, follow", "noindex, follow"],
|
|
203
|
+
["index, nofollow", "index, nofollow"],
|
|
204
|
+
["noindex, nofollow", "noindex, nofollow"]
|
|
205
|
+
], @page.meta_robots),
|
|
206
|
+
{},
|
|
207
|
+
class: "form-control" %>
|
|
208
|
+
<p class="help-text">
|
|
209
|
+
Controls how search engines index this page. Use "noindex" to prevent the page from appearing in search results.
|
|
210
|
+
</p>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</details>
|
|
215
|
+
|
|
216
|
+
<!-- Twitter Card Options -->
|
|
217
|
+
<details class="card" style="margin-bottom: 1.5rem;">
|
|
218
|
+
<summary class="card-header" style="cursor: pointer; list-style: none;">
|
|
219
|
+
<span class="card-title">
|
|
220
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="vertical-align: -2px; margin-right: 0.5rem; color: #1da1f2;">
|
|
221
|
+
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
|
|
222
|
+
</svg>
|
|
223
|
+
Twitter Card Options
|
|
224
|
+
</span>
|
|
225
|
+
<span class="badge badge-gray">Optional</span>
|
|
226
|
+
</summary>
|
|
227
|
+
<div class="card-body">
|
|
228
|
+
<p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1.25rem; padding: 0.75rem; background: var(--bg-main, #f8fafc); border-radius: 0.375rem;">
|
|
229
|
+
Twitter (X) uses its own card format. If left empty, Twitter will use the Open Graph tags above.
|
|
230
|
+
Only fill these if you want different content specifically for Twitter.
|
|
231
|
+
</p>
|
|
232
|
+
|
|
233
|
+
<div class="form-group">
|
|
234
|
+
<%= form.label :twitter_card, "Card Type" %>
|
|
235
|
+
<%= form.select :twitter_card,
|
|
236
|
+
options_for_select([
|
|
237
|
+
["Default (uses Open Graph)", ""],
|
|
238
|
+
["Summary - Small image", "summary"],
|
|
239
|
+
["Summary Large Image - Big image", "summary_large_image"]
|
|
240
|
+
], @page.twitter_card),
|
|
241
|
+
{},
|
|
242
|
+
class: "form-control" %>
|
|
243
|
+
<p class="help-text">"Summary Large Image" displays a large, prominent image. Best for visual content.</p>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem;">
|
|
247
|
+
<div class="form-group">
|
|
248
|
+
<%= form.label :twitter_title, "Twitter Title" %>
|
|
249
|
+
<%= form.text_field :twitter_title, class: "form-control", placeholder: "Title for Twitter (defaults to OG title)" %>
|
|
250
|
+
<p class="help-text">Override the title specifically for Twitter/X.</p>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<div class="form-group">
|
|
254
|
+
<%= form.label :twitter_image, "Twitter Image URL" %>
|
|
255
|
+
<%= form.text_field :twitter_image, class: "form-control", placeholder: "https://example.com/twitter-image.jpg" %>
|
|
256
|
+
<p class="help-text">Recommended: 1200x600 pixels for large image cards.</p>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div class="form-group">
|
|
261
|
+
<%= form.label :twitter_description, "Twitter Description" %>
|
|
262
|
+
<%= form.text_area :twitter_description, class: "form-control", rows: 2, placeholder: "Description for Twitter...", style: "min-height: 60px;" %>
|
|
263
|
+
<p class="help-text">Override the description specifically for Twitter/X.</p>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
</details>
|
|
267
|
+
|
|
268
|
+
<!-- Structured Data -->
|
|
269
|
+
<details class="card" style="margin-bottom: 1.5rem;">
|
|
270
|
+
<summary class="card-header" style="cursor: pointer; list-style: none;">
|
|
271
|
+
<span class="card-title">
|
|
272
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: -2px; margin-right: 0.5rem; color: var(--text-muted);">
|
|
273
|
+
<polyline points="16 18 22 12 16 6"/>
|
|
274
|
+
<polyline points="8 6 2 12 8 18"/>
|
|
275
|
+
</svg>
|
|
276
|
+
Structured Data (JSON-LD)
|
|
277
|
+
</span>
|
|
278
|
+
<span class="badge badge-gray">Advanced</span>
|
|
279
|
+
</summary>
|
|
280
|
+
<div class="card-body">
|
|
281
|
+
<p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1.25rem; padding: 0.75rem; background: var(--bg-main, #f8fafc); border-radius: 0.375rem;">
|
|
282
|
+
Structured data helps search engines understand your content better and can enable rich results (like FAQ dropdowns, ratings, etc.).
|
|
283
|
+
Use <a href="https://schema.org" target="_blank" style="color: var(--primary);">Schema.org</a> format.
|
|
284
|
+
</p>
|
|
285
|
+
|
|
286
|
+
<div class="form-group">
|
|
287
|
+
<%= form.label :structured_data, "JSON-LD Structured Data" %>
|
|
288
|
+
<%= form.text_area :structured_data, class: "form-control", rows: 8, placeholder: '{"@context": "https://schema.org", "@type": "WebPage", "name": "Page Title"}', style: "font-family: monospace; min-height: 150px;" %>
|
|
289
|
+
<p class="help-text">
|
|
290
|
+
Enter valid JSON-LD. This will be injected as a script tag in the page head.
|
|
291
|
+
<a href="https://developers.google.com/search/docs/appearance/structured-data" target="_blank" style="color: var(--primary);">Learn more about structured data</a>.
|
|
292
|
+
</p>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
</details>
|
|
296
|
+
|
|
297
|
+
<!-- Form Actions -->
|
|
298
|
+
<div class="form-actions" style="background: var(--bg-card, white); padding: 1.25rem; border-radius: 0.5rem; border: 1px solid var(--border, #e2e8f0);">
|
|
299
|
+
<%= form.submit "Save Changes", class: "btn btn-primary" %>
|
|
300
|
+
<%= link_to "Cancel", admin_page_path(@page), class: "btn btn-secondary" %>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<% end %>
|
|
304
|
+
|
|
305
|
+
<style>
|
|
306
|
+
details.card > summary {
|
|
307
|
+
display: flex;
|
|
308
|
+
align-items: center;
|
|
309
|
+
justify-content: space-between;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
details.card > summary::-webkit-details-marker {
|
|
313
|
+
display: none;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
details.card > summary::after {
|
|
317
|
+
content: "";
|
|
318
|
+
width: 20px;
|
|
319
|
+
height: 20px;
|
|
320
|
+
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
|
321
|
+
background-repeat: no-repeat;
|
|
322
|
+
background-position: center;
|
|
323
|
+
transition: transform 0.2s ease;
|
|
324
|
+
margin-left: auto;
|
|
325
|
+
flex-shrink: 0;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
details.card[open] > summary::after {
|
|
329
|
+
transform: rotate(180deg);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
details.card > .card-body {
|
|
333
|
+
border-top: 1px solid var(--border, #e2e8f0);
|
|
334
|
+
}
|
|
335
|
+
</style>
|