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,469 @@
|
|
|
1
|
+
<% content_for :page_title, "Version #{@version.version_number} - #{@page.title}" %>
|
|
2
|
+
|
|
3
|
+
<style>
|
|
4
|
+
.version-nav {
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: space-between;
|
|
8
|
+
margin-bottom: 1.5rem;
|
|
9
|
+
}
|
|
10
|
+
.version-nav-arrows {
|
|
11
|
+
display: flex;
|
|
12
|
+
gap: 0.5rem;
|
|
13
|
+
}
|
|
14
|
+
.version-nav-arrow {
|
|
15
|
+
display: inline-flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: 0.25rem;
|
|
18
|
+
padding: 0.5rem 0.75rem;
|
|
19
|
+
background: var(--bg-card);
|
|
20
|
+
border: 1px solid var(--border);
|
|
21
|
+
border-radius: var(--radius);
|
|
22
|
+
color: var(--text-muted);
|
|
23
|
+
text-decoration: none;
|
|
24
|
+
font-size: 0.8125rem;
|
|
25
|
+
transition: all 0.15s ease;
|
|
26
|
+
}
|
|
27
|
+
.version-nav-arrow:hover {
|
|
28
|
+
background: var(--bg-main);
|
|
29
|
+
color: var(--text);
|
|
30
|
+
border-color: var(--primary);
|
|
31
|
+
}
|
|
32
|
+
.version-nav-arrow.disabled {
|
|
33
|
+
opacity: 0.4;
|
|
34
|
+
pointer-events: none;
|
|
35
|
+
}
|
|
36
|
+
.version-info-bar {
|
|
37
|
+
display: flex;
|
|
38
|
+
gap: 1.5rem;
|
|
39
|
+
flex-wrap: wrap;
|
|
40
|
+
padding: 1rem;
|
|
41
|
+
background: var(--bg-main);
|
|
42
|
+
border-radius: var(--radius);
|
|
43
|
+
margin-bottom: 1.5rem;
|
|
44
|
+
}
|
|
45
|
+
.version-info-item {
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
gap: 0.25rem;
|
|
49
|
+
}
|
|
50
|
+
.version-info-label {
|
|
51
|
+
font-size: 0.6875rem;
|
|
52
|
+
text-transform: uppercase;
|
|
53
|
+
letter-spacing: 0.05em;
|
|
54
|
+
color: var(--text-muted);
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
}
|
|
57
|
+
.version-info-value {
|
|
58
|
+
font-size: 0.875rem;
|
|
59
|
+
color: var(--text);
|
|
60
|
+
}
|
|
61
|
+
.diff-tabs {
|
|
62
|
+
display: flex;
|
|
63
|
+
gap: 0;
|
|
64
|
+
border-bottom: 1px solid var(--border);
|
|
65
|
+
margin-bottom: 0;
|
|
66
|
+
}
|
|
67
|
+
.diff-tab {
|
|
68
|
+
padding: 0.75rem 1.25rem;
|
|
69
|
+
background: none;
|
|
70
|
+
border: none;
|
|
71
|
+
border-bottom: 2px solid transparent;
|
|
72
|
+
color: var(--text-muted);
|
|
73
|
+
font-size: 0.875rem;
|
|
74
|
+
font-weight: 500;
|
|
75
|
+
cursor: pointer;
|
|
76
|
+
transition: all 0.15s ease;
|
|
77
|
+
}
|
|
78
|
+
.diff-tab:hover {
|
|
79
|
+
color: var(--text);
|
|
80
|
+
background: var(--bg-main);
|
|
81
|
+
}
|
|
82
|
+
.diff-tab.active {
|
|
83
|
+
color: var(--primary);
|
|
84
|
+
border-bottom-color: var(--primary);
|
|
85
|
+
}
|
|
86
|
+
.diff-panel {
|
|
87
|
+
display: none;
|
|
88
|
+
padding: 1rem;
|
|
89
|
+
}
|
|
90
|
+
.diff-panel.active {
|
|
91
|
+
display: block;
|
|
92
|
+
}
|
|
93
|
+
.diff-container {
|
|
94
|
+
display: grid;
|
|
95
|
+
grid-template-columns: 1fr 1fr;
|
|
96
|
+
gap: 1rem;
|
|
97
|
+
}
|
|
98
|
+
.diff-side {
|
|
99
|
+
background: #0f172a;
|
|
100
|
+
border-radius: var(--radius);
|
|
101
|
+
overflow: hidden;
|
|
102
|
+
}
|
|
103
|
+
.diff-side-header {
|
|
104
|
+
padding: 0.75rem 1rem;
|
|
105
|
+
background: #1e293b;
|
|
106
|
+
font-size: 0.75rem;
|
|
107
|
+
font-weight: 600;
|
|
108
|
+
text-transform: uppercase;
|
|
109
|
+
letter-spacing: 0.05em;
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
justify-content: space-between;
|
|
113
|
+
}
|
|
114
|
+
.diff-side-header.before {
|
|
115
|
+
color: #f87171;
|
|
116
|
+
}
|
|
117
|
+
.diff-side-header.after {
|
|
118
|
+
color: #4ade80;
|
|
119
|
+
}
|
|
120
|
+
.diff-side-content {
|
|
121
|
+
padding: 1rem;
|
|
122
|
+
font-family: 'SF Mono', 'Fira Code', 'Monaco', monospace;
|
|
123
|
+
font-size: 0.75rem;
|
|
124
|
+
line-height: 1.6;
|
|
125
|
+
color: #e2e8f0;
|
|
126
|
+
max-height: 500px;
|
|
127
|
+
overflow: auto;
|
|
128
|
+
white-space: pre-wrap;
|
|
129
|
+
word-break: break-all;
|
|
130
|
+
}
|
|
131
|
+
.unified-diff {
|
|
132
|
+
background: #0f172a;
|
|
133
|
+
border-radius: var(--radius);
|
|
134
|
+
overflow: hidden;
|
|
135
|
+
}
|
|
136
|
+
.unified-diff-header {
|
|
137
|
+
padding: 0.75rem 1rem;
|
|
138
|
+
background: #1e293b;
|
|
139
|
+
font-size: 0.75rem;
|
|
140
|
+
font-weight: 600;
|
|
141
|
+
color: #94a3b8;
|
|
142
|
+
display: flex;
|
|
143
|
+
align-items: center;
|
|
144
|
+
gap: 1rem;
|
|
145
|
+
}
|
|
146
|
+
.unified-diff-content {
|
|
147
|
+
padding: 0;
|
|
148
|
+
font-family: 'SF Mono', 'Fira Code', 'Monaco', monospace;
|
|
149
|
+
font-size: 0.75rem;
|
|
150
|
+
line-height: 1.6;
|
|
151
|
+
max-height: 600px;
|
|
152
|
+
overflow: auto;
|
|
153
|
+
}
|
|
154
|
+
.diff-line {
|
|
155
|
+
display: flex;
|
|
156
|
+
padding: 0 1rem;
|
|
157
|
+
min-height: 1.6em;
|
|
158
|
+
}
|
|
159
|
+
.diff-line-num {
|
|
160
|
+
width: 3rem;
|
|
161
|
+
text-align: right;
|
|
162
|
+
padding-right: 1rem;
|
|
163
|
+
color: #475569;
|
|
164
|
+
user-select: none;
|
|
165
|
+
flex-shrink: 0;
|
|
166
|
+
}
|
|
167
|
+
.diff-line-content {
|
|
168
|
+
flex: 1;
|
|
169
|
+
white-space: pre-wrap;
|
|
170
|
+
word-break: break-all;
|
|
171
|
+
}
|
|
172
|
+
.diff-line.added {
|
|
173
|
+
background: rgba(74, 222, 128, 0.15);
|
|
174
|
+
}
|
|
175
|
+
.diff-line.added .diff-line-content {
|
|
176
|
+
color: #4ade80;
|
|
177
|
+
}
|
|
178
|
+
.diff-line.removed {
|
|
179
|
+
background: rgba(248, 113, 113, 0.15);
|
|
180
|
+
}
|
|
181
|
+
.diff-line.removed .diff-line-content {
|
|
182
|
+
color: #f87171;
|
|
183
|
+
}
|
|
184
|
+
.diff-line.unchanged .diff-line-content {
|
|
185
|
+
color: #94a3b8;
|
|
186
|
+
}
|
|
187
|
+
.diff-stats {
|
|
188
|
+
display: flex;
|
|
189
|
+
gap: 1rem;
|
|
190
|
+
font-size: 0.75rem;
|
|
191
|
+
}
|
|
192
|
+
.diff-stat {
|
|
193
|
+
display: flex;
|
|
194
|
+
align-items: center;
|
|
195
|
+
gap: 0.25rem;
|
|
196
|
+
}
|
|
197
|
+
.diff-stat.additions {
|
|
198
|
+
color: #4ade80;
|
|
199
|
+
}
|
|
200
|
+
.diff-stat.deletions {
|
|
201
|
+
color: #f87171;
|
|
202
|
+
}
|
|
203
|
+
.preview-frame {
|
|
204
|
+
width: 100%;
|
|
205
|
+
height: 500px;
|
|
206
|
+
border: 1px solid var(--border);
|
|
207
|
+
border-radius: var(--radius);
|
|
208
|
+
background: white;
|
|
209
|
+
}
|
|
210
|
+
.no-content {
|
|
211
|
+
padding: 3rem;
|
|
212
|
+
text-align: center;
|
|
213
|
+
color: var(--text-muted);
|
|
214
|
+
}
|
|
215
|
+
</style>
|
|
216
|
+
|
|
217
|
+
<!-- Page Header -->
|
|
218
|
+
<div class="page-header">
|
|
219
|
+
<div class="page-header-left">
|
|
220
|
+
<h2>Version <%= @version.version_number %></h2>
|
|
221
|
+
<p class="page-header-subtitle">
|
|
222
|
+
<%= @page.title %> · <%= @version.created_at.strftime("%B %d, %Y at %l:%M %p") %>
|
|
223
|
+
</p>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="page-header-actions">
|
|
226
|
+
<%= link_to versions_admin_page_path(@page), class: "btn btn-secondary" do %>
|
|
227
|
+
<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">
|
|
228
|
+
<line x1="19" y1="12" x2="5" y2="12"/>
|
|
229
|
+
<polyline points="12 19 5 12 12 5"/>
|
|
230
|
+
</svg>
|
|
231
|
+
Back to History
|
|
232
|
+
<% end %>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<!-- Version Navigation -->
|
|
237
|
+
<div class="version-nav">
|
|
238
|
+
<div class="version-nav-arrows">
|
|
239
|
+
<% if @previous_version %>
|
|
240
|
+
<%= link_to admin_page_version_path(@page, @previous_version), class: "version-nav-arrow" do %>
|
|
241
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
242
|
+
<polyline points="15 18 9 12 15 6"/>
|
|
243
|
+
</svg>
|
|
244
|
+
Version <%= @previous_version.version_number %>
|
|
245
|
+
<% end %>
|
|
246
|
+
<% else %>
|
|
247
|
+
<span class="version-nav-arrow disabled">
|
|
248
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
249
|
+
<polyline points="15 18 9 12 15 6"/>
|
|
250
|
+
</svg>
|
|
251
|
+
No older version
|
|
252
|
+
</span>
|
|
253
|
+
<% end %>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<span style="color: var(--text-muted); font-size: 0.875rem;">
|
|
257
|
+
Version <%= @version.version_number %> of <%= @page.versions.count %>
|
|
258
|
+
</span>
|
|
259
|
+
|
|
260
|
+
<div class="version-nav-arrows">
|
|
261
|
+
<% if @next_version %>
|
|
262
|
+
<%= link_to admin_page_version_path(@page, @next_version), class: "version-nav-arrow" do %>
|
|
263
|
+
Version <%= @next_version.version_number %>
|
|
264
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
265
|
+
<polyline points="9 18 15 12 9 6"/>
|
|
266
|
+
</svg>
|
|
267
|
+
<% end %>
|
|
268
|
+
<% else %>
|
|
269
|
+
<span class="version-nav-arrow disabled">
|
|
270
|
+
Latest version
|
|
271
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
272
|
+
<polyline points="9 18 15 12 9 6"/>
|
|
273
|
+
</svg>
|
|
274
|
+
</span>
|
|
275
|
+
<% end %>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
<!-- Version Info -->
|
|
280
|
+
<div class="version-info-bar">
|
|
281
|
+
<div class="version-info-item">
|
|
282
|
+
<span class="version-info-label">Created</span>
|
|
283
|
+
<span class="version-info-value"><%= time_ago_in_words(@version.created_at) %> ago</span>
|
|
284
|
+
</div>
|
|
285
|
+
<% if @version.changed_by.present? %>
|
|
286
|
+
<div class="version-info-item">
|
|
287
|
+
<span class="version-info-label">Changed by</span>
|
|
288
|
+
<span class="version-info-value"><%= @version.changed_by %></span>
|
|
289
|
+
</div>
|
|
290
|
+
<% end %>
|
|
291
|
+
<div class="version-info-item">
|
|
292
|
+
<span class="version-info-label">Size before</span>
|
|
293
|
+
<span class="version-info-value"><%= number_to_human_size(@version.content_size_before || 0) %></span>
|
|
294
|
+
</div>
|
|
295
|
+
<div class="version-info-item">
|
|
296
|
+
<span class="version-info-label">Size after</span>
|
|
297
|
+
<span class="version-info-value"><%= number_to_human_size(@version.content_size_after || 0) %></span>
|
|
298
|
+
</div>
|
|
299
|
+
<div class="version-info-item">
|
|
300
|
+
<span class="version-info-label">Difference</span>
|
|
301
|
+
<span class="version-info-value">
|
|
302
|
+
<% diff = @version.size_difference %>
|
|
303
|
+
<% if diff > 0 %>
|
|
304
|
+
<span style="color: var(--success);">+<%= number_to_human_size(diff) %></span>
|
|
305
|
+
<% elsif diff < 0 %>
|
|
306
|
+
<span style="color: var(--danger);"><%= number_to_human_size(diff) %></span>
|
|
307
|
+
<% else %>
|
|
308
|
+
<span style="color: var(--text-muted);">No change</span>
|
|
309
|
+
<% end %>
|
|
310
|
+
</span>
|
|
311
|
+
</div>
|
|
312
|
+
<% if @version.change_summary.present? %>
|
|
313
|
+
<div class="version-info-item">
|
|
314
|
+
<span class="version-info-label">Summary</span>
|
|
315
|
+
<span class="version-info-value"><%= @version.change_summary %></span>
|
|
316
|
+
</div>
|
|
317
|
+
<% end %>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
<!-- Diff Tabs -->
|
|
321
|
+
<div class="card">
|
|
322
|
+
<div class="diff-tabs">
|
|
323
|
+
<button type="button" class="diff-tab active" onclick="showDiffPanel('unified')">Unified Diff</button>
|
|
324
|
+
<button type="button" class="diff-tab" onclick="showDiffPanel('side-by-side')">Side by Side</button>
|
|
325
|
+
<button type="button" class="diff-tab" onclick="showDiffPanel('before')">Before</button>
|
|
326
|
+
<button type="button" class="diff-tab" onclick="showDiffPanel('after')">After</button>
|
|
327
|
+
<button type="button" class="diff-tab" onclick="showDiffPanel('preview')">Preview After</button>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
<!-- Unified Diff -->
|
|
331
|
+
<div id="panel-unified" class="diff-panel active">
|
|
332
|
+
<% if @version.content_diff.present? %>
|
|
333
|
+
<%
|
|
334
|
+
lines = @version.content_diff.to_s.lines
|
|
335
|
+
additions = lines.count { |l| l.start_with?('+') }
|
|
336
|
+
deletions = lines.count { |l| l.start_with?('-') }
|
|
337
|
+
%>
|
|
338
|
+
<div class="unified-diff">
|
|
339
|
+
<div class="unified-diff-header">
|
|
340
|
+
<span>Content changes</span>
|
|
341
|
+
<div class="diff-stats">
|
|
342
|
+
<span class="diff-stat additions">+<%= additions %> additions</span>
|
|
343
|
+
<span class="diff-stat deletions">-<%= deletions %> deletions</span>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
<div class="unified-diff-content">
|
|
347
|
+
<% lines.each_with_index do |line, index| %>
|
|
348
|
+
<%
|
|
349
|
+
css_class = if line.start_with?('+')
|
|
350
|
+
'added'
|
|
351
|
+
elsif line.start_with?('-')
|
|
352
|
+
'removed'
|
|
353
|
+
else
|
|
354
|
+
'unchanged'
|
|
355
|
+
end
|
|
356
|
+
%>
|
|
357
|
+
<div class="diff-line <%= css_class %>">
|
|
358
|
+
<span class="diff-line-num"><%= index + 1 %></span>
|
|
359
|
+
<span class="diff-line-content"><%= line.chomp %></span>
|
|
360
|
+
</div>
|
|
361
|
+
<% end %>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
<% else %>
|
|
365
|
+
<div class="no-content">
|
|
366
|
+
<p>No diff available for this version.</p>
|
|
367
|
+
</div>
|
|
368
|
+
<% end %>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
<!-- Side by Side -->
|
|
372
|
+
<div id="panel-side-by-side" class="diff-panel">
|
|
373
|
+
<div class="diff-container">
|
|
374
|
+
<div class="diff-side">
|
|
375
|
+
<div class="diff-side-header before">
|
|
376
|
+
<span>Before</span>
|
|
377
|
+
<span style="font-weight: 400; text-transform: none;"><%= number_to_human_size(@version.content_size_before || 0) %></span>
|
|
378
|
+
</div>
|
|
379
|
+
<div class="diff-side-content"><%= @version.content_before %></div>
|
|
380
|
+
</div>
|
|
381
|
+
<div class="diff-side">
|
|
382
|
+
<div class="diff-side-header after">
|
|
383
|
+
<span>After</span>
|
|
384
|
+
<span style="font-weight: 400; text-transform: none;"><%= number_to_human_size(@version.content_size_after || 0) %></span>
|
|
385
|
+
</div>
|
|
386
|
+
<div class="diff-side-content"><%= @version.content_after %></div>
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
<!-- Before Only -->
|
|
392
|
+
<div id="panel-before" class="diff-panel">
|
|
393
|
+
<% if @version.content_before.present? %>
|
|
394
|
+
<div class="unified-diff">
|
|
395
|
+
<div class="unified-diff-header">
|
|
396
|
+
<span>Content before this change</span>
|
|
397
|
+
<span style="font-weight: 400;"><%= number_to_human_size(@version.content_size_before || 0) %></span>
|
|
398
|
+
</div>
|
|
399
|
+
<div class="unified-diff-content">
|
|
400
|
+
<% @version.content_before.to_s.lines.each_with_index do |line, index| %>
|
|
401
|
+
<div class="diff-line unchanged">
|
|
402
|
+
<span class="diff-line-num"><%= index + 1 %></span>
|
|
403
|
+
<span class="diff-line-content"><%= line.chomp %></span>
|
|
404
|
+
</div>
|
|
405
|
+
<% end %>
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
<% else %>
|
|
409
|
+
<div class="no-content">
|
|
410
|
+
<p>No previous content (this may be the first version).</p>
|
|
411
|
+
</div>
|
|
412
|
+
<% end %>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
<!-- After Only -->
|
|
416
|
+
<div id="panel-after" class="diff-panel">
|
|
417
|
+
<% if @version.content_after.present? %>
|
|
418
|
+
<div class="unified-diff">
|
|
419
|
+
<div class="unified-diff-header">
|
|
420
|
+
<span>Content after this change</span>
|
|
421
|
+
<span style="font-weight: 400;"><%= number_to_human_size(@version.content_size_after || 0) %></span>
|
|
422
|
+
</div>
|
|
423
|
+
<div class="unified-diff-content">
|
|
424
|
+
<% @version.content_after.to_s.lines.each_with_index do |line, index| %>
|
|
425
|
+
<div class="diff-line unchanged">
|
|
426
|
+
<span class="diff-line-num"><%= index + 1 %></span>
|
|
427
|
+
<span class="diff-line-content"><%= line.chomp %></span>
|
|
428
|
+
</div>
|
|
429
|
+
<% end %>
|
|
430
|
+
</div>
|
|
431
|
+
</div>
|
|
432
|
+
<% else %>
|
|
433
|
+
<div class="no-content">
|
|
434
|
+
<p>No content recorded for this version.</p>
|
|
435
|
+
</div>
|
|
436
|
+
<% end %>
|
|
437
|
+
</div>
|
|
438
|
+
|
|
439
|
+
<!-- Preview -->
|
|
440
|
+
<div id="panel-preview" class="diff-panel">
|
|
441
|
+
<% if @version.content_after.present? %>
|
|
442
|
+
<iframe id="preview-frame" class="preview-frame" srcdoc="<%= @version.content_after.gsub('"', '"') %>"></iframe>
|
|
443
|
+
<% else %>
|
|
444
|
+
<div class="no-content">
|
|
445
|
+
<p>No content to preview.</p>
|
|
446
|
+
</div>
|
|
447
|
+
<% end %>
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
|
|
451
|
+
<script>
|
|
452
|
+
function showDiffPanel(panelId) {
|
|
453
|
+
// Hide all panels
|
|
454
|
+
document.querySelectorAll('.diff-panel').forEach(panel => {
|
|
455
|
+
panel.classList.remove('active');
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// Deactivate all tabs
|
|
459
|
+
document.querySelectorAll('.diff-tab').forEach(tab => {
|
|
460
|
+
tab.classList.remove('active');
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// Show selected panel
|
|
464
|
+
document.getElementById('panel-' + panelId).classList.add('active');
|
|
465
|
+
|
|
466
|
+
// Activate selected tab
|
|
467
|
+
event.target.classList.add('active');
|
|
468
|
+
}
|
|
469
|
+
</script>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<% if page.errors.any? %>
|
|
2
|
+
<div class="error-messages">
|
|
3
|
+
<strong><%= pluralize(page.errors.count, "error") %> prohibited this page from being saved:</strong>
|
|
4
|
+
<ul>
|
|
5
|
+
<% page.errors.full_messages.each do |message| %>
|
|
6
|
+
<li><%= message %></li>
|
|
7
|
+
<% end %>
|
|
8
|
+
</ul>
|
|
9
|
+
</div>
|
|
10
|
+
<% end %>
|
|
11
|
+
|
|
12
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem;">
|
|
13
|
+
<div class="form-group">
|
|
14
|
+
<%= form.label :title %>
|
|
15
|
+
<%= form.text_field :title, class: "form-control", placeholder: "Enter page title" %>
|
|
16
|
+
<p class="help-text">The title displayed in browsers and search results.</p>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="form-group">
|
|
20
|
+
<%= form.label :slug, "URL Slug" %>
|
|
21
|
+
<%= form.text_field :slug, class: "form-control", placeholder: "Leave blank for auto-generated" %>
|
|
22
|
+
<p class="help-text">The URL path for this page (e.g., "about-us").</p>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="form-group">
|
|
27
|
+
<%= form.label :page_type_id, "Page Type" %>
|
|
28
|
+
<%= form.collection_select :page_type_id, ActiveCanvas::PageType.order(:name), :id, :name, {}, class: "form-control" %>
|
|
29
|
+
<p class="help-text">Determines the layout template used for this page.</p>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<% if ActiveCanvas::Partial.table_exists? %>
|
|
33
|
+
<div class="form-group">
|
|
34
|
+
<label style="font-weight: 500; margin-bottom: 0.75rem; display: block;">Header & Footer</label>
|
|
35
|
+
<div style="display: flex; gap: 2rem; background: var(--bg-main, #f8fafc); padding: 1rem; border-radius: 0.5rem; border: 1px solid var(--border, #e2e8f0);">
|
|
36
|
+
<div class="form-check">
|
|
37
|
+
<%= form.check_box :show_header, id: "page_show_header", checked: page.show_header? %>
|
|
38
|
+
<label for="page_show_header">Show Header</label>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="form-check">
|
|
41
|
+
<%= form.check_box :show_footer, id: "page_show_footer", checked: page.show_footer? %>
|
|
42
|
+
<label for="page_show_footer">Show Footer</label>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<p class="help-text">Include the global header and/or footer on this page.</p>
|
|
46
|
+
</div>
|
|
47
|
+
<% end %>
|
|
48
|
+
|
|
49
|
+
<div class="form-group">
|
|
50
|
+
<div class="form-check" style="background: var(--bg-main, #f8fafc); padding: 1rem; border-radius: 0.5rem; border: 1px solid var(--border, #e2e8f0);">
|
|
51
|
+
<%= form.check_box :published, id: "page_published" %>
|
|
52
|
+
<label for="page_published" style="font-weight: 500;">
|
|
53
|
+
Publish this page
|
|
54
|
+
</label>
|
|
55
|
+
<p class="help-text" style="margin-left: 1.5rem; margin-top: 0.25rem;">Published pages are visible to the public. Draft pages are only visible in the admin.</p>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="form-actions">
|
|
60
|
+
<%= form.submit class: "btn btn-primary" %>
|
|
61
|
+
<%= link_to "Cancel", admin_pages_path, class: "btn btn-secondary" %>
|
|
62
|
+
</div>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Edit Content - <%= @page.title %></title>
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<style>
|
|
7
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
8
|
+
|
|
9
|
+
body {
|
|
10
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
11
|
+
background: #1e1e1e;
|
|
12
|
+
color: #d4d4d4;
|
|
13
|
+
height: 100vh;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.toolbar {
|
|
19
|
+
background: #2d2d2d;
|
|
20
|
+
padding: 0.75rem 1rem;
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: space-between;
|
|
24
|
+
border-bottom: 1px solid #404040;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.toolbar-left {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
gap: 1rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.page-title {
|
|
34
|
+
font-size: 0.875rem;
|
|
35
|
+
color: #888;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.page-title strong {
|
|
39
|
+
color: #d4d4d4;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.toolbar-actions {
|
|
43
|
+
display: flex;
|
|
44
|
+
gap: 0.5rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.btn {
|
|
48
|
+
padding: 0.5rem 1rem;
|
|
49
|
+
border-radius: 0.25rem;
|
|
50
|
+
font-size: 0.875rem;
|
|
51
|
+
font-weight: 500;
|
|
52
|
+
text-decoration: none;
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
border: none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.btn-primary {
|
|
58
|
+
background: #2563eb;
|
|
59
|
+
color: white;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.btn-primary:hover {
|
|
63
|
+
background: #1d4ed8;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.btn-secondary {
|
|
67
|
+
background: #404040;
|
|
68
|
+
color: #d4d4d4;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.btn-secondary:hover {
|
|
72
|
+
background: #505050;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.editor-container {
|
|
76
|
+
flex: 1;
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: column;
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.editor {
|
|
83
|
+
flex: 1;
|
|
84
|
+
width: 100%;
|
|
85
|
+
padding: 1rem;
|
|
86
|
+
background: #1e1e1e;
|
|
87
|
+
color: #d4d4d4;
|
|
88
|
+
border: none;
|
|
89
|
+
resize: none;
|
|
90
|
+
font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', monospace;
|
|
91
|
+
font-size: 14px;
|
|
92
|
+
line-height: 1.6;
|
|
93
|
+
outline: none;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.editor:focus {
|
|
97
|
+
background: #1e1e1e;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.flash {
|
|
101
|
+
padding: 0.5rem 1rem;
|
|
102
|
+
font-size: 0.875rem;
|
|
103
|
+
background: #166534;
|
|
104
|
+
color: white;
|
|
105
|
+
}
|
|
106
|
+
</style>
|
|
107
|
+
</head>
|
|
108
|
+
<body>
|
|
109
|
+
<%= form_with model: @page, url: update_content_admin_page_path(@page), method: :patch, id: "content-form" do |form| %>
|
|
110
|
+
<div class="toolbar">
|
|
111
|
+
<div class="toolbar-left">
|
|
112
|
+
<span class="page-title">Editing: <strong><%= @page.title %></strong></span>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="toolbar-actions">
|
|
115
|
+
<%= link_to "Back", admin_page_path(@page), class: "btn btn-secondary" %>
|
|
116
|
+
<%= form.submit "Save", class: "btn btn-primary" %>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<% if notice %>
|
|
121
|
+
<div class="flash"><%= notice %></div>
|
|
122
|
+
<% end %>
|
|
123
|
+
|
|
124
|
+
<div class="editor-container">
|
|
125
|
+
<%= form.text_area :content, class: "editor", autofocus: true, spellcheck: false %>
|
|
126
|
+
</div>
|
|
127
|
+
<% end %>
|
|
128
|
+
|
|
129
|
+
<script>
|
|
130
|
+
// Keyboard shortcut: Cmd/Ctrl+S to save
|
|
131
|
+
document.addEventListener('keydown', function(e) {
|
|
132
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 's') {
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
document.getElementById('content-form').submit();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
</script>
|
|
138
|
+
</body>
|
|
139
|
+
</html>
|