iron-cms 0.5.2 → 0.7.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/app/assets/builds/iron.css +682 -388
- data/app/assets/tailwind/iron/application.css +1 -0
- data/app/assets/tailwind/iron/components/button.css +0 -7
- data/app/assets/tailwind/iron/components/checkbox.css +21 -0
- data/app/assets/tailwind/iron/components/form.css +1 -1
- data/app/assets/tailwind/iron/lexxy.css +165 -51
- data/app/controllers/iron/account/exports_controller.rb +26 -0
- data/app/controllers/iron/account/imports_controller.rb +27 -0
- data/app/helpers/iron/form_builder.rb +7 -0
- data/app/javascript/iron/controllers/local_preference_controller.js +62 -0
- data/app/jobs/iron/export_job.rb +9 -0
- data/app/jobs/iron/import_job.rb +9 -0
- data/app/models/concerns/iron/broadcastable.rb +9 -0
- data/app/models/concerns/iron/processable.rb +34 -0
- data/app/models/iron/account/export.rb +86 -0
- data/app/models/iron/account/import.rb +208 -0
- data/app/models/iron/block_definition/exportable.rb +14 -0
- data/app/models/iron/block_definition/importable.rb +27 -0
- data/app/models/iron/block_definition.rb +1 -1
- data/app/models/iron/content_type/exportable.rb +20 -0
- data/app/models/iron/content_type/importable.rb +32 -0
- data/app/models/iron/content_type.rb +1 -1
- data/app/models/iron/current.rb +6 -3
- data/app/models/iron/entry/exportable.rb +49 -0
- data/app/models/iron/entry/importable.rb +181 -0
- data/app/models/iron/entry.rb +1 -1
- data/app/models/iron/field.rb +9 -1
- data/app/models/iron/field_definition/exportable.rb +23 -0
- data/app/models/iron/field_definition/importable.rb +39 -0
- data/app/models/iron/field_definition.rb +1 -1
- data/app/models/iron/fields/block.rb +34 -0
- data/app/models/iron/fields/block_list.rb +8 -0
- data/app/models/iron/fields/boolean.rb +4 -0
- data/app/models/iron/fields/date.rb +4 -0
- data/app/models/iron/fields/file.rb +16 -0
- data/app/models/iron/fields/number.rb +4 -0
- data/app/models/iron/fields/reference.rb +4 -0
- data/app/models/iron/fields/reference_list.rb +4 -0
- data/app/models/iron/fields/rich_text_area.rb +32 -0
- data/app/models/iron/fields/text_area.rb +4 -0
- data/app/models/iron/fields/text_field.rb +4 -0
- data/app/models/iron/user.rb +2 -0
- data/app/views/iron/account/exports/index.html.erb +43 -0
- data/app/views/iron/account/exports/new.html.erb +39 -0
- data/app/views/iron/account/exports/show.html.erb +40 -0
- data/app/views/iron/account/imports/index.html.erb +43 -0
- data/app/views/iron/account/imports/new.html.erb +52 -0
- data/app/views/iron/account/imports/show.html.erb +37 -0
- data/app/views/iron/content_types/index.html.erb +1 -8
- data/app/views/iron/entries/fields/_block.html.erb +23 -10
- data/app/views/iron/entries/fields/_file.html.erb +3 -3
- data/app/views/iron/settings/show.html.erb +4 -11
- data/app/views/layouts/iron/application.html.erb +14 -0
- data/config/routes.rb +3 -9
- data/db/migrate/20251209103109_create_iron_account_exports.rb +13 -0
- data/db/migrate/20251209103110_create_iron_account_imports.rb +13 -0
- data/lib/iron/version.rb +1 -1
- data/lib/iron.rb +1 -1
- metadata +41 -28
- data/app/controllers/iron/contents_controller.rb +0 -33
- data/app/controllers/iron/schemas_controller.rb +0 -32
- data/app/models/concerns/iron/csv_serializable.rb +0 -28
- data/app/models/iron/archive.rb +0 -69
- data/app/models/iron/block_definition/portable.rb +0 -20
- data/app/models/iron/content_export.rb +0 -73
- data/app/models/iron/content_import/entry_builder.rb +0 -80
- data/app/models/iron/content_import/entry_snapshot.rb +0 -23
- data/app/models/iron/content_import/field_reconstructor.rb +0 -276
- data/app/models/iron/content_import/field_snapshot.rb +0 -33
- data/app/models/iron/content_import/registry.rb +0 -32
- data/app/models/iron/content_import/session.rb +0 -89
- data/app/models/iron/content_import.rb +0 -15
- data/app/models/iron/content_type/portable.rb +0 -30
- data/app/models/iron/entry/portable.rb +0 -35
- data/app/models/iron/field/portable.rb +0 -33
- data/app/models/iron/field_definition/portable.rb +0 -42
- data/app/models/iron/schema_archive.rb +0 -71
- data/app/models/iron/schema_exporter.rb +0 -15
- data/app/models/iron/schema_importer/import_strategy.rb +0 -59
- data/app/models/iron/schema_importer/merge_strategy.rb +0 -52
- data/app/models/iron/schema_importer/replace_strategy.rb +0 -51
- data/app/models/iron/schema_importer/safe_strategy.rb +0 -55
- data/app/models/iron/schema_importer.rb +0 -108
- data/app/views/iron/contents/new.html.erb +0 -34
- data/app/views/iron/schemas/new.html.erb +0 -57
- data/lib/iron/test_fixtures.rb +0 -50
|
@@ -45,6 +45,18 @@ module Iron
|
|
|
45
45
|
result
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
def export_value
|
|
49
|
+
{
|
|
50
|
+
type: "block",
|
|
51
|
+
block_handle: block_definition.handle,
|
|
52
|
+
fields: export_nested_fields
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def export_attachments
|
|
57
|
+
fields.flat_map(&:export_attachments)
|
|
58
|
+
end
|
|
59
|
+
|
|
48
60
|
def to_csv_rows
|
|
49
61
|
rows = []
|
|
50
62
|
|
|
@@ -56,5 +68,27 @@ module Iron
|
|
|
56
68
|
|
|
57
69
|
rows
|
|
58
70
|
end
|
|
71
|
+
|
|
72
|
+
def title
|
|
73
|
+
text_types = %w[Iron::Fields::TextField Iron::Fields::TextArea Iron::Fields::RichTextArea]
|
|
74
|
+
|
|
75
|
+
text_field = fields.find { |f| text_types.include?(f.type) }
|
|
76
|
+
return nil unless text_field
|
|
77
|
+
|
|
78
|
+
content = case text_field
|
|
79
|
+
when Fields::RichTextArea
|
|
80
|
+
text_field.rich_text&.to_plain_text
|
|
81
|
+
else
|
|
82
|
+
text_field.value
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
content&.truncate(300)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def export_nested_fields
|
|
91
|
+
fields.to_h { |f| [ f.definition.handle, f.export_value ] }
|
|
92
|
+
end
|
|
59
93
|
end
|
|
60
94
|
end
|
|
@@ -13,6 +13,14 @@ module Iron
|
|
|
13
13
|
blocks.map(&:value)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
def export_value
|
|
17
|
+
{ type: "block_list", value: blocks.map(&:export_value) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def export_attachments
|
|
21
|
+
blocks.flat_map(&:export_attachments)
|
|
22
|
+
end
|
|
23
|
+
|
|
16
24
|
def to_csv_rows
|
|
17
25
|
rows = []
|
|
18
26
|
|
|
@@ -8,12 +8,28 @@ module Iron
|
|
|
8
8
|
file.attached? ? file : nil
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
def export_value
|
|
12
|
+
return { type: "file", value: nil } unless file.attached?
|
|
13
|
+
|
|
14
|
+
{ type: "file", value: export_attachment_path }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def export_attachments
|
|
18
|
+
return [] unless file.attached?
|
|
19
|
+
|
|
20
|
+
[ file.blob ]
|
|
21
|
+
end
|
|
22
|
+
|
|
11
23
|
def image?
|
|
12
24
|
file.attached? && file.blob.content_type&.start_with?("image/")
|
|
13
25
|
end
|
|
14
26
|
|
|
15
27
|
private
|
|
16
28
|
|
|
29
|
+
def export_attachment_path
|
|
30
|
+
"#{entry.id}/#{file.blob.key}_#{file.blob.filename}"
|
|
31
|
+
end
|
|
32
|
+
|
|
17
33
|
def definition_restricts_file_type?
|
|
18
34
|
file.attached? &&
|
|
19
35
|
definition.file_scope == "specific" &&
|
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
module Iron
|
|
2
2
|
class Fields::RichTextArea < Field
|
|
3
|
+
include ActionView::Helpers::TagHelper
|
|
4
|
+
|
|
3
5
|
has_rich_text :rich_text
|
|
4
6
|
|
|
5
7
|
def value
|
|
6
8
|
rich_text&.to_s
|
|
7
9
|
end
|
|
8
10
|
|
|
11
|
+
def export_value
|
|
12
|
+
return { type: "rich_text_area", value: nil } unless rich_text&.body.present?
|
|
13
|
+
|
|
14
|
+
html = rich_text.body.render_attachments do |attachment|
|
|
15
|
+
export_attachment_tag(attachment)
|
|
16
|
+
end.to_html
|
|
17
|
+
|
|
18
|
+
{ type: "rich_text_area", value: html }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def export_attachments
|
|
22
|
+
return [] unless rich_text&.body.present?
|
|
23
|
+
|
|
24
|
+
rich_text.body.attachables.filter_map do |attachable|
|
|
25
|
+
attachable.blob if attachable.respond_to?(:blob)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
9
29
|
def to_csv_rows
|
|
10
30
|
rows = []
|
|
11
31
|
|
|
@@ -24,6 +44,18 @@ module Iron
|
|
|
24
44
|
|
|
25
45
|
private
|
|
26
46
|
|
|
47
|
+
def export_attachment_tag(attachment)
|
|
48
|
+
case attachable = attachment.attachable
|
|
49
|
+
when ActiveStorage::Blob
|
|
50
|
+
path = "#{entry.id}/#{attachable.key}_#{attachable.filename}"
|
|
51
|
+
attachable.image? ? tag.img(src: path) : tag.a(attachable.filename, href: path)
|
|
52
|
+
when ActionText::Attachables::RemoteImage
|
|
53
|
+
tag.img(src: attachable.url)
|
|
54
|
+
else
|
|
55
|
+
attachment.to_html
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
27
59
|
def csv_type
|
|
28
60
|
"rich_text"
|
|
29
61
|
end
|
data/app/models/iron/user.rb
CHANGED
|
@@ -6,6 +6,8 @@ module Iron
|
|
|
6
6
|
|
|
7
7
|
has_secure_password
|
|
8
8
|
has_many :sessions, dependent: :destroy
|
|
9
|
+
has_many :exports, class_name: "Iron::Account::Export", dependent: :destroy
|
|
10
|
+
has_many :imports, class_name: "Iron::Account::Import", dependent: :destroy
|
|
9
11
|
|
|
10
12
|
normalizes :email_address, with: ->(e) { e.strip.downcase }
|
|
11
13
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<% content_for :title, "Exports" %>
|
|
2
|
+
|
|
3
|
+
<div class="">
|
|
4
|
+
<%= back_button_to "Settings", settings_path %>
|
|
5
|
+
<div class="flex justify-between items-center">
|
|
6
|
+
<h1 class="page-title">Exports</h1>
|
|
7
|
+
<%= link_to "New Export", new_account_export_path, class: "button-primary" %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="mt-8">
|
|
11
|
+
<% if @exports.any? %>
|
|
12
|
+
<div class="bg-white dark:bg-stone-800/50 rounded-lg shadow-sm divide-y divide-stone-200 dark:divide-stone-700/20 overflow-hidden">
|
|
13
|
+
<% @exports.each do |export| %>
|
|
14
|
+
<div class="p-4 flex items-center justify-between">
|
|
15
|
+
<div>
|
|
16
|
+
<p class="text-sm font-medium text-stone-900 dark:text-white">
|
|
17
|
+
<%= export.title %>
|
|
18
|
+
</p>
|
|
19
|
+
<p class="text-sm text-stone-500 dark:text-stone-400">
|
|
20
|
+
<%= export.include_schema? ? "Schema" : "" %><%= export.include_schema? && export.include_content? ? " + " : "" %><%= export.include_content? ? "Content" : "" %>
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="flex items-center gap-3">
|
|
24
|
+
<% if export.pending? || export.processing? %>
|
|
25
|
+
<span class="inline-flex items-center gap-1.5 text-sm text-stone-500">
|
|
26
|
+
<div class="animate-spin h-4 w-4 border-2 border-stone-300 border-t-stone-600 rounded-full"></div>
|
|
27
|
+
Processing
|
|
28
|
+
</span>
|
|
29
|
+
<% elsif export.completed? %>
|
|
30
|
+
<%= link_to "Download", main_app.rails_blob_path(export.file, disposition: "attachment"), class: "button-secondary text-sm" %>
|
|
31
|
+
<% elsif export.failed? %>
|
|
32
|
+
<span class="text-sm text-red-600">Failed</span>
|
|
33
|
+
<% end %>
|
|
34
|
+
<%= link_to "View", account_export_path(export), class: "text-sm text-stone-500 hover:text-stone-700" %>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<% end %>
|
|
38
|
+
</div>
|
|
39
|
+
<% else %>
|
|
40
|
+
<p class="text-sm text-stone-500">No exports yet.</p>
|
|
41
|
+
<% end %>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<% content_for :title, "New Export" %>
|
|
2
|
+
|
|
3
|
+
<div class="">
|
|
4
|
+
<%= back_button_to "Exports", account_exports_path %>
|
|
5
|
+
<h1 class="page-title">New Export</h1>
|
|
6
|
+
<p class="mt-2 text-sm text-stone-500">Select what you want to export from your CMS.</p>
|
|
7
|
+
|
|
8
|
+
<%= form_with model: @export, class: "mt-8 max-w-2xl" do |form| %>
|
|
9
|
+
<fieldset class="mb-6">
|
|
10
|
+
<legend class="text-sm font-medium mb-3">Select what to export</legend>
|
|
11
|
+
<div class="space-y-5">
|
|
12
|
+
<div class="flex gap-3">
|
|
13
|
+
<div class="flex h-6 shrink-0 items-center">
|
|
14
|
+
<%= form.check_box :include_schema, aria: { describedby: "include_schema_description" } %>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="text-sm/6">
|
|
17
|
+
<%= form.label :include_schema, "Schema", class: "font-medium text-stone-900 dark:text-white" %>
|
|
18
|
+
<p id="include_schema_description" class="text-stone-500 dark:text-stone-400">Content types, fields, and block definitions</p>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="flex gap-3">
|
|
23
|
+
<div class="flex h-6 shrink-0 items-center">
|
|
24
|
+
<%= form.check_box :include_content, aria: { describedby: "include_content_description" } %>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="text-sm/6">
|
|
27
|
+
<%= form.label :include_content, "Content", class: "font-medium text-stone-900 dark:text-white" %>
|
|
28
|
+
<p id="include_content_description" class="text-stone-500 dark:text-stone-400">Entries and uploaded files</p>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</fieldset>
|
|
33
|
+
|
|
34
|
+
<div class="flex items-center gap-x-3">
|
|
35
|
+
<%= form.submit "Export", class: "button-primary" %>
|
|
36
|
+
<%= link_to "Cancel", account_exports_path, class: "button-secondary" %>
|
|
37
|
+
</div>
|
|
38
|
+
<% end %>
|
|
39
|
+
</div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<% content_for :title, @export.title %>
|
|
2
|
+
<%= turbo_stream_from @export %>
|
|
3
|
+
|
|
4
|
+
<div class="">
|
|
5
|
+
<%= back_button_to "Exports", account_exports_path %>
|
|
6
|
+
<h1 class="page-title"><%= @export.title %></h1>
|
|
7
|
+
|
|
8
|
+
<div class="mt-8 max-w-2xl">
|
|
9
|
+
<div class="bg-white shadow-sm sm:rounded-lg dark:bg-stone-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10">
|
|
10
|
+
<div class="px-4 py-5 sm:p-6">
|
|
11
|
+
<% if @export.pending? || @export.processing? %>
|
|
12
|
+
<div class="flex items-center gap-3">
|
|
13
|
+
<div class="animate-spin h-5 w-5 border-2 border-stone-300 border-t-stone-600 rounded-full dark:border-stone-600 dark:border-t-stone-300"></div>
|
|
14
|
+
<h3 class="text-base font-semibold text-stone-900 dark:text-white">Generating export...</h3>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<% elsif @export.completed? %>
|
|
18
|
+
<h3 class="text-base font-semibold text-stone-900 dark:text-white">Export complete</h3>
|
|
19
|
+
<div class="mt-2 text-sm text-stone-500 dark:text-stone-400">
|
|
20
|
+
<p>
|
|
21
|
+
Exported:
|
|
22
|
+
<%= @export.include_schema? ? "Schema" : "" %><%= @export.include_schema? && @export.include_content? ? " and " : "" %><%= @export.include_content? ? "Content" : "" %>
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="mt-4">
|
|
26
|
+
<%= link_to "Download", main_app.rails_blob_path(@export.file, disposition: "attachment"), class: "button-primary" %>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<% elsif @export.failed? %>
|
|
30
|
+
<h3 class="text-base font-semibold text-stone-900 dark:text-white">Export failed</h3>
|
|
31
|
+
<% if @export.error_message.present? %>
|
|
32
|
+
<div class="mt-2 text-sm text-stone-500 dark:text-stone-400">
|
|
33
|
+
<p><%= @export.error_message %></p>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
36
|
+
<% end %>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<% content_for :title, "Imports" %>
|
|
2
|
+
|
|
3
|
+
<div class="">
|
|
4
|
+
<%= back_button_to "Settings", settings_path %>
|
|
5
|
+
<div class="flex justify-between items-center">
|
|
6
|
+
<h1 class="page-title">Imports</h1>
|
|
7
|
+
<%= link_to "New Import", new_account_import_path, class: "button-primary" %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="mt-8">
|
|
11
|
+
<% if @imports.any? %>
|
|
12
|
+
<div class="bg-white dark:bg-stone-800/50 rounded-lg shadow-sm divide-y divide-stone-200 dark:divide-stone-700/20 overflow-hidden">
|
|
13
|
+
<% @imports.each do |import| %>
|
|
14
|
+
<div class="p-4 flex items-center justify-between">
|
|
15
|
+
<div>
|
|
16
|
+
<p class="text-sm font-medium text-stone-900 dark:text-white">
|
|
17
|
+
<%= import.title %>
|
|
18
|
+
</p>
|
|
19
|
+
<p class="text-sm text-stone-500 dark:text-stone-400">
|
|
20
|
+
<%= import.include_schema? ? "Schema" : "" %><%= import.include_schema? && import.include_content? ? " + " : "" %><%= import.include_content? ? "Content" : "" %>
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="flex items-center gap-3">
|
|
24
|
+
<% if import.pending? || import.processing? %>
|
|
25
|
+
<span class="inline-flex items-center gap-1.5 text-sm text-stone-500">
|
|
26
|
+
<div class="animate-spin h-4 w-4 border-2 border-stone-300 border-t-stone-600 rounded-full"></div>
|
|
27
|
+
Processing
|
|
28
|
+
</span>
|
|
29
|
+
<% elsif import.completed? %>
|
|
30
|
+
<span class="text-sm text-green-600">Completed</span>
|
|
31
|
+
<% elsif import.failed? %>
|
|
32
|
+
<span class="text-sm text-red-600">Failed</span>
|
|
33
|
+
<% end %>
|
|
34
|
+
<%= link_to "View", account_import_path(import), class: "text-sm text-stone-500 hover:text-stone-700" %>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<% end %>
|
|
38
|
+
</div>
|
|
39
|
+
<% else %>
|
|
40
|
+
<p class="text-sm text-stone-500">No imports yet.</p>
|
|
41
|
+
<% end %>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<% content_for :title, "Import Data" %>
|
|
2
|
+
|
|
3
|
+
<div class="">
|
|
4
|
+
<%= back_button_to "Imports", account_imports_path %>
|
|
5
|
+
<h1 class="page-title">Import Data</h1>
|
|
6
|
+
<p class="mt-2 text-sm text-stone-500">Upload a file to import your CMS data.</p>
|
|
7
|
+
|
|
8
|
+
<%= form_with model: @import, multipart: true, class: "mt-8 max-w-2xl" do |form| %>
|
|
9
|
+
<fieldset class="mb-6">
|
|
10
|
+
<legend class="text-sm font-medium mb-3">Select what to import</legend>
|
|
11
|
+
<div class="space-y-5">
|
|
12
|
+
<div class="flex gap-3">
|
|
13
|
+
<div class="flex h-6 shrink-0 items-center">
|
|
14
|
+
<%= form.check_box :include_schema, aria: { describedby: "include_schema_description" } %>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="text-sm/6">
|
|
17
|
+
<%= form.label :include_schema, "Schema", class: "font-medium text-stone-900 dark:text-white" %>
|
|
18
|
+
<p id="include_schema_description" class="text-stone-500 dark:text-stone-400">Content types, fields, and block definitions</p>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="flex gap-3">
|
|
23
|
+
<div class="flex h-6 shrink-0 items-center">
|
|
24
|
+
<%= form.check_box :include_content, aria: { describedby: "include_content_description" } %>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="text-sm/6">
|
|
27
|
+
<%= form.label :include_content, "Content", class: "font-medium text-stone-900 dark:text-white" %>
|
|
28
|
+
<p id="include_content_description" class="text-stone-500 dark:text-stone-400">Entries and uploaded files</p>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</fieldset>
|
|
33
|
+
|
|
34
|
+
<div class="field-group">
|
|
35
|
+
<div class="field">
|
|
36
|
+
<%= form.label :file, "File" %>
|
|
37
|
+
<div>
|
|
38
|
+
<%= form.file_field :file,
|
|
39
|
+
accept: ".zip",
|
|
40
|
+
required: true,
|
|
41
|
+
class: "input" %>
|
|
42
|
+
<p class="mt-2 text-sm text-stone-500">Select a file exported from Iron CMS</p>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="mt-6 flex items-center gap-x-3">
|
|
48
|
+
<%= form.submit "Import", class: "button-primary" %>
|
|
49
|
+
<%= link_to "Cancel", account_imports_path, class: "button-secondary" %>
|
|
50
|
+
</div>
|
|
51
|
+
<% end %>
|
|
52
|
+
</div>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<% content_for :title, @import.title %>
|
|
2
|
+
<%= turbo_stream_from @import %>
|
|
3
|
+
|
|
4
|
+
<div class="">
|
|
5
|
+
<%= back_button_to "Imports", account_imports_path %>
|
|
6
|
+
<h1 class="page-title"><%= @import.title %></h1>
|
|
7
|
+
|
|
8
|
+
<div class="mt-8 max-w-2xl">
|
|
9
|
+
<div class="bg-white shadow-sm sm:rounded-lg dark:bg-stone-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10">
|
|
10
|
+
<div class="px-4 py-5 sm:p-6">
|
|
11
|
+
<% if @import.pending? || @import.processing? %>
|
|
12
|
+
<div class="flex items-center gap-3">
|
|
13
|
+
<div class="animate-spin h-5 w-5 border-2 border-stone-300 border-t-stone-600 rounded-full dark:border-stone-600 dark:border-t-stone-300"></div>
|
|
14
|
+
<h3 class="text-base font-semibold text-stone-900 dark:text-white">Importing...</h3>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<% elsif @import.completed? %>
|
|
18
|
+
<h3 class="text-base font-semibold text-stone-900 dark:text-white">Import complete</h3>
|
|
19
|
+
<div class="mt-2 text-sm text-stone-500 dark:text-stone-400">
|
|
20
|
+
<p>
|
|
21
|
+
Imported:
|
|
22
|
+
<%= @import.include_schema? ? "Schema" : "" %><%= @import.include_schema? && @import.include_content? ? " and " : "" %><%= @import.include_content? ? "Content" : "" %>
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<% elsif @import.failed? %>
|
|
27
|
+
<h3 class="text-base font-semibold text-stone-900 dark:text-white">Import failed</h3>
|
|
28
|
+
<% if @import.error_message.present? %>
|
|
29
|
+
<div class="mt-2 text-sm text-stone-500 dark:text-stone-400">
|
|
30
|
+
<p><%= @import.error_message %></p>
|
|
31
|
+
</div>
|
|
32
|
+
<% end %>
|
|
33
|
+
<% end %>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
@@ -3,14 +3,7 @@
|
|
|
3
3
|
<div class="space-y-8">
|
|
4
4
|
<div class="flex justify-between items-center">
|
|
5
5
|
<h1 class="page-title">Content Types</h1>
|
|
6
|
-
|
|
7
|
-
<%= link_to "Export",
|
|
8
|
-
export_schema_path,
|
|
9
|
-
class: "button-secondary",
|
|
10
|
-
data: { turbo: false } %>
|
|
11
|
-
<%= link_to "Import", schema_path, class: "button-secondary" %>
|
|
12
|
-
<%= link_to "New Content Type", new_content_type_path, class: "button-primary" %>
|
|
13
|
-
</div>
|
|
6
|
+
<%= link_to "New Content Type", new_content_type_path, class: "button-primary" %>
|
|
14
7
|
</div>
|
|
15
8
|
|
|
16
9
|
<div
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<% block = field %>
|
|
4
4
|
|
|
5
5
|
<% if block.block_list.present? %>
|
|
6
|
+
<% disclosure_id = dom_id(block, :disclosure) %>
|
|
6
7
|
|
|
7
8
|
<div
|
|
8
9
|
class="has-[>_.destroy:checked]:hidden flex items-start space-x-1"
|
|
@@ -11,22 +12,31 @@
|
|
|
11
12
|
<%= builder.check_box :_destroy, class: "destroy hidden", data: { destroy: "" } %>
|
|
12
13
|
<button
|
|
13
14
|
type="button"
|
|
14
|
-
class="handle button-ghost button-sm"
|
|
15
|
+
class="handle button-ghost button-sm py-3.5"
|
|
15
16
|
data-sortable-list-target="handle"
|
|
16
17
|
>
|
|
17
18
|
<%= icon "grip-vertical" %>
|
|
18
19
|
</button>
|
|
19
|
-
<details
|
|
20
|
+
<details
|
|
21
|
+
id="<%= disclosure_id %>"
|
|
22
|
+
class="group grow border border-stone-800 rounded-lg"
|
|
23
|
+
data-controller="local-preference"
|
|
24
|
+
<%= "open" if block.new_record? %>
|
|
25
|
+
>
|
|
20
26
|
<summary
|
|
21
|
-
class="
|
|
22
|
-
relative flex items-center justify-between px-4 py-3 cursor-pointer
|
|
23
|
-
transition-all hover:bg-stone-800/50 rounded-t-lg select-none
|
|
24
|
-
"
|
|
27
|
+
class="relative flex items-center justify-between gap-2 px-4 py-3 cursor-pointer transition-all hover:bg-stone-800/50 rounded-t-lg select-none"
|
|
25
28
|
>
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
<div class="flex flex-col gap-0.5 w-full min-w-0">
|
|
30
|
+
<span class="font-medium text-sm min-w-0">
|
|
31
|
+
<%= block.definition.name %>
|
|
32
|
+
</span>
|
|
33
|
+
<% if block.title.present? %>
|
|
34
|
+
<span class="text-stone-400 text-sm line-clamp-1 group-open:hidden">
|
|
35
|
+
<%= block.title %>
|
|
36
|
+
</span>
|
|
37
|
+
<% end %>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="flex items-center gap-2 shrink-0">
|
|
30
40
|
<%= builder.label :_destroy, class: "button-secondary button-sm" do %>
|
|
31
41
|
<%= icon "trash" %>
|
|
32
42
|
<% end %>
|
|
@@ -51,6 +61,9 @@
|
|
|
51
61
|
<% end %>
|
|
52
62
|
</div>
|
|
53
63
|
</details>
|
|
64
|
+
<% unless block.new_record? %>
|
|
65
|
+
<script>Iron.preference.apply("<%= disclosure_id %>", "open")</script>
|
|
66
|
+
<% end %>
|
|
54
67
|
</div>
|
|
55
68
|
|
|
56
69
|
<% else %>
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
bg-stone-700
|
|
21
21
|
"
|
|
22
22
|
>
|
|
23
|
-
<% if field.file.attached? && field.valid? %>
|
|
24
|
-
<%= image_tag field.file,
|
|
23
|
+
<% if field.file.attached? && field.file.blob.persisted? && field.valid? %>
|
|
24
|
+
<%= image_tag main_app.url_for(field.file),
|
|
25
25
|
class: "h-full w-full object-cover object-center",
|
|
26
26
|
data: {
|
|
27
27
|
file_upload_target: "preview",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
>
|
|
37
37
|
<% end %>
|
|
38
38
|
</div>
|
|
39
|
-
<%= builder.label :file, "Change", class: "
|
|
39
|
+
<%= builder.label :file, "Change", class: "button-primary mt-2" %>
|
|
40
40
|
<%= builder.file_field :file,
|
|
41
41
|
class: "hidden",
|
|
42
42
|
accept:
|
|
@@ -32,20 +32,13 @@
|
|
|
32
32
|
</div>
|
|
33
33
|
|
|
34
34
|
<div class="bg-white dark:bg-stone-800/50 rounded-lg shadow-sm p-6 max-w-96">
|
|
35
|
-
<h2 class="text-lg font-semibold mb-4">
|
|
35
|
+
<h2 class="text-lg font-semibold mb-4">Data Management</h2>
|
|
36
36
|
<p class="text-sm text-stone-600 dark:text-stone-400 mb-4">
|
|
37
|
-
Export
|
|
37
|
+
Export and import your CMS data.
|
|
38
38
|
</p>
|
|
39
39
|
<div class="flex flex-col gap-2">
|
|
40
|
-
<%= link_to "Export
|
|
41
|
-
|
|
42
|
-
class: "button-primary",
|
|
43
|
-
data: {
|
|
44
|
-
turbo: false,
|
|
45
|
-
} %>
|
|
46
|
-
<%= link_to "Import Content",
|
|
47
|
-
new_content_path,
|
|
48
|
-
class: "button-secondary" %>
|
|
40
|
+
<%= link_to "Export Data", account_exports_path, class: "button-secondary" %>
|
|
41
|
+
<%= link_to "Import Data", account_imports_path, class: "button-secondary" %>
|
|
49
42
|
</div>
|
|
50
43
|
</div>
|
|
51
44
|
</div>
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html class="h-full bg-stone-50 text-stone-950 antialiased dark:bg-stone-900 dark:text-white">
|
|
3
3
|
<head>
|
|
4
|
+
<script>
|
|
5
|
+
window.Iron = {
|
|
6
|
+
preference: {
|
|
7
|
+
storageKey: (k) => `iron:preference:${k}`,
|
|
8
|
+
apply: (id, property) => {
|
|
9
|
+
const el = document.getElementById(id)
|
|
10
|
+
if (!el) return
|
|
11
|
+
const raw = localStorage.getItem(Iron.preference.storageKey(id))
|
|
12
|
+
if (raw === null) return
|
|
13
|
+
el[property] = raw === "true" ? true : raw === "false" ? false : raw
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
4
18
|
<title><%= content_for(:title).present? ? "#{content_for(:title)} | Iron CMS" : "Iron CMS" %></title>
|
|
5
19
|
<%= csrf_meta_tags %>
|
|
6
20
|
<%= csp_meta_tag %>
|
data/config/routes.rb
CHANGED
|
@@ -16,15 +16,9 @@ Iron::Engine.routes.draw do
|
|
|
16
16
|
get "entries/search", to: "entries#search"
|
|
17
17
|
resources :icons, only: [ :index ]
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
post :import, on: :member
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
resource :content, only: [ :new ] do
|
|
26
|
-
get :export, on: :collection
|
|
27
|
-
post :import, on: :collection
|
|
19
|
+
namespace :account do
|
|
20
|
+
resources :exports, only: %i[index new create show]
|
|
21
|
+
resources :imports, only: %i[index new create show]
|
|
28
22
|
end
|
|
29
23
|
|
|
30
24
|
resources :block_definitions do
|