iron-cms 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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +174 -0
- data/Rakefile +8 -0
- data/app/assets/builds/iron.css +2 -0
- data/app/assets/config/iron_manifest.js +1 -0
- data/app/assets/images/iron/icons/sprite.svg +9425 -0
- data/app/assets/tailwind/iron/actiontext.css +550 -0
- data/app/assets/tailwind/iron/application.css +66 -0
- data/app/assets/tailwind/iron/components/button.css +74 -0
- data/app/assets/tailwind/iron/components/dropdown.css +42 -0
- data/app/assets/tailwind/iron/components/fieldset.css +18 -0
- data/app/assets/tailwind/iron/components/form.css +14 -0
- data/app/assets/tailwind/iron/components/icon-picker.css +9 -0
- data/app/assets/tailwind/iron/components/input.css +44 -0
- data/app/assets/tailwind/iron/components/page.css +7 -0
- data/app/assets/tailwind/iron/components/select.css +36 -0
- data/app/assets/tailwind/iron/components/sidebar.css +21 -0
- data/app/assets/tailwind/iron/components/textarea.css +27 -0
- data/app/controllers/concerns/iron/authentication.rb +66 -0
- data/app/controllers/concerns/iron/authorization.rb +11 -0
- data/app/controllers/concerns/iron/locale_aware.rb +20 -0
- data/app/controllers/concerns/iron/web_page.rb +18 -0
- data/app/controllers/iron/application_controller.rb +7 -0
- data/app/controllers/iron/block_definitions/field_definitions_controller.rb +8 -0
- data/app/controllers/iron/block_definitions_controller.rb +52 -0
- data/app/controllers/iron/blocks_controller.rb +12 -0
- data/app/controllers/iron/content_types/field_definitions_controller.rb +8 -0
- data/app/controllers/iron/content_types_controller.rb +52 -0
- data/app/controllers/iron/entries_controller.rb +103 -0
- data/app/controllers/iron/field_definitions_controller.rb +55 -0
- data/app/controllers/iron/first_runs_controller.rb +29 -0
- data/app/controllers/iron/home_controller.rb +6 -0
- data/app/controllers/iron/icons_controller.rb +10 -0
- data/app/controllers/iron/locales_controller.rb +52 -0
- data/app/controllers/iron/passwords_controller.rb +36 -0
- data/app/controllers/iron/references_controller.rb +11 -0
- data/app/controllers/iron/schemas_controller.rb +32 -0
- data/app/controllers/iron/sessions_controller.rb +32 -0
- data/app/controllers/iron/settings_controller.rb +23 -0
- data/app/controllers/iron/users_controller.rb +66 -0
- data/app/helpers/iron/application_helper.rb +25 -0
- data/app/helpers/iron/avatar_helper.rb +35 -0
- data/app/helpers/iron/block_helper.rb +4 -0
- data/app/helpers/iron/buttons_helper.rb +14 -0
- data/app/helpers/iron/components/badge_helper.rb +70 -0
- data/app/helpers/iron/components/dropdown_helper.rb +161 -0
- data/app/helpers/iron/content_types_helper.rb +11 -0
- data/app/helpers/iron/entries_helper.rb +13 -0
- data/app/helpers/iron/field_definitions_helper.rb +5 -0
- data/app/helpers/iron/form_builder.rb +43 -0
- data/app/helpers/iron/icons_helper.rb +30 -0
- data/app/helpers/iron/image_helper.rb +50 -0
- data/app/javascript/iron/application.js +6 -0
- data/app/javascript/iron/controllers/application.js +9 -0
- data/app/javascript/iron/controllers/element_controller.js +11 -0
- data/app/javascript/iron/controllers/file_upload_controller.js +18 -0
- data/app/javascript/iron/controllers/form_controller.js +7 -0
- data/app/javascript/iron/controllers/handle_controller.js +27 -0
- data/app/javascript/iron/controllers/icon_picker_controller.js +48 -0
- data/app/javascript/iron/controllers/index.js +4 -0
- data/app/javascript/iron/controllers/sortable_list_controller.js +125 -0
- data/app/javascript/iron/controllers/toggle_controller.js +14 -0
- data/app/javascript/iron/controllers/trix_controller.js +19 -0
- data/app/javascript/iron/lib/lexorank.js +51 -0
- data/app/jobs/iron/application_job.rb +4 -0
- data/app/jobs/iron/generate_entry_routes_job.rb +15 -0
- data/app/mailers/iron/application_mailer.rb +6 -0
- data/app/mailers/passwords_mailer.rb +6 -0
- data/app/models/concerns/iron/csv_serializable.rb +28 -0
- data/app/models/iron/account/joinable.rb +16 -0
- data/app/models/iron/account.rb +13 -0
- data/app/models/iron/application_record.rb +5 -0
- data/app/models/iron/block_definition/portable.rb +20 -0
- data/app/models/iron/block_definition.rb +10 -0
- data/app/models/iron/content_type/field_queryable.rb +101 -0
- data/app/models/iron/content_type/portable.rb +30 -0
- data/app/models/iron/content_type/titlable.rb +13 -0
- data/app/models/iron/content_type/web_publishable.rb +47 -0
- data/app/models/iron/content_type.rb +39 -0
- data/app/models/iron/content_types/collection.rb +6 -0
- data/app/models/iron/content_types/single.rb +9 -0
- data/app/models/iron/current.rb +12 -0
- data/app/models/iron/entry/deep_validation.rb +32 -0
- data/app/models/iron/entry/presentable.rb +40 -0
- data/app/models/iron/entry/schemable.rb +16 -0
- data/app/models/iron/entry/titlable.rb +35 -0
- data/app/models/iron/entry/web_publishable.rb +85 -0
- data/app/models/iron/entry.rb +16 -0
- data/app/models/iron/field/belongs_to_entry.rb +29 -0
- data/app/models/iron/field.rb +20 -0
- data/app/models/iron/field_definition/portable.rb +36 -0
- data/app/models/iron/field_definition.rb +42 -0
- data/app/models/iron/field_definitions/block.rb +23 -0
- data/app/models/iron/field_definitions/block_list.rb +8 -0
- data/app/models/iron/field_definitions/boolean.rb +7 -0
- data/app/models/iron/field_definitions/date.rb +7 -0
- data/app/models/iron/field_definitions/file.rb +67 -0
- data/app/models/iron/field_definitions/number.rb +7 -0
- data/app/models/iron/field_definitions/reference.rb +8 -0
- data/app/models/iron/field_definitions/reference_list.rb +8 -0
- data/app/models/iron/field_definitions/rich_text_area.rb +4 -0
- data/app/models/iron/field_definitions/text_area.rb +7 -0
- data/app/models/iron/field_definitions/text_field.rb +25 -0
- data/app/models/iron/fields/block.rb +40 -0
- data/app/models/iron/fields/block_list.rb +16 -0
- data/app/models/iron/fields/boolean.rb +7 -0
- data/app/models/iron/fields/date.rb +7 -0
- data/app/models/iron/fields/file.rb +29 -0
- data/app/models/iron/fields/number.rb +7 -0
- data/app/models/iron/fields/reference.rb +14 -0
- data/app/models/iron/fields/reference_list.rb +17 -0
- data/app/models/iron/fields/rich_text_area.rb +9 -0
- data/app/models/iron/fields/text_area.rb +7 -0
- data/app/models/iron/fields/text_field.rb +19 -0
- data/app/models/iron/first_run.rb +13 -0
- data/app/models/iron/icon_catalog.rb +45 -0
- data/app/models/iron/locale.rb +30 -0
- data/app/models/iron/reference.rb +8 -0
- data/app/models/iron/schema_archive.rb +71 -0
- data/app/models/iron/schema_exporter.rb +15 -0
- data/app/models/iron/schema_importer.rb +278 -0
- data/app/models/iron/session.rb +5 -0
- data/app/models/iron/user/role.rb +21 -0
- data/app/models/iron/user.rb +37 -0
- data/app/views/active_storage/blobs/_blob.html.erb +14 -0
- data/app/views/iron/block_definitions/_block_definition.html.erb +21 -0
- data/app/views/iron/block_definitions/_form.html.erb +46 -0
- data/app/views/iron/block_definitions/edit.html.erb +8 -0
- data/app/views/iron/block_definitions/index.html.erb +18 -0
- data/app/views/iron/block_definitions/new.html.erb +8 -0
- data/app/views/iron/block_definitions/show.html.erb +34 -0
- data/app/views/iron/blocks/new.turbo_stream.erb +6 -0
- data/app/views/iron/content_types/_content_type.html.erb +21 -0
- data/app/views/iron/content_types/_form.html.erb +137 -0
- data/app/views/iron/content_types/edit.html.erb +8 -0
- data/app/views/iron/content_types/index.html.erb +28 -0
- data/app/views/iron/content_types/new.html.erb +8 -0
- data/app/views/iron/content_types/show.html.erb +49 -0
- data/app/views/iron/entries/_entry.html.erb +17 -0
- data/app/views/iron/entries/_entry_option.html.erb +19 -0
- data/app/views/iron/entries/_form.html.erb +63 -0
- data/app/views/iron/entries/edit.html.erb +52 -0
- data/app/views/iron/entries/entry.html.erb +21 -0
- data/app/views/iron/entries/fields/_block.html.erb +92 -0
- data/app/views/iron/entries/fields/_block_list.html.erb +45 -0
- data/app/views/iron/entries/fields/_boolean.html.erb +13 -0
- data/app/views/iron/entries/fields/_date.html.erb +11 -0
- data/app/views/iron/entries/fields/_file.html.erb +60 -0
- data/app/views/iron/entries/fields/_number.html.erb +11 -0
- data/app/views/iron/entries/fields/_reference.html.erb +12 -0
- data/app/views/iron/entries/fields/_reference_item.html.erb +28 -0
- data/app/views/iron/entries/fields/_reference_list.html.erb +44 -0
- data/app/views/iron/entries/fields/_rich_text_area.html.erb +13 -0
- data/app/views/iron/entries/fields/_text_area.html.erb +11 -0
- data/app/views/iron/entries/fields/_text_field.html.erb +28 -0
- data/app/views/iron/entries/index.html.erb +21 -0
- data/app/views/iron/entries/new.html.erb +8 -0
- data/app/views/iron/entries/search.html.erb +33 -0
- data/app/views/iron/field_definitions/_field_definition.html.erb +37 -0
- data/app/views/iron/field_definitions/block/_form.html.erb +16 -0
- data/app/views/iron/field_definitions/block_list/_form.html.erb +13 -0
- data/app/views/iron/field_definitions/boolean/_form.html.erb +2 -0
- data/app/views/iron/field_definitions/date/_form.html.erb +2 -0
- data/app/views/iron/field_definitions/edit.html.erb +16 -0
- data/app/views/iron/field_definitions/file/_form.html.erb +40 -0
- data/app/views/iron/field_definitions/index.html.erb +14 -0
- data/app/views/iron/field_definitions/layouts/_form.html.erb +47 -0
- data/app/views/iron/field_definitions/new.html.erb +22 -0
- data/app/views/iron/field_definitions/number/_form.html.erb +2 -0
- data/app/views/iron/field_definitions/reference/_form.html.erb +13 -0
- data/app/views/iron/field_definitions/reference_list/_form.html.erb +13 -0
- data/app/views/iron/field_definitions/rich_text_area/_form.html.erb +2 -0
- data/app/views/iron/field_definitions/text_area/_form.html.erb +2 -0
- data/app/views/iron/field_definitions/text_field/_form.html.erb +17 -0
- data/app/views/iron/first_runs/show.html.erb +44 -0
- data/app/views/iron/home/show.html.erb +2 -0
- data/app/views/iron/icons/index.html.erb +21 -0
- data/app/views/iron/locales/_form.html.erb +24 -0
- data/app/views/iron/locales/_locale.html.erb +15 -0
- data/app/views/iron/locales/edit.html.erb +8 -0
- data/app/views/iron/locales/index.html.erb +12 -0
- data/app/views/iron/locales/new.html.erb +8 -0
- data/app/views/iron/passwords/edit.html.erb +7 -0
- data/app/views/iron/passwords/new.html.erb +26 -0
- data/app/views/iron/passwords_mailer/reset.html.erb +4 -0
- data/app/views/iron/passwords_mailer/reset.text.erb +2 -0
- data/app/views/iron/published_pages/show.html.erb +10 -0
- data/app/views/iron/references/new.turbo_stream.erb +6 -0
- data/app/views/iron/schemas/new.html.erb +57 -0
- data/app/views/iron/sessions/new.html.erb +36 -0
- data/app/views/iron/settings/show.html.erb +38 -0
- data/app/views/iron/shared/_icon_picker.html.erb +60 -0
- data/app/views/iron/users/_form.html.erb +21 -0
- data/app/views/iron/users/_user.html.erb +27 -0
- data/app/views/iron/users/edit.html.erb +12 -0
- data/app/views/iron/users/index.html.erb +21 -0
- data/app/views/iron/users/new.html.erb +29 -0
- data/app/views/iron/users/show.html.erb +21 -0
- data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
- data/app/views/layouts/iron/_sidebar.html.erb +83 -0
- data/app/views/layouts/iron/_sidebar_item.html.erb +14 -0
- data/app/views/layouts/iron/_toast.html.erb +29 -0
- data/app/views/layouts/iron/application.html.erb +46 -0
- data/app/views/layouts/iron/authentication.html.erb +41 -0
- data/config/importmap.rb +9 -0
- data/config/routes.rb +40 -0
- data/db/migrate/20250422131656_initial_iron_schema.rb +105 -0
- data/db/migrate/20250427100754_create_iron_accounts.rb +10 -0
- data/db/migrate/20250427104020_add_role_to_iron_users.rb +5 -0
- data/db/migrate/20250428094923_add_default_iron_locale_to_iron_account.rb +5 -0
- data/db/migrate/20250504144731_create_join_table_content_types_field_definitions.rb +25 -0
- data/db/migrate/20250505090716_add_title_field_definition_to_iron_content_types.rb +5 -0
- data/db/migrate/20250512210352_create_iron_references.rb +11 -0
- data/db/migrate/20250524220355_add_referenced_entry_id_to_iron_fields.rb +5 -0
- data/db/migrate/20250529211703_add_metadata_to_iron_field_definitions.rb +5 -0
- data/db/migrate/20250529211704_add_value_boolean_to_iron_fields.rb +5 -0
- data/db/migrate/20250531172837_add_type_to_iron_content_types.rb +5 -0
- data/db/migrate/20250601080146_add_icon_to_iron_content_types.rb +5 -0
- data/db/migrate/20250609091605_add_web_publishing_to_iron_content_types.rb +7 -0
- data/db/migrate/20250609091813_add_route_to_iron_entries.rb +6 -0
- data/lib/generators/iron/pages/pages_generator.rb +34 -0
- data/lib/generators/iron/pages/templates/pages_controller.rb +14 -0
- data/lib/generators/iron/pages/templates/show.html.erb +9 -0
- data/lib/generators/iron/template/template_generator.rb +35 -0
- data/lib/generators/iron/template/templates/content_type.html.erb +5 -0
- data/lib/iron/cva.rb +69 -0
- data/lib/iron/engine.rb +42 -0
- data/lib/iron/image_analyzer.rb +26 -0
- data/lib/iron/lexorank/exceptions.rb +4 -0
- data/lib/iron/lexorank/rankable.rb +185 -0
- data/lib/iron/lexorank/rebalance_rank_job.rb +10 -0
- data/lib/iron/lexorank/utils.rb +77 -0
- data/lib/iron/lexorank.rb +7 -0
- data/lib/iron/routing.rb +66 -0
- data/lib/iron/sdk.rb +19 -0
- data/lib/iron/version.rb +3 -0
- data/lib/iron.rb +8 -0
- data/lib/puma/plugin/iron_tailwindcss.rb +78 -0
- metadata +458 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
module Iron
|
2
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
3
|
+
INPUT_FIELD_TYPES = %i[
|
4
|
+
text_field
|
5
|
+
password_field
|
6
|
+
email_field
|
7
|
+
number_field
|
8
|
+
tel_field
|
9
|
+
url_field
|
10
|
+
search_field
|
11
|
+
date_field
|
12
|
+
time_field
|
13
|
+
datetime_field
|
14
|
+
datetime_local_field
|
15
|
+
month_field
|
16
|
+
week_field
|
17
|
+
color_field
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
INPUT_FIELD_TYPES.each do |field_type|
|
21
|
+
define_method field_type do |method, options = {}|
|
22
|
+
if object&.errors&.[](method).present?
|
23
|
+
options[:class] = @template.tw(options[:class], "is-invalid")
|
24
|
+
end
|
25
|
+
|
26
|
+
super(method, options)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def error_for(method)
|
31
|
+
if object&.errors&.[](method)&.any?
|
32
|
+
@template.content_tag(:div, class: "text-red-500 text-sm font-medium mt-1") do
|
33
|
+
object.errors[method].join(", ")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def label(method, text = nil, options = {}, &block)
|
39
|
+
options[:class] = @template.tw("text-sm/6 font-medium", options[:class])
|
40
|
+
super(method, text, options, &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Iron
|
2
|
+
module IconsHelper
|
3
|
+
SPRITE_PATH = "iron/icons/sprite.svg"
|
4
|
+
|
5
|
+
def available_icons
|
6
|
+
IconCatalog.all
|
7
|
+
end
|
8
|
+
|
9
|
+
# Examples
|
10
|
+
# icon "heart" # => 24 px outline heart
|
11
|
+
# icon "trash", class: "text-red-600 hover:text-red-700"
|
12
|
+
#
|
13
|
+
def icon(name, **attributes)
|
14
|
+
css_class = attributes.delete(:class)
|
15
|
+
data_attrs = { slot: "icon" }.merge!(attributes.delete(:data) || {})
|
16
|
+
|
17
|
+
tag.svg **attributes.merge(
|
18
|
+
class: tw(
|
19
|
+
"stroke-current size-5 fill-none stroke-2 [stroke-linecap:round] [stroke-linejoin:round]",
|
20
|
+
css_class
|
21
|
+
),
|
22
|
+
data: data_attrs,
|
23
|
+
viewBox: "0 0 24 24",
|
24
|
+
aria_hidden: "true"
|
25
|
+
) do
|
26
|
+
tag.use href: "#{image_path(SPRITE_PATH)}##{name}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Iron
|
2
|
+
module ImageHelper
|
3
|
+
IMAGE_BREAKPOINTS = [ 150, 320, 640, 1024, 1920 ].freeze
|
4
|
+
|
5
|
+
def iron_image_tag(attachment, sizes: "100vw", loading: "lazy", blur: true, **options)
|
6
|
+
return "" unless attachment&.attached?
|
7
|
+
return image_tag(attachment, **options) unless attachment.blob.variable?
|
8
|
+
|
9
|
+
image_options = options.merge(
|
10
|
+
srcset: generate_srcset(attachment),
|
11
|
+
sizes:,
|
12
|
+
loading:,
|
13
|
+
width: attachment.blob.metadata["width"],
|
14
|
+
height: attachment.blob.metadata["height"]
|
15
|
+
).compact
|
16
|
+
|
17
|
+
if blur && attachment.blob.metadata["blur_data_uri"].present?
|
18
|
+
blur_style = "background-image: url('#{attachment.blob.metadata["blur_data_uri"]}'); background-size: cover; background-position: center;"
|
19
|
+
image_options[:style] = "#{blur_style} #{options[:style]}".strip
|
20
|
+
end
|
21
|
+
|
22
|
+
image_tag attachment.variant(resize_to_limit: [ IMAGE_BREAKPOINTS.third, nil ]), **image_options
|
23
|
+
end
|
24
|
+
|
25
|
+
def iron_picture_tag(attachment, sizes: "100vw", loading: "lazy", blur: true, **options)
|
26
|
+
return "" unless attachment&.attached?
|
27
|
+
return image_tag(attachment, **options) unless attachment.blob.variable?
|
28
|
+
|
29
|
+
content_tag :picture do
|
30
|
+
concat source_tag(attachment, :avif, sizes)
|
31
|
+
concat source_tag(attachment, :webp, sizes)
|
32
|
+
concat iron_image_tag(attachment, sizes: sizes, loading: loading, blur: blur, **options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def source_tag(attachment, format, sizes)
|
39
|
+
tag.source type: "image/#{format}", srcset: generate_srcset(attachment, format:), sizes:
|
40
|
+
end
|
41
|
+
|
42
|
+
def generate_srcset(attachment, format: nil)
|
43
|
+
IMAGE_BREAKPOINTS.map { |w| "#{variant_url(attachment, w, format)} #{w}w" }.join(", ")
|
44
|
+
end
|
45
|
+
|
46
|
+
def variant_url(attachment, width, format = nil)
|
47
|
+
url_for(attachment.variant(resize_to_limit: [ width, nil ], format: format))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
|
3
|
+
export default class FileUploadController extends Controller {
|
4
|
+
static targets = ["preview", "placeholder"];
|
5
|
+
|
6
|
+
preview(event) {
|
7
|
+
const file = event.target.files[0];
|
8
|
+
if (file) {
|
9
|
+
const previewUrl = URL.createObjectURL(file);
|
10
|
+
this.previewTarget.src = previewUrl;
|
11
|
+
if (this.hasPlaceholderTarget) {
|
12
|
+
this.placeholderTarget.classList.add("hidden");
|
13
|
+
}
|
14
|
+
this.previewTarget.classList.remove("hidden");
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = ["source", "destination"];
|
5
|
+
static values = { automatic: { type: Boolean, default: true } };
|
6
|
+
|
7
|
+
handleize() {
|
8
|
+
if (this.automaticValue) {
|
9
|
+
this.destinationTarget.value = handleize(this.sourceTarget.value);
|
10
|
+
}
|
11
|
+
}
|
12
|
+
|
13
|
+
edit(event) {
|
14
|
+
this.automaticValue = false;
|
15
|
+
const value = event.target.value;
|
16
|
+
if (value !== handleize(value)) {
|
17
|
+
this.destinationTarget.value = handleize(value);
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
function handleize(str) {
|
23
|
+
return str
|
24
|
+
.toLowerCase()
|
25
|
+
.replace(/[^a-z0-9\s_]/g, "")
|
26
|
+
.replace(/\s+/g, "_");
|
27
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = ["input", "preview", "label", "popover", "icon"]
|
5
|
+
|
6
|
+
connect() {
|
7
|
+
this.updateSelectedState()
|
8
|
+
}
|
9
|
+
|
10
|
+
select(event) {
|
11
|
+
const iconName = event.currentTarget.dataset.iconName
|
12
|
+
|
13
|
+
this.inputTarget.value = iconName
|
14
|
+
this.previewTarget.innerHTML = event.currentTarget.innerHTML
|
15
|
+
this.labelTarget.textContent = iconName
|
16
|
+
this.updateSelectedState()
|
17
|
+
this.popoverTarget.hidePopover()
|
18
|
+
this.inputTarget.dispatchEvent(new Event('change', { bubbles: true }))
|
19
|
+
}
|
20
|
+
|
21
|
+
iconTargetConnected(target) {
|
22
|
+
const currentValue = this.inputTarget.value
|
23
|
+
if (target.dataset.iconName === currentValue) {
|
24
|
+
target.dataset.ui = "selected"
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
updateSelectedState() {
|
29
|
+
const currentValue = this.inputTarget.value
|
30
|
+
this.iconTargets.forEach(icon => {
|
31
|
+
if (icon.dataset.iconName === currentValue) {
|
32
|
+
icon.dataset.ui = "selected"
|
33
|
+
} else {
|
34
|
+
delete icon.dataset.ui
|
35
|
+
}
|
36
|
+
})
|
37
|
+
}
|
38
|
+
|
39
|
+
filter(event) {
|
40
|
+
const searchTerm = event.target.value.toLowerCase()
|
41
|
+
|
42
|
+
this.iconTargets.forEach(iconButton => {
|
43
|
+
const iconName = iconButton.dataset.iconName
|
44
|
+
const matches = iconName.includes(searchTerm)
|
45
|
+
iconButton.style.display = matches ? '' : 'none'
|
46
|
+
})
|
47
|
+
}
|
48
|
+
}
|
@@ -0,0 +1,4 @@
|
|
1
|
+
// Import and register all your controllers from the importmap via controllers/**/*_controller
|
2
|
+
import { application } from "controllers/application";
|
3
|
+
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading";
|
4
|
+
eagerLoadControllersFrom("controllers", application);
|
@@ -0,0 +1,125 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
import { Lexorank } from "lib/lexorank";
|
3
|
+
|
4
|
+
export default class extends Controller {
|
5
|
+
static targets = ["item", "handle", "rank"];
|
6
|
+
|
7
|
+
handleTargetConnected(handle) {
|
8
|
+
handle.addEventListener("pointerdown", this.#onHandleTapStart);
|
9
|
+
handle.addEventListener("pointerup", this.#onHandleTapEnd)
|
10
|
+
}
|
11
|
+
|
12
|
+
handleTargetDisconnected(handle) {
|
13
|
+
handle.removeEventListener("pointerdown", this.#onHandleTapStart);
|
14
|
+
handle.removeEventListener("pointerup", this.#onHandleTapEnd)
|
15
|
+
}
|
16
|
+
|
17
|
+
rankTargetConnected(rank) {
|
18
|
+
if (!rank.value) {
|
19
|
+
const item = this.itemTargets.find(i => i.contains(rank));
|
20
|
+
this.updateRank(item);
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
onDragStart = (event) => {
|
25
|
+
this.draggedEl = event.currentTarget;
|
26
|
+
event.dataTransfer.effectAllowed = "move";
|
27
|
+
event.dataTransfer.setData("text/plain", "");
|
28
|
+
requestAnimationFrame(() => {
|
29
|
+
this.draggedEl.classList.add("dragging");
|
30
|
+
});
|
31
|
+
event.stopPropagation();
|
32
|
+
}
|
33
|
+
|
34
|
+
onDragEnd = () => {
|
35
|
+
this.draggedEl.classList.remove("dragging");
|
36
|
+
this.updateRank(this.draggedEl);
|
37
|
+
this.draggedEl = null;
|
38
|
+
|
39
|
+
this.#disableDraggableBehaviour();
|
40
|
+
}
|
41
|
+
|
42
|
+
onDragOver = (event) => {
|
43
|
+
event.preventDefault();
|
44
|
+
const overIndex = this.itemTargets.findIndex(t => t.contains(event.target));
|
45
|
+
const overTarget = this.itemTargets[overIndex];
|
46
|
+
if (!overTarget || overTarget === this.draggedEl) return;
|
47
|
+
|
48
|
+
const firstRects = new Map(this.itemTargets.map(el => [el, el.getBoundingClientRect()]));
|
49
|
+
|
50
|
+
const shouldInsertBefore = this.draggedEl.getBoundingClientRect().top > overTarget.getBoundingClientRect().top;
|
51
|
+
this.element.insertBefore(this.draggedEl, shouldInsertBefore ? overTarget : this.itemTargets[overIndex + 1] ?? null);
|
52
|
+
|
53
|
+
this.itemTargets.forEach(el => {
|
54
|
+
const dy = firstRects.get(el).top - el.getBoundingClientRect().top;
|
55
|
+
if (dy) {
|
56
|
+
el.style.transition = "none";
|
57
|
+
el.style.transform = `translateY(${dy}px)`;
|
58
|
+
requestAnimationFrame(() => {
|
59
|
+
el.style.transition = "transform 150ms ease";
|
60
|
+
el.style.transform = "";
|
61
|
+
});
|
62
|
+
}
|
63
|
+
});
|
64
|
+
}
|
65
|
+
|
66
|
+
updateRank(item) {
|
67
|
+
const idx = this.itemTargets.indexOf(item);
|
68
|
+
const prevRank = idx > 0 ? this.#rankFor(this.itemTargets[idx - 1]) : undefined;
|
69
|
+
const nextRank = idx < this.itemTargets.length - 1 ? this.#rankFor(this.itemTargets[idx + 1]) : undefined;
|
70
|
+
const newRank = Lexorank.between(prevRank, nextRank);
|
71
|
+
this.#setRank(item, newRank);
|
72
|
+
}
|
73
|
+
|
74
|
+
// Private
|
75
|
+
|
76
|
+
#onHandleTapStart = () => {
|
77
|
+
this.#enableDraggableBehaviour();
|
78
|
+
}
|
79
|
+
|
80
|
+
#onHandleTapEnd = () => {
|
81
|
+
this.#disableDraggableBehaviour();
|
82
|
+
}
|
83
|
+
|
84
|
+
#enableDraggableBehaviour = () => {
|
85
|
+
this.element.addEventListener("dragover", this.onDragOver);
|
86
|
+
this.itemTargets.forEach(this.#enableItemDraggableBehaviour);
|
87
|
+
}
|
88
|
+
|
89
|
+
#disableDraggableBehaviour = () => {
|
90
|
+
this.element.removeEventListener("dragover", this.onDragOver);
|
91
|
+
this.itemTargets.forEach(this.#disableItemDraggableBehaviour);
|
92
|
+
};
|
93
|
+
|
94
|
+
#enableItemDraggableBehaviour = (item) => {
|
95
|
+
item.setAttribute("draggable", "true");
|
96
|
+
item.addEventListener("dragstart", this.onDragStart);
|
97
|
+
item.addEventListener("dragend", this.onDragEnd);
|
98
|
+
}
|
99
|
+
|
100
|
+
#disableItemDraggableBehaviour = (item) => {
|
101
|
+
item.removeAttribute("draggable");
|
102
|
+
item.removeEventListener("dragstart", this.onDragStart);
|
103
|
+
item.removeEventListener("dragend", this.onDragEnd);
|
104
|
+
}
|
105
|
+
|
106
|
+
#rankFor(item) {
|
107
|
+
return this.#findRankInputForItem(item)?.value;
|
108
|
+
}
|
109
|
+
|
110
|
+
#setRank(item, value) {
|
111
|
+
const input = this.#findRankInputForItem(item);
|
112
|
+
if (input && input.value != value) {
|
113
|
+
input.value = value;
|
114
|
+
input.dispatchEvent(
|
115
|
+
new Event("change", { bubbles: true, cancelable: true })
|
116
|
+
);
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
#findRankInputForItem(item) {
|
121
|
+
const input = this.rankTargets.find(r => item.contains(r));
|
122
|
+
if (input == null) console.warn("Item is missing a rank input target", item);
|
123
|
+
return input;
|
124
|
+
}
|
125
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static values = { selector: String };
|
5
|
+
|
6
|
+
toggle(event) {
|
7
|
+
const isChecked = event.target.checked;
|
8
|
+
const targets = document.querySelectorAll(this.selectorValue);
|
9
|
+
|
10
|
+
targets.forEach(target => {
|
11
|
+
target.dataset.ui = isChecked ? "visible" : "hidden";
|
12
|
+
});
|
13
|
+
}
|
14
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
connect() {
|
5
|
+
this.element.addEventListener("turbo:before-morph-attribute", this.#preventMorphing);
|
6
|
+
this.element.addEventListener("turbo:before-morph-element", this.#preventMorphing);
|
7
|
+
}
|
8
|
+
|
9
|
+
// Private
|
10
|
+
|
11
|
+
#preventMorphing = (event) => {
|
12
|
+
const { target } = event;
|
13
|
+
|
14
|
+
if (target.tagName == "TRIX-EDITOR" || target.tagName == "TRIX-TOOLBAR") {
|
15
|
+
event.preventDefault();
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
const MIN_RANK_VALUE = "0";
|
2
|
+
const MAX_RANK_VALUE = "z";
|
3
|
+
|
4
|
+
export class Lexorank {
|
5
|
+
static between(before, after) {
|
6
|
+
const _before = before || MIN_RANK_VALUE;
|
7
|
+
const _after = after || MAX_RANK_VALUE;
|
8
|
+
let result = '';
|
9
|
+
let index = 0;
|
10
|
+
let found = false;
|
11
|
+
|
12
|
+
while (!found) {
|
13
|
+
const prev_char = _before[index] || MIN_RANK_VALUE;
|
14
|
+
const after_char = _after[index] || MAX_RANK_VALUE;
|
15
|
+
if (prev_char === after_char) {
|
16
|
+
result += prev_char;
|
17
|
+
index++;
|
18
|
+
} else {
|
19
|
+
const mid_char = character_between(prev_char, after_char);
|
20
|
+
if (mid_char === prev_char || mid_char === after_char) {
|
21
|
+
result += prev_char;
|
22
|
+
index++;
|
23
|
+
} else {
|
24
|
+
result += mid_char;
|
25
|
+
found = true;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
return result;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
/**
|
34
|
+
* @param {String} prev A single character
|
35
|
+
* @param {String} after A single character
|
36
|
+
* @returns {String} A character that sits between prev and after
|
37
|
+
*/
|
38
|
+
function character_between(prev, after) {
|
39
|
+
let middle_ascii = Math.round((ascii(prev) + ascii(after)) / 2);
|
40
|
+
return String.fromCharCode(middle_ascii);
|
41
|
+
}
|
42
|
+
|
43
|
+
/**
|
44
|
+
* @param {String} char A single character
|
45
|
+
* @returns {Number} The ascii value of char
|
46
|
+
*/
|
47
|
+
function ascii(char) {
|
48
|
+
return char.charCodeAt(0);
|
49
|
+
}
|
50
|
+
|
51
|
+
window.Lexorank = Lexorank;
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Iron
|
2
|
+
class GenerateEntryRoutesJob < ApplicationJob
|
3
|
+
queue_as :default
|
4
|
+
|
5
|
+
def perform(content_type = nil)
|
6
|
+
if content_type
|
7
|
+
content_type.entries.where(route: nil).find_each(&:save) if content_type.web_publishing_enabled?
|
8
|
+
else
|
9
|
+
ContentType.web_published.find_each do |ct|
|
10
|
+
ct.entries.where(route: nil).find_each(&:save)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Iron
|
2
|
+
module CsvSerializable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class_methods do
|
6
|
+
def to_csv
|
7
|
+
CSV.generate do |csv|
|
8
|
+
csv << csv_headers
|
9
|
+
csv_scope.find_each do |record|
|
10
|
+
csv << record.to_csv_row
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def csv_headers
|
16
|
+
raise NotImplementedError, "#{name} must implement csv_headers"
|
17
|
+
end
|
18
|
+
|
19
|
+
def csv_scope
|
20
|
+
all
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_csv_row
|
25
|
+
raise NotImplementedError, "#{self.class.name} must implement to_csv_row"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Iron::Account::Joinable
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_create { self.join_code = generate_join_code }
|
6
|
+
end
|
7
|
+
|
8
|
+
def reset_join_code
|
9
|
+
update! join_code: generate_join_code
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def generate_join_code
|
14
|
+
SecureRandom.alphanumeric(12).scan(/.{4}/).join("-")
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Iron
|
2
|
+
class Account < ApplicationRecord
|
3
|
+
include Joinable
|
4
|
+
|
5
|
+
belongs_to :default_locale, class_name: "Iron::Locale"
|
6
|
+
|
7
|
+
def self.clear_schema!
|
8
|
+
FieldDefinition.destroy_all
|
9
|
+
ContentType.destroy_all
|
10
|
+
BlockDefinition.destroy_all
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Iron
|
2
|
+
module BlockDefinition::Portable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include ::Iron::CsvSerializable
|
5
|
+
|
6
|
+
class_methods do
|
7
|
+
def csv_headers
|
8
|
+
%w[handle name description]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_csv_row
|
13
|
+
[
|
14
|
+
handle,
|
15
|
+
name,
|
16
|
+
description
|
17
|
+
]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|