active_canvas 0.0.5 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +23 -1
- data/app/assets/javascripts/active_canvas/admin/data_panel.js +357 -0
- data/app/assets/javascripts/active_canvas/admin/field_builder.js +84 -0
- data/app/assets/javascripts/active_canvas/admin/media_select_preview.js +11 -0
- data/app/assets/javascripts/active_canvas/editor/ac_bindings.js +81 -0
- data/app/assets/javascripts/active_canvas/editor/ac_chips.js +192 -0
- data/app/assets/javascripts/active_canvas/editor/code_panel.js +10 -7
- data/app/assets/javascripts/active_canvas/editor/directive_traits.js +95 -0
- data/app/assets/javascripts/active_canvas/editor/live_data.js +178 -0
- data/app/assets/javascripts/active_canvas/editor/panels.js +23 -7
- data/app/assets/javascripts/active_canvas/editor/preview_modal.js +112 -0
- data/app/assets/javascripts/active_canvas/editor/template_validator.js +95 -0
- data/app/assets/javascripts/active_canvas/editor.js +21 -3
- data/app/assets/stylesheets/active_canvas/editor.css +626 -0
- data/app/controllers/active_canvas/admin/application_controller.rb +8 -0
- data/app/controllers/active_canvas/admin/collection_items_controller.rb +93 -0
- data/app/controllers/active_canvas/admin/collections_controller.rb +51 -0
- data/app/controllers/active_canvas/admin/form_submissions_controller.rb +51 -0
- data/app/controllers/active_canvas/admin/pages_controller.rb +127 -5
- data/app/controllers/active_canvas/form_submissions_controller.rb +90 -0
- data/app/controllers/active_canvas/pages_controller.rb +8 -0
- data/app/controllers/active_canvas/redirects_controller.rb +10 -0
- data/app/helpers/active_canvas/admin/collection_items_helper.rb +33 -0
- data/app/models/active_canvas/auto_drop.rb +69 -0
- data/app/models/active_canvas/collection.rb +98 -0
- data/app/models/active_canvas/collection_item.rb +66 -0
- data/app/models/active_canvas/collection_item_version.rb +18 -0
- data/app/models/active_canvas/current.rb +2 -0
- data/app/models/active_canvas/form_submission.rb +9 -0
- data/app/models/active_canvas/page.rb +76 -15
- data/app/models/active_canvas/page_redirect.rb +7 -0
- data/app/services/active_canvas/collection_schema.rb +110 -0
- data/app/services/active_canvas/collection_source.rb +81 -0
- data/app/services/active_canvas/content_sanitizer.rb +33 -21
- data/app/services/active_canvas/form_schema.rb +48 -0
- data/app/services/active_canvas/form_stamper.rb +86 -0
- data/app/services/active_canvas/template_renderer/binding_resolver.rb +138 -0
- data/app/services/active_canvas/template_renderer/directive_expander.rb +54 -0
- data/app/services/active_canvas/template_renderer.rb +160 -0
- data/app/views/active_canvas/admin/collection_items/_field_input.html.erb +40 -0
- data/app/views/active_canvas/admin/collection_items/_form.html.erb +28 -0
- data/app/views/active_canvas/admin/collection_items/edit.html.erb +24 -0
- data/app/views/active_canvas/admin/collection_items/history.html.erb +34 -0
- data/app/views/active_canvas/admin/collection_items/index.html.erb +63 -0
- data/app/views/active_canvas/admin/collection_items/new.html.erb +9 -0
- data/app/views/active_canvas/admin/collections/_field_builder.html.erb +35 -0
- data/app/views/active_canvas/admin/collections/_form.html.erb +31 -0
- data/app/views/active_canvas/admin/collections/edit.html.erb +10 -0
- data/app/views/active_canvas/admin/collections/index.html.erb +45 -0
- data/app/views/active_canvas/admin/collections/new.html.erb +9 -0
- data/app/views/active_canvas/admin/form_submissions/index.html.erb +80 -0
- data/app/views/active_canvas/admin/form_submissions/show.html.erb +99 -0
- data/app/views/active_canvas/admin/pages/_data_panel.html.erb +81 -0
- data/app/views/active_canvas/admin/pages/_form.html.erb +10 -0
- data/app/views/active_canvas/admin/pages/editor.html.erb +62 -1
- data/app/views/active_canvas/pages/show.html.erb +4 -1
- data/app/views/layouts/active_canvas/admin/application.html.erb +15 -0
- data/config/routes.rb +20 -0
- data/db/migrate/20260517100001_add_template_enabled_and_bindings_to_active_canvas_pages.rb +6 -0
- data/db/migrate/20260517100002_add_bindings_to_active_canvas_page_versions.rb +6 -0
- data/db/migrate/20260702000001_create_active_canvas_page_redirects.rb +11 -0
- data/db/migrate/20260702000002_create_active_canvas_form_submissions.rb +14 -0
- data/db/migrate/20260702000003_create_active_canvas_collections.rb +12 -0
- data/db/migrate/20260702000004_create_active_canvas_collection_items.rb +16 -0
- data/db/migrate/20260702000005_create_active_canvas_collection_item_versions.rb +17 -0
- data/lib/active_canvas/configuration.rb +32 -1
- data/lib/active_canvas/data_sources/errors.rb +51 -0
- data/lib/active_canvas/data_sources/param_spec.rb +64 -0
- data/lib/active_canvas/data_sources/registration.rb +60 -0
- data/lib/active_canvas/data_sources/source.rb +47 -0
- data/lib/active_canvas/data_sources.rb +54 -0
- data/lib/active_canvas/engine.rb +15 -0
- data/lib/active_canvas/version.rb +1 -1
- data/lib/active_canvas.rb +6 -0
- metadata +85 -3
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<% content_for :page_title, "Collections" %>
|
|
2
|
+
|
|
3
|
+
<div class="page-header">
|
|
4
|
+
<h2>Collections</h2>
|
|
5
|
+
<%= link_to "New collection", new_admin_collection_path, class: "btn btn-primary" %>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="card">
|
|
9
|
+
<% if @collections.any? %>
|
|
10
|
+
<table>
|
|
11
|
+
<thead>
|
|
12
|
+
<tr>
|
|
13
|
+
<th>Name</th>
|
|
14
|
+
<th>Slug</th>
|
|
15
|
+
<th>Fields</th>
|
|
16
|
+
<th>Items</th>
|
|
17
|
+
<th></th>
|
|
18
|
+
</tr>
|
|
19
|
+
</thead>
|
|
20
|
+
<tbody>
|
|
21
|
+
<% @collections.each do |collection| %>
|
|
22
|
+
<tr>
|
|
23
|
+
<td><%= collection.name %></td>
|
|
24
|
+
<td><code><%= collection.slug %></code></td>
|
|
25
|
+
<td><%= collection.fields.size %></td>
|
|
26
|
+
<td><%= collection.items.count %></td>
|
|
27
|
+
<td class="actions">
|
|
28
|
+
<%= link_to "Items", admin_collection_items_path(collection), class: "btn btn-sm btn-secondary" %>
|
|
29
|
+
<%= link_to "Edit", edit_admin_collection_path(collection), class: "btn btn-sm btn-secondary" %>
|
|
30
|
+
<%= button_to "Delete", admin_collection_path(collection), method: :delete,
|
|
31
|
+
class: "btn btn-sm btn-danger", form: { style: "display:inline" },
|
|
32
|
+
onclick: "return confirm('Delete this collection and all its items?')" %>
|
|
33
|
+
</td>
|
|
34
|
+
</tr>
|
|
35
|
+
<% end %>
|
|
36
|
+
</tbody>
|
|
37
|
+
</table>
|
|
38
|
+
<% else %>
|
|
39
|
+
<div class="empty-state">
|
|
40
|
+
<h3>No collections yet</h3>
|
|
41
|
+
<p>Create a collection to manage typed content you can loop over in pages.</p>
|
|
42
|
+
<%= link_to "New collection", new_admin_collection_path, class: "btn btn-primary" %>
|
|
43
|
+
</div>
|
|
44
|
+
<% end %>
|
|
45
|
+
</div>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<% content_for :page_title, "Form Submissions" %>
|
|
2
|
+
|
|
3
|
+
<!-- Page Header -->
|
|
4
|
+
<div class="page-header">
|
|
5
|
+
<div class="page-header-left">
|
|
6
|
+
<h2>Form Submissions</h2>
|
|
7
|
+
<p class="page-header-subtitle">Browse and export submissions from your page forms</p>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<!-- Filters -->
|
|
12
|
+
<div class="card" style="margin-bottom: 1.5rem;">
|
|
13
|
+
<div class="card-body">
|
|
14
|
+
<%= form_with url: admin_form_submissions_path, method: :get, local: true do %>
|
|
15
|
+
<div style="display: flex; gap: 0.75rem; align-items: flex-end; flex-wrap: wrap;">
|
|
16
|
+
<label style="display: flex; flex-direction: column; gap: 0.375rem;">
|
|
17
|
+
<span style="font-size: 0.75rem; font-weight: 600; color: var(--text-muted);">Page</span>
|
|
18
|
+
<%= select_tag :page_id,
|
|
19
|
+
options_from_collection_for_select(ActiveCanvas::Page.order(:title), :id, :title, params[:page_id]),
|
|
20
|
+
include_blank: "All pages", class: "form-control" %>
|
|
21
|
+
</label>
|
|
22
|
+
<label style="display: flex; flex-direction: column; gap: 0.375rem;">
|
|
23
|
+
<span style="font-size: 0.75rem; font-weight: 600; color: var(--text-muted);">Form</span>
|
|
24
|
+
<% form_key_scope = params[:page_id].present? ? ActiveCanvas::FormSubmission.where(page_id: params[:page_id]) : ActiveCanvas::FormSubmission %>
|
|
25
|
+
<%= select_tag :form_key,
|
|
26
|
+
options_for_select(form_key_scope.distinct.pluck(:form_key).sort, params[:form_key]),
|
|
27
|
+
include_blank: "All forms", class: "form-control" %>
|
|
28
|
+
</label>
|
|
29
|
+
<%= submit_tag "Filter", class: "btn btn-secondary" %>
|
|
30
|
+
<%= link_to "Export CSV", admin_form_submissions_path(format: :csv, page_id: params[:page_id], form_key: params[:form_key]), class: "btn btn-secondary" %>
|
|
31
|
+
</div>
|
|
32
|
+
<% end %>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- Submissions Table -->
|
|
37
|
+
<% if @submissions.any? %>
|
|
38
|
+
<div class="card">
|
|
39
|
+
<table>
|
|
40
|
+
<thead>
|
|
41
|
+
<tr>
|
|
42
|
+
<th>Page</th>
|
|
43
|
+
<th>Form</th>
|
|
44
|
+
<th>Data</th>
|
|
45
|
+
<th>IP</th>
|
|
46
|
+
<th>Received</th>
|
|
47
|
+
<th></th>
|
|
48
|
+
</tr>
|
|
49
|
+
</thead>
|
|
50
|
+
<tbody>
|
|
51
|
+
<% @submissions.each do |submission| %>
|
|
52
|
+
<tr>
|
|
53
|
+
<td><%= submission.page.title %></td>
|
|
54
|
+
<td><span class="badge badge-gray"><%= submission.form_key %></span></td>
|
|
55
|
+
<td><%= truncate(submission.data.map { |k, v| "#{k}: #{v}" }.join(" · "), length: 80) %></td>
|
|
56
|
+
<td><code><%= submission.ip %></code></td>
|
|
57
|
+
<td><span style="color: var(--text-muted);"><%= submission.created_at.strftime("%b %d, %Y at %l:%M %p") %></span></td>
|
|
58
|
+
<td class="actions">
|
|
59
|
+
<%= link_to "View", admin_form_submission_path(submission), class: "btn btn-secondary btn-sm" %>
|
|
60
|
+
</td>
|
|
61
|
+
</tr>
|
|
62
|
+
<% end %>
|
|
63
|
+
</tbody>
|
|
64
|
+
</table>
|
|
65
|
+
</div>
|
|
66
|
+
<% if @submissions.size == ActiveCanvas::Admin::FormSubmissionsController::MAX_ROWS %>
|
|
67
|
+
<p class="help-text" style="margin-top: 0.75rem;">Showing the most recent <%= ActiveCanvas::Admin::FormSubmissionsController::MAX_ROWS %> submissions.</p>
|
|
68
|
+
<% end %>
|
|
69
|
+
<% else %>
|
|
70
|
+
<div class="card">
|
|
71
|
+
<div class="empty-state">
|
|
72
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
73
|
+
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
|
|
74
|
+
<polyline points="22,6 12,13 2,6"/>
|
|
75
|
+
</svg>
|
|
76
|
+
<h3>No submissions yet</h3>
|
|
77
|
+
<p>Submissions from your published page forms will appear here</p>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
<% end %>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<% content_for :page_title, "#{@submission.form_key} — #{@submission.page.title}" %>
|
|
2
|
+
|
|
3
|
+
<!-- Page Header -->
|
|
4
|
+
<div class="page-header">
|
|
5
|
+
<div class="page-header-left">
|
|
6
|
+
<h2><%= @submission.form_key %> — <%= @submission.page.title %></h2>
|
|
7
|
+
<p class="page-header-subtitle">Received <%= time_ago_in_words(@submission.created_at) %> ago</p>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="page-header-actions">
|
|
10
|
+
<%= link_to admin_form_submissions_path, class: "btn btn-ghost" do %>
|
|
11
|
+
<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">
|
|
12
|
+
<line x1="19" y1="12" x2="5" y2="12"/>
|
|
13
|
+
<polyline points="12 19 5 12 12 5"/>
|
|
14
|
+
</svg>
|
|
15
|
+
Back to Submissions
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<!-- Submitted Fields -->
|
|
21
|
+
<div class="card" style="margin-bottom: 1.5rem;">
|
|
22
|
+
<div class="card-header">
|
|
23
|
+
<span class="card-title">Submitted Fields</span>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="card-body" style="padding: 0;">
|
|
26
|
+
<% if @submission.data.any? %>
|
|
27
|
+
<% @submission.data.each do |key, value| %>
|
|
28
|
+
<div class="detail-row">
|
|
29
|
+
<div class="detail-label"><%= key %></div>
|
|
30
|
+
<div class="detail-value" style="white-space: pre-wrap;"><%= value %></div>
|
|
31
|
+
</div>
|
|
32
|
+
<% end %>
|
|
33
|
+
<% else %>
|
|
34
|
+
<div class="detail-row">
|
|
35
|
+
<div class="detail-value" style="color: var(--text-muted);">No fields were submitted.</div>
|
|
36
|
+
</div>
|
|
37
|
+
<% end %>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<!-- Metadata -->
|
|
42
|
+
<div class="card" style="margin-bottom: 1.5rem;">
|
|
43
|
+
<div class="card-header">
|
|
44
|
+
<span class="card-title">Details</span>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="card-body" style="padding: 0;">
|
|
47
|
+
<div class="detail-row">
|
|
48
|
+
<div class="detail-label">Received</div>
|
|
49
|
+
<div class="detail-value"><%= @submission.created_at.strftime("%B %d, %Y at %l:%M %p") %></div>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="detail-row">
|
|
52
|
+
<div class="detail-label">IP Address</div>
|
|
53
|
+
<div class="detail-value"><code><%= @submission.ip %></code></div>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="detail-row">
|
|
56
|
+
<div class="detail-label">User Agent</div>
|
|
57
|
+
<div class="detail-value" style="word-break: break-all;"><%= @submission.user_agent %></div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<!-- Danger Zone -->
|
|
63
|
+
<div class="card" style="border-color: #fecaca;">
|
|
64
|
+
<div class="card-header" style="background: var(--danger-light);">
|
|
65
|
+
<span class="card-title" style="color: var(--danger);">Danger Zone</span>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="card-body">
|
|
68
|
+
<p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem;">
|
|
69
|
+
Deleting this submission removes it permanently.
|
|
70
|
+
</p>
|
|
71
|
+
<%= button_to "Delete Submission", admin_form_submission_path(@submission), method: :delete,
|
|
72
|
+
class: "btn btn-danger", data: { turbo_confirm: "Delete this submission?" } %>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<style>
|
|
77
|
+
.detail-row {
|
|
78
|
+
padding: 0.875rem 1.25rem;
|
|
79
|
+
border-bottom: 1px solid var(--border);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.detail-row:last-child {
|
|
83
|
+
border-bottom: none;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.detail-row .detail-label {
|
|
87
|
+
font-size: 0.6875rem;
|
|
88
|
+
text-transform: uppercase;
|
|
89
|
+
letter-spacing: 0.05em;
|
|
90
|
+
color: var(--text-muted);
|
|
91
|
+
font-weight: 600;
|
|
92
|
+
margin-bottom: 0.25rem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.detail-row .detail-value {
|
|
96
|
+
font-size: 0.875rem;
|
|
97
|
+
color: var(--text);
|
|
98
|
+
}
|
|
99
|
+
</style>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<%# app/views/active_canvas/admin/pages/_data_panel.html.erb %>
|
|
2
|
+
<div id="data-panel-container" class="data-panel" style="display: none;"
|
|
3
|
+
data-page-id="<%= @page.id %>"
|
|
4
|
+
data-template-enabled="<%= @page.template_enabled? %>"
|
|
5
|
+
data-data-sources-url="<%= data_sources_admin_pages_path(format: :json) %>"
|
|
6
|
+
data-validate-url="<%= validate_template_admin_page_path(@page) %>"
|
|
7
|
+
data-sample-data-url="<%= sample_data_admin_page_path(@page) %>"
|
|
8
|
+
data-chip-values-url="<%= chip_values_admin_page_path(@page) %>">
|
|
9
|
+
|
|
10
|
+
<div class="data-panel-header">
|
|
11
|
+
<span class="data-panel-title">Bindings</span>
|
|
12
|
+
<button type="button" id="ac-add-binding-btn" class="data-panel-add-btn" title="Add a new binding">
|
|
13
|
+
<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">
|
|
14
|
+
<line x1="12" y1="5" x2="12" y2="19"/>
|
|
15
|
+
<line x1="5" y1="12" x2="19" y2="12"/>
|
|
16
|
+
</svg>
|
|
17
|
+
Add
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<p class="data-panel-message" id="ac-panel-message" hidden></p>
|
|
22
|
+
|
|
23
|
+
<label class="data-panel-toggle">
|
|
24
|
+
<input type="checkbox" id="ac-template-toggle" <%= "checked" if @page.template_enabled? %>>
|
|
25
|
+
<span>Render dynamic content (Liquid)</span>
|
|
26
|
+
</label>
|
|
27
|
+
<p class="data-panel-notice" id="ac-template-notice" <%= "hidden" if @page.template_enabled? %>>
|
|
28
|
+
Dynamic rendering is off. Bindings and <code>{{ tags }}</code> are saved but visitors see them as plain text until you turn it on.
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
<ul id="ac-bindings-list" class="data-panel-list" data-empty="true"></ul>
|
|
32
|
+
|
|
33
|
+
<div class="data-panel-empty" id="ac-bindings-empty">
|
|
34
|
+
<div class="data-panel-empty-icon">
|
|
35
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
36
|
+
<ellipse cx="12" cy="5" rx="9" ry="3"/>
|
|
37
|
+
<path d="M3 5v14a9 3 0 0 0 18 0V5"/>
|
|
38
|
+
<path d="M3 12a9 3 0 0 0 18 0"/>
|
|
39
|
+
</svg>
|
|
40
|
+
</div>
|
|
41
|
+
<h3>No bindings yet</h3>
|
|
42
|
+
<p>Connect a data source to use <code>{{ variables }}</code> in your page.</p>
|
|
43
|
+
<button type="button" id="ac-empty-add-btn" class="data-panel-empty-btn">
|
|
44
|
+
<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">
|
|
45
|
+
<line x1="12" y1="5" x2="12" y2="19"/>
|
|
46
|
+
<line x1="5" y1="12" x2="19" y2="12"/>
|
|
47
|
+
</svg>
|
|
48
|
+
Add binding
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<p class="data-panel-help">Tip: click a chip to copy the snippet to your clipboard.</p>
|
|
53
|
+
|
|
54
|
+
<template id="ac-binding-form-template">
|
|
55
|
+
<form id="ac-binding-form" class="data-panel-add-card">
|
|
56
|
+
<div class="data-panel-add-card-header">
|
|
57
|
+
<span class="data-panel-add-card-title">New binding</span>
|
|
58
|
+
<button type="button" id="ac-binding-cancel" class="data-panel-add-card-close" title="Cancel">
|
|
59
|
+
<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">
|
|
60
|
+
<line x1="18" y1="6" x2="6" y2="18"/>
|
|
61
|
+
<line x1="6" y1="6" x2="18" y2="18"/>
|
|
62
|
+
</svg>
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
<label class="data-panel-field">
|
|
66
|
+
<span class="data-panel-field-label">Name</span>
|
|
67
|
+
<input type="text" name="name" required autocomplete="off" placeholder="articles">
|
|
68
|
+
<span class="data-panel-field-error" id="ac-binding-name-error" hidden></span>
|
|
69
|
+
</label>
|
|
70
|
+
<label class="data-panel-field">
|
|
71
|
+
<span class="data-panel-field-label">Source</span>
|
|
72
|
+
<select name="source"></select>
|
|
73
|
+
</label>
|
|
74
|
+
<div id="ac-binding-params" class="data-panel-add-card-params"></div>
|
|
75
|
+
<div class="data-panel-add-card-actions">
|
|
76
|
+
<button type="button" class="data-panel-btn data-panel-btn-ghost" data-cancel>Cancel</button>
|
|
77
|
+
<button type="submit" class="data-panel-btn data-panel-btn-primary">Save binding</button>
|
|
78
|
+
</div>
|
|
79
|
+
</form>
|
|
80
|
+
</template>
|
|
81
|
+
</div>
|
|
@@ -56,6 +56,16 @@
|
|
|
56
56
|
</div>
|
|
57
57
|
</div>
|
|
58
58
|
|
|
59
|
+
<div class="form-group">
|
|
60
|
+
<div class="form-check" style="background: var(--bg-main, #f8fafc); padding: 1rem; border-radius: 0.5rem; border: 1px solid var(--border, #e2e8f0);">
|
|
61
|
+
<%= form.check_box :template_enabled, id: "page_template_enabled" %>
|
|
62
|
+
<label for="page_template_enabled" style="font-weight: 500;">
|
|
63
|
+
Render dynamic content (Liquid)
|
|
64
|
+
</label>
|
|
65
|
+
<p class="help-text" style="margin-left: 1.5rem; margin-top: 0.25rem;">Enable Liquid template rendering for dynamic content and data bindings.</p>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
59
69
|
<div class="form-actions">
|
|
60
70
|
<%= form.submit class: "btn btn-primary" %>
|
|
61
71
|
<%= link_to "Cancel", admin_pages_path, class: "btn btn-secondary" %>
|
|
@@ -149,6 +149,21 @@
|
|
|
149
149
|
</div>
|
|
150
150
|
</div>
|
|
151
151
|
<div class="header-separator"></div>
|
|
152
|
+
<button class="header-btn header-btn-secondary" id="btn-live-data" title="Show the real values inside the canvas" aria-pressed="false">
|
|
153
|
+
<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">
|
|
154
|
+
<ellipse cx="12" cy="5" rx="9" ry="3"/>
|
|
155
|
+
<path d="M3 5v14a9 3 0 0 0 18 0V5"/>
|
|
156
|
+
<path d="M3 12a9 3 0 0 0 18 0"/>
|
|
157
|
+
</svg>
|
|
158
|
+
Live data
|
|
159
|
+
</button>
|
|
160
|
+
<button class="header-btn header-btn-secondary" id="btn-preview" title="Preview (Ctrl+Shift+P)" data-preview-url="<%= preview_iframe_admin_page_path(@page) %>">
|
|
161
|
+
<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">
|
|
162
|
+
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/>
|
|
163
|
+
<circle cx="12" cy="12" r="3"/>
|
|
164
|
+
</svg>
|
|
165
|
+
Preview
|
|
166
|
+
</button>
|
|
152
167
|
<button class="header-btn header-btn-primary" id="btn-save">
|
|
153
168
|
<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">
|
|
154
169
|
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
|
|
@@ -170,6 +185,7 @@
|
|
|
170
185
|
<button class="panel-tab active" data-panel="blocks">Blocks</button>
|
|
171
186
|
<button class="panel-tab" data-panel="assets">Assets</button>
|
|
172
187
|
<button class="panel-tab" data-panel="layers">Layers</button>
|
|
188
|
+
<button class="panel-tab" data-panel="data">Data</button>
|
|
173
189
|
</div>
|
|
174
190
|
<div class="panel-content">
|
|
175
191
|
<div id="blocks-container"></div>
|
|
@@ -199,6 +215,7 @@
|
|
|
199
215
|
</div>
|
|
200
216
|
</div>
|
|
201
217
|
<div id="layers-container" style="display: none;"></div>
|
|
218
|
+
<%= render "data_panel" %>
|
|
202
219
|
</div>
|
|
203
220
|
</div>
|
|
204
221
|
|
|
@@ -576,6 +593,41 @@
|
|
|
576
593
|
</div>
|
|
577
594
|
</div>
|
|
578
595
|
|
|
596
|
+
<!-- Preview Modal (full-screen iframe preview) -->
|
|
597
|
+
<div class="preview-modal" id="preview-modal" hidden>
|
|
598
|
+
<div class="preview-modal-toolbar">
|
|
599
|
+
<div class="preview-modal-title">
|
|
600
|
+
<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">
|
|
601
|
+
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/>
|
|
602
|
+
<circle cx="12" cy="12" r="3"/>
|
|
603
|
+
</svg>
|
|
604
|
+
<span>Preview · <strong><%= @page.title %></strong></span>
|
|
605
|
+
</div>
|
|
606
|
+
<button class="preview-modal-close" id="btn-close-preview" title="Close (Esc)">
|
|
607
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
608
|
+
<line x1="18" y1="6" x2="6" y2="18"/>
|
|
609
|
+
<line x1="6" y1="6" x2="18" y2="18"/>
|
|
610
|
+
</svg>
|
|
611
|
+
</button>
|
|
612
|
+
</div>
|
|
613
|
+
<div class="preview-modal-body">
|
|
614
|
+
<iframe class="preview-modal-iframe" id="preview-modal-iframe" title="Page preview" sandbox="allow-scripts allow-forms"></iframe>
|
|
615
|
+
<div class="preview-modal-loading" id="preview-modal-loading">
|
|
616
|
+
<div class="preview-modal-spinner"></div>
|
|
617
|
+
<span>Rendering preview…</span>
|
|
618
|
+
</div>
|
|
619
|
+
<div class="preview-modal-error" id="preview-modal-error" hidden>
|
|
620
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
621
|
+
<circle cx="12" cy="12" r="10"/>
|
|
622
|
+
<line x1="12" y1="8" x2="12" y2="12"/>
|
|
623
|
+
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
|
624
|
+
</svg>
|
|
625
|
+
<h3>Preview failed</h3>
|
|
626
|
+
<p id="preview-modal-error-message"></p>
|
|
627
|
+
</div>
|
|
628
|
+
</div>
|
|
629
|
+
</div>
|
|
630
|
+
|
|
579
631
|
<!-- Toast Notification -->
|
|
580
632
|
<div id="editor-toast" class="editor-toast"></div>
|
|
581
633
|
|
|
@@ -599,13 +651,14 @@
|
|
|
599
651
|
window.ActiveCanvasEditor = {
|
|
600
652
|
config: {
|
|
601
653
|
pageId: <%= @page.id %>,
|
|
654
|
+
templateEnabled: <%= @page.template_enabled? %>,
|
|
602
655
|
saveUrl: '<%= save_editor_admin_page_path(@page) %>',
|
|
603
656
|
mediaUrl: '<%= admin_media_path(format: :json) %>',
|
|
604
657
|
uploadUrl: '<%= admin_media_path %>',
|
|
605
658
|
content: <%= raw (@page.content || '').to_json %>,
|
|
606
659
|
contentCss: <%= raw (@page.content_css || '').to_json %>,
|
|
607
660
|
contentJs: <%= raw (@page.content_js || '').to_json %>,
|
|
608
|
-
contentComponents: <%= raw (@page.content_components || '').to_json %>,
|
|
661
|
+
contentComponents: <%= raw (@page.template_enabled? ? '' : (@page.content_components || '')).to_json %>,
|
|
609
662
|
cssFramework: '<%= ActiveCanvas::Setting.css_framework %>',
|
|
610
663
|
cssFrameworkUrl: <%= raw (ActiveCanvas::Setting.css_framework_url || '').to_json %>,
|
|
611
664
|
cssFrameworkType: '<%= ActiveCanvas::Setting.css_framework_type %>',
|
|
@@ -640,7 +693,15 @@
|
|
|
640
693
|
<%= javascript_include_tag "active_canvas/editor/component_toolbar" %>
|
|
641
694
|
<%= javascript_include_tag "active_canvas/editor/panels" %>
|
|
642
695
|
<%= javascript_include_tag "active_canvas/editor/ai_panel" %>
|
|
696
|
+
<%= javascript_include_tag "active_canvas/editor/preview_modal" %>
|
|
697
|
+
<script id="ac-server-bindings" type="application/json"><%= json_escape(@page.bindings.to_json).html_safe %></script>
|
|
698
|
+
<%= javascript_include_tag "active_canvas/editor/ac_bindings" %>
|
|
699
|
+
<%= javascript_include_tag "active_canvas/editor/ac_chips" %>
|
|
700
|
+
<%= javascript_include_tag "active_canvas/editor/directive_traits" %>
|
|
701
|
+
<%= javascript_include_tag "active_canvas/editor/template_validator" %>
|
|
702
|
+
<%= javascript_include_tag "active_canvas/editor/live_data" %>
|
|
643
703
|
<%= javascript_include_tag "active_canvas/editor" %>
|
|
704
|
+
<%= javascript_include_tag "active_canvas/admin/data_panel" %>
|
|
644
705
|
|
|
645
706
|
<!-- Theme Switcher JavaScript -->
|
|
646
707
|
<script>
|
|
@@ -93,7 +93,10 @@
|
|
|
93
93
|
<% end %>
|
|
94
94
|
|
|
95
95
|
<%# Main Page Content %>
|
|
96
|
-
<%= @page.rendered_content
|
|
96
|
+
<%= @page.rendered_content(
|
|
97
|
+
form_feedback: { submitted: params[:ac_submitted], error: params[:ac_form_error], form_key: params[:ac_form] },
|
|
98
|
+
form_action: public_form_submissions_path
|
|
99
|
+
) %>
|
|
97
100
|
|
|
98
101
|
<%# Footer %>
|
|
99
102
|
<% if footer_partial&.content.present? %>
|
|
@@ -877,6 +877,21 @@
|
|
|
877
877
|
</svg>
|
|
878
878
|
<span>Header & Footer</span>
|
|
879
879
|
<% end %>
|
|
880
|
+
<%= link_to admin_form_submissions_path, class: "nav-link #{controller_name == 'form_submissions' ? 'active' : ''}" do %>
|
|
881
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
882
|
+
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
|
|
883
|
+
<polyline points="22,6 12,13 2,6"/>
|
|
884
|
+
</svg>
|
|
885
|
+
<span>Form submissions</span>
|
|
886
|
+
<% end %>
|
|
887
|
+
<%= link_to admin_collections_path, class: "nav-link #{controller_name.in?(%w[collections collection_items]) ? 'active' : ''}" do %>
|
|
888
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
889
|
+
<path d="M12 2 2 7l10 5 10-5-10-5Z"/>
|
|
890
|
+
<path d="m2 17 10 5 10-5"/>
|
|
891
|
+
<path d="m2 12 10 5 10-5"/>
|
|
892
|
+
</svg>
|
|
893
|
+
<span>Collections</span>
|
|
894
|
+
<% end %>
|
|
880
895
|
</div>
|
|
881
896
|
|
|
882
897
|
<div class="nav-section">
|
data/config/routes.rb
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
ActiveCanvas::Engine.routes.draw do
|
|
2
2
|
namespace :admin do
|
|
3
3
|
resources :pages do
|
|
4
|
+
collection do
|
|
5
|
+
get :data_sources
|
|
6
|
+
end
|
|
4
7
|
member do
|
|
5
8
|
get :content
|
|
6
9
|
patch :update_content
|
|
7
10
|
get :editor
|
|
8
11
|
patch :save_editor
|
|
9
12
|
get :versions
|
|
13
|
+
post :validate_template
|
|
14
|
+
post :sample_data
|
|
15
|
+
post :chip_values
|
|
16
|
+
post :preview_iframe
|
|
10
17
|
end
|
|
11
18
|
resources :versions, only: [:show], controller: "page_versions"
|
|
12
19
|
end
|
|
@@ -18,6 +25,16 @@ ActiveCanvas::Engine.routes.draw do
|
|
|
18
25
|
end
|
|
19
26
|
end
|
|
20
27
|
resources :media, only: [:index, :show, :create, :destroy]
|
|
28
|
+
resources :form_submissions, only: [ :index, :show, :destroy ]
|
|
29
|
+
resources :collections do
|
|
30
|
+
resources :items, controller: "collection_items", only: %i[index new create edit update destroy] do
|
|
31
|
+
member do
|
|
32
|
+
patch :publish
|
|
33
|
+
patch :unpublish
|
|
34
|
+
get :history
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
21
38
|
resource :settings, only: [:show, :update] do
|
|
22
39
|
patch :update_global_css
|
|
23
40
|
patch :update_global_js
|
|
@@ -44,6 +61,9 @@ ActiveCanvas::Engine.routes.draw do
|
|
|
44
61
|
end
|
|
45
62
|
|
|
46
63
|
root to: "pages#home"
|
|
64
|
+
post "forms", to: "form_submissions#create", as: :public_form_submissions
|
|
47
65
|
get ":slug", to: "pages#show", as: :public_page,
|
|
48
66
|
constraints: ->(req) { ActiveCanvas::Page.published.exists?(slug: req.params[:slug]) }
|
|
67
|
+
get ":slug", to: "redirects#show", as: :page_redirect,
|
|
68
|
+
constraints: ->(req) { ActiveCanvas::PageRedirect.exists?(from_slug: req.params[:slug]) }
|
|
49
69
|
end
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
class AddTemplateEnabledAndBindingsToActiveCanvasPages < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
add_column :active_canvas_pages, :template_enabled, :boolean, default: false, null: false
|
|
4
|
+
add_column :active_canvas_pages, :bindings, :json, default: {}, null: false
|
|
5
|
+
end
|
|
6
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class CreateActiveCanvasPageRedirects < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :active_canvas_page_redirects do |t|
|
|
4
|
+
t.string :from_slug, null: false
|
|
5
|
+
t.references :page, null: false, foreign_key: { to_table: :active_canvas_pages }
|
|
6
|
+
t.timestamps
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
add_index :active_canvas_page_redirects, :from_slug, unique: true
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class CreateActiveCanvasFormSubmissions < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :active_canvas_form_submissions do |t|
|
|
4
|
+
t.references :page, null: false, index: false, foreign_key: { to_table: :active_canvas_pages }
|
|
5
|
+
t.string :form_key, null: false
|
|
6
|
+
t.json :data, null: false, default: {}
|
|
7
|
+
t.string :ip
|
|
8
|
+
t.string :user_agent
|
|
9
|
+
t.timestamps
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
add_index :active_canvas_form_submissions, [ :page_id, :form_key ]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class CreateActiveCanvasCollections < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :active_canvas_collections do |t|
|
|
4
|
+
t.string :name, null: false
|
|
5
|
+
t.string :slug, null: false
|
|
6
|
+
t.json :fields, null: false, default: []
|
|
7
|
+
t.timestamps
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
add_index :active_canvas_collections, :slug, unique: true
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class CreateActiveCanvasCollectionItems < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :active_canvas_collection_items do |t|
|
|
4
|
+
t.references :collection, null: false, index: false,
|
|
5
|
+
foreign_key: { to_table: :active_canvas_collections }
|
|
6
|
+
t.json :data, null: false, default: {}
|
|
7
|
+
t.json :draft_data, null: false, default: {}
|
|
8
|
+
t.string :status, null: false, default: "draft"
|
|
9
|
+
t.string :slug
|
|
10
|
+
t.datetime :published_at
|
|
11
|
+
t.timestamps
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
add_index :active_canvas_collection_items, [ :collection_id, :status ]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class CreateActiveCanvasCollectionItemVersions < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :active_canvas_collection_item_versions do |t|
|
|
4
|
+
t.references :collection_item, null: false, index: false,
|
|
5
|
+
foreign_key: { to_table: :active_canvas_collection_items }
|
|
6
|
+
t.integer :version_number, null: false
|
|
7
|
+
t.json :data, null: false, default: {}
|
|
8
|
+
t.string :changed_by
|
|
9
|
+
t.string :change_summary
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
add_index :active_canvas_collection_item_versions,
|
|
14
|
+
[ :collection_item_id, :version_number ], unique: true,
|
|
15
|
+
name: "idx_ac_collection_item_versions_on_item_and_number"
|
|
16
|
+
end
|
|
17
|
+
end
|