rivet_cms 0.1.0.pre
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 +28 -0
- data/Rakefile +8 -0
- data/app/assets/builds/rivet_cms.css +2 -0
- data/app/assets/builds/rivet_cms.js +9536 -0
- data/app/assets/builds/rivet_cms.js.map +7 -0
- data/app/assets/stylesheets/rivet_cms/application.tailwind.css +25 -0
- data/app/assets/stylesheets/rivet_cms/brand_colors.css +168 -0
- data/app/controllers/rivet_cms/api/docs_controller.rb +38 -0
- data/app/controllers/rivet_cms/application_controller.rb +5 -0
- data/app/controllers/rivet_cms/components_controller.rb +7 -0
- data/app/controllers/rivet_cms/content_types_controller.rb +61 -0
- data/app/controllers/rivet_cms/dashboard_controller.rb +10 -0
- data/app/controllers/rivet_cms/fields_controller.rb +109 -0
- data/app/helpers/rivet_cms/application_helper.rb +7 -0
- data/app/helpers/rivet_cms/brand_color_helper.rb +71 -0
- data/app/helpers/rivet_cms/flash_helper.rb +37 -0
- data/app/helpers/rivet_cms/sign_out_helper.rb +11 -0
- data/app/javascript/controllers/content_type_form_controller.js +53 -0
- data/app/javascript/controllers/field_layout_controller.js +709 -0
- data/app/javascript/rivet_cms.js +29 -0
- data/app/jobs/rivet_cms/application_job.rb +4 -0
- data/app/mailers/rivet_cms/application_mailer.rb +6 -0
- data/app/models/rivet_cms/application_record.rb +5 -0
- data/app/models/rivet_cms/component.rb +4 -0
- data/app/models/rivet_cms/content.rb +4 -0
- data/app/models/rivet_cms/content_type.rb +40 -0
- data/app/models/rivet_cms/content_value.rb +4 -0
- data/app/models/rivet_cms/field.rb +82 -0
- data/app/models/rivet_cms/field_values/base.rb +11 -0
- data/app/models/rivet_cms/field_values/boolean.rb +4 -0
- data/app/models/rivet_cms/field_values/integer.rb +4 -0
- data/app/models/rivet_cms/field_values/string.rb +4 -0
- data/app/models/rivet_cms/field_values/text.rb +4 -0
- data/app/services/rivet_cms/open_api_generator.rb +245 -0
- data/app/views/layouts/rivet_cms/application.html.erb +49 -0
- data/app/views/rivet_cms/api/docs/show.html.erb +47 -0
- data/app/views/rivet_cms/content_types/_form.html.erb +98 -0
- data/app/views/rivet_cms/content_types/edit.html.erb +27 -0
- data/app/views/rivet_cms/content_types/index.html.erb +151 -0
- data/app/views/rivet_cms/content_types/new.html.erb +19 -0
- data/app/views/rivet_cms/content_types/show.html.erb +147 -0
- data/app/views/rivet_cms/dashboard/index.html.erb +263 -0
- data/app/views/rivet_cms/fields/_form.html.erb +111 -0
- data/app/views/rivet_cms/fields/edit.html.erb +25 -0
- data/app/views/rivet_cms/fields/index.html.erb +126 -0
- data/app/views/rivet_cms/fields/new.html.erb +25 -0
- data/app/views/rivet_cms/shared/_navigation.html.erb +153 -0
- data/config/i18n-tasks.yml +178 -0
- data/config/locales/en.yml +14 -0
- data/config/routes.rb +56 -0
- data/db/migrate/20250317194359_create_core_tables.rb +90 -0
- data/lib/rivet_cms/engine.rb +55 -0
- data/lib/rivet_cms/version.rb +3 -0
- data/lib/rivet_cms.rb +44 -0
- data/lib/tasks/rivet_cms_tasks.rake +4 -0
- metadata +231 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
@layer base {
|
2
|
+
:root {
|
3
|
+
/* Brand color variables will be set dynamically */
|
4
|
+
--color-brand-50: '';
|
5
|
+
--color-brand-100: '';
|
6
|
+
--color-brand-200: '';
|
7
|
+
--color-brand-300: '';
|
8
|
+
--color-brand-400: '';
|
9
|
+
--color-brand-500: '';
|
10
|
+
--color-brand-600: '';
|
11
|
+
--color-brand-700: '';
|
12
|
+
--color-brand-800: '';
|
13
|
+
--color-brand-900: '';
|
14
|
+
--color-brand-950: '';
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
@import url('https://fonts.googleapis.com/css2?family=Passion+One:wght@400;700;900&display=swap');
|
19
|
+
|
20
|
+
@import "tailwindcss";
|
21
|
+
@import "./brand_colors";
|
22
|
+
|
23
|
+
@theme {
|
24
|
+
--font-passion-one: "Passion One", sans-serif;
|
25
|
+
}
|
@@ -0,0 +1,168 @@
|
|
1
|
+
/*
|
2
|
+
This file contains overrides for brand colors.
|
3
|
+
It should be loaded after the main Tailwind CSS.
|
4
|
+
*/
|
5
|
+
|
6
|
+
/* Brand color backgrounds */
|
7
|
+
.bg-brand-50 { background-color: var(--color-brand-50) !important; }
|
8
|
+
.bg-brand-100 { background-color: var(--color-brand-100) !important; }
|
9
|
+
.bg-brand-200 { background-color: var(--color-brand-200) !important; }
|
10
|
+
.bg-brand-300 { background-color: var(--color-brand-300) !important; }
|
11
|
+
.bg-brand-400 { background-color: var(--color-brand-400) !important; }
|
12
|
+
.bg-brand-500 { background-color: var(--color-brand-500) !important; }
|
13
|
+
.bg-brand-600 { background-color: var(--color-brand-600) !important; }
|
14
|
+
.bg-brand-700 { background-color: var(--color-brand-700) !important; }
|
15
|
+
.bg-brand-800 { background-color: var(--color-brand-800) !important; }
|
16
|
+
.bg-brand-900 { background-color: var(--color-brand-900) !important; }
|
17
|
+
.bg-brand-950 { background-color: var(--color-brand-950) !important; }
|
18
|
+
|
19
|
+
/* Brand color text */
|
20
|
+
.text-brand-50 { color: var(--color-brand-50) !important; }
|
21
|
+
.text-brand-100 { color: var(--color-brand-100) !important; }
|
22
|
+
.text-brand-200 { color: var(--color-brand-200) !important; }
|
23
|
+
.text-brand-300 { color: var(--color-brand-300) !important; }
|
24
|
+
.text-brand-400 { color: var(--color-brand-400) !important; }
|
25
|
+
.text-brand-500 { color: var(--color-brand-500) !important; }
|
26
|
+
.text-brand-600 { color: var(--color-brand-600) !important; }
|
27
|
+
.text-brand-700 { color: var(--color-brand-700) !important; }
|
28
|
+
.text-brand-800 { color: var(--color-brand-800) !important; }
|
29
|
+
.text-brand-900 { color: var(--color-brand-900) !important; }
|
30
|
+
.text-brand-950 { color: var(--color-brand-950) !important; }
|
31
|
+
|
32
|
+
/* Brand color borders */
|
33
|
+
.border-brand-50 { border-color: var(--color-brand-50) !important; }
|
34
|
+
.border-brand-100 { border-color: var(--color-brand-100) !important; }
|
35
|
+
.border-brand-200 { border-color: var(--color-brand-200) !important; }
|
36
|
+
.border-brand-300 { border-color: var(--color-brand-300) !important; }
|
37
|
+
.border-brand-400 { border-color: var(--color-brand-400) !important; }
|
38
|
+
.border-brand-500 { border-color: var(--color-brand-500) !important; }
|
39
|
+
.border-brand-600 { border-color: var(--color-brand-600) !important; }
|
40
|
+
.border-brand-700 { border-color: var(--color-brand-700) !important; }
|
41
|
+
.border-brand-800 { border-color: var(--color-brand-800) !important; }
|
42
|
+
.border-brand-900 { border-color: var(--color-brand-900) !important; }
|
43
|
+
.border-brand-950 { border-color: var(--color-brand-950) !important; }
|
44
|
+
|
45
|
+
/* Hover states */
|
46
|
+
.hover\:bg-brand-50:hover { background-color: var(--color-brand-50) !important; }
|
47
|
+
.hover\:bg-brand-100:hover { background-color: var(--color-brand-100) !important; }
|
48
|
+
.hover\:bg-brand-200:hover { background-color: var(--color-brand-200) !important; }
|
49
|
+
.hover\:bg-brand-300:hover { background-color: var(--color-brand-300) !important; }
|
50
|
+
.hover\:bg-brand-400:hover { background-color: var(--color-brand-400) !important; }
|
51
|
+
.hover\:bg-brand-500:hover { background-color: var(--color-brand-500) !important; }
|
52
|
+
.hover\:bg-brand-600:hover { background-color: var(--color-brand-600) !important; }
|
53
|
+
.hover\:bg-brand-700:hover { background-color: var(--color-brand-700) !important; }
|
54
|
+
.hover\:bg-brand-800:hover { background-color: var(--color-brand-800) !important; }
|
55
|
+
.hover\:bg-brand-900:hover { background-color: var(--color-brand-900) !important; }
|
56
|
+
.hover\:bg-brand-950:hover { background-color: var(--color-brand-950) !important; }
|
57
|
+
|
58
|
+
.hover\:text-brand-50:hover { color: var(--color-brand-50) !important; }
|
59
|
+
.hover\:text-brand-100:hover { color: var(--color-brand-100) !important; }
|
60
|
+
.hover\:text-brand-200:hover { color: var(--color-brand-200) !important; }
|
61
|
+
.hover\:text-brand-300:hover { color: var(--color-brand-300) !important; }
|
62
|
+
.hover\:text-brand-400:hover { color: var(--color-brand-400) !important; }
|
63
|
+
.hover\:text-brand-500:hover { color: var(--color-brand-500) !important; }
|
64
|
+
.hover\:text-brand-600:hover { color: var(--color-brand-600) !important; }
|
65
|
+
.hover\:text-brand-700:hover { color: var(--color-brand-700) !important; }
|
66
|
+
.hover\:text-brand-800:hover { color: var(--color-brand-800) !important; }
|
67
|
+
.hover\:text-brand-900:hover { color: var(--color-brand-900) !important; }
|
68
|
+
.hover\:text-brand-950:hover { color: var(--color-brand-950) !important; }
|
69
|
+
|
70
|
+
.hover\:border-brand-50:hover { border-color: var(--color-brand-50) !important; }
|
71
|
+
.hover\:border-brand-100:hover { border-color: var(--color-brand-100) !important; }
|
72
|
+
.hover\:border-brand-200:hover { border-color: var(--color-brand-200) !important; }
|
73
|
+
.hover\:border-brand-300:hover { border-color: var(--color-brand-300) !important; }
|
74
|
+
.hover\:border-brand-400:hover { border-color: var(--color-brand-400) !important; }
|
75
|
+
.hover\:border-brand-500:hover { border-color: var(--color-brand-500) !important; }
|
76
|
+
.hover\:border-brand-600:hover { border-color: var(--color-brand-600) !important; }
|
77
|
+
.hover\:border-brand-700:hover { border-color: var(--color-brand-700) !important; }
|
78
|
+
.hover\:border-brand-800:hover { border-color: var(--color-brand-800) !important; }
|
79
|
+
.hover\:border-brand-900:hover { border-color: var(--color-brand-900) !important; }
|
80
|
+
.hover\:border-brand-950:hover { border-color: var(--color-brand-950) !important; }
|
81
|
+
|
82
|
+
/* Focus states */
|
83
|
+
.focus\:ring-brand-50:focus { --tw-ring-color: var(--color-brand-50) !important; }
|
84
|
+
.focus\:ring-brand-100:focus { --tw-ring-color: var(--color-brand-100) !important; }
|
85
|
+
.focus\:ring-brand-200:focus { --tw-ring-color: var(--color-brand-200) !important; }
|
86
|
+
.focus\:ring-brand-300:focus { --tw-ring-color: var(--color-brand-300) !important; }
|
87
|
+
.focus\:ring-brand-400:focus { --tw-ring-color: var(--color-brand-400) !important; }
|
88
|
+
.focus\:ring-brand-500:focus { --tw-ring-color: var(--color-brand-500) !important; }
|
89
|
+
.focus\:ring-brand-600:focus { --tw-ring-color: var(--color-brand-600) !important; }
|
90
|
+
.focus\:ring-brand-700:focus { --tw-ring-color: var(--color-brand-700) !important; }
|
91
|
+
.focus\:ring-brand-800:focus { --tw-ring-color: var(--color-brand-800) !important; }
|
92
|
+
.focus\:ring-brand-900:focus { --tw-ring-color: var(--color-brand-900) !important; }
|
93
|
+
.focus\:ring-brand-950:focus { --tw-ring-color: var(--color-brand-950) !important; }
|
94
|
+
|
95
|
+
.focus\:border-brand-50:focus { border-color: var(--color-brand-50) !important; }
|
96
|
+
.focus\:border-brand-100:focus { border-color: var(--color-brand-100) !important; }
|
97
|
+
.focus\:border-brand-200:focus { border-color: var(--color-brand-200) !important; }
|
98
|
+
.focus\:border-brand-300:focus { border-color: var(--color-brand-300) !important; }
|
99
|
+
.focus\:border-brand-400:focus { border-color: var(--color-brand-400) !important; }
|
100
|
+
.focus\:border-brand-500:focus { border-color: var(--color-brand-500) !important; }
|
101
|
+
.focus\:border-brand-600:focus { border-color: var(--color-brand-600) !important; }
|
102
|
+
.focus\:border-brand-700:focus { border-color: var(--color-brand-700) !important; }
|
103
|
+
.focus\:border-brand-800:focus { border-color: var(--color-brand-800) !important; }
|
104
|
+
.focus\:border-brand-900:focus { border-color: var(--color-brand-900) !important; }
|
105
|
+
.focus\:border-brand-950:focus { border-color: var(--color-brand-950) !important; }
|
106
|
+
|
107
|
+
|
108
|
+
[type='text']:focus,
|
109
|
+
[type='email']:focus,
|
110
|
+
[type='url']:focus,
|
111
|
+
[type='password']:focus,
|
112
|
+
[type='number']:focus,
|
113
|
+
[type='date']:focus,
|
114
|
+
[type='datetime-local']:focus,
|
115
|
+
[type='month']:focus,
|
116
|
+
[type='search']:focus,
|
117
|
+
[type='tel']:focus,
|
118
|
+
[type='time']:focus,
|
119
|
+
[type='week']:focus,
|
120
|
+
[multiple]:focus,
|
121
|
+
textarea:focus,
|
122
|
+
select:focus {
|
123
|
+
--tw-ring-color: var(--color-brand-500) !important;
|
124
|
+
--tw-ring-opacity: 0.5 !important;
|
125
|
+
border-color: var(--color-brand-500) !important;
|
126
|
+
outline: 2px solid transparent;
|
127
|
+
outline-offset: 2px;
|
128
|
+
}
|
129
|
+
|
130
|
+
/* Override focus styles for checkboxes and radio buttons */
|
131
|
+
[type='checkbox']:focus,
|
132
|
+
[type='radio']:focus {
|
133
|
+
--tw-ring-color: var(--color-brand-500) !important;
|
134
|
+
--tw-ring-opacity: 0.5 !important;
|
135
|
+
outline: 2px solid transparent;
|
136
|
+
outline-offset: 2px;
|
137
|
+
}
|
138
|
+
|
139
|
+
/* Override checked styles for checkboxes and radio buttons */
|
140
|
+
[type='checkbox']:checked,
|
141
|
+
[type='radio']:checked {
|
142
|
+
background-color: var(--color-brand-600) !important;
|
143
|
+
border-color: var(--color-brand-600) !important;
|
144
|
+
}
|
145
|
+
|
146
|
+
/* Override focus-visible styles for all form elements */
|
147
|
+
[type='text']:focus-visible,
|
148
|
+
[type='email']:focus-visible,
|
149
|
+
[type='url']:focus-visible,
|
150
|
+
[type='password']:focus-visible,
|
151
|
+
[type='number']:focus-visible,
|
152
|
+
[type='date']:focus-visible,
|
153
|
+
[type='datetime-local']:focus-visible,
|
154
|
+
[type='month']:focus-visible,
|
155
|
+
[type='search']:focus-visible,
|
156
|
+
[type='tel']:focus-visible,
|
157
|
+
[type='time']:focus-visible,
|
158
|
+
[type='week']:focus-visible,
|
159
|
+
[multiple]:focus-visible,
|
160
|
+
textarea:focus-visible,
|
161
|
+
select:focus-visible,
|
162
|
+
[type='checkbox']:focus-visible,
|
163
|
+
[type='radio']:focus-visible {
|
164
|
+
--tw-ring-color: var(--color-brand-500) !important;
|
165
|
+
--tw-ring-opacity: 0.5 !important;
|
166
|
+
outline: 2px solid transparent;
|
167
|
+
outline-offset: 2px;
|
168
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RivetCms
|
2
|
+
module Api
|
3
|
+
class DocsController < RivetCms::ApplicationController
|
4
|
+
layout false
|
5
|
+
|
6
|
+
def show
|
7
|
+
@schema = RivetCms::OpenApiGenerator.generate
|
8
|
+
|
9
|
+
respond_to do |format|
|
10
|
+
format.html
|
11
|
+
format.json { render json: @schema }
|
12
|
+
format.yaml { render plain: @schema.to_yaml, content_type: 'application/x-yaml' }
|
13
|
+
rescue StandardError => e
|
14
|
+
Rails.logger.error "Error generating API documentation: #{e.message}"
|
15
|
+
render_error_response(format)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def render_error_response(format)
|
22
|
+
error_schema = {
|
23
|
+
openapi: "3.0.1",
|
24
|
+
info: {
|
25
|
+
title: "API Documentation Temporarily Unavailable",
|
26
|
+
version: "1.0.0",
|
27
|
+
description: "There was an error generating the API documentation. Please try again later."
|
28
|
+
},
|
29
|
+
paths: {}
|
30
|
+
}
|
31
|
+
|
32
|
+
format.html { render :error, status: :internal_server_error }
|
33
|
+
format.json { render json: error_schema, status: :internal_server_error }
|
34
|
+
format.yaml { render plain: error_schema.to_yaml, status: :internal_server_error }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module RivetCms
|
2
|
+
class ContentTypesController < ApplicationController
|
3
|
+
before_action :set_content_type, only: [:show, :edit, :update, :destroy]
|
4
|
+
|
5
|
+
def index
|
6
|
+
@content_types = ContentType.order(:name)
|
7
|
+
end
|
8
|
+
|
9
|
+
def show
|
10
|
+
@contents = @content_type.contents.order(created_at: :desc).limit(10)
|
11
|
+
end
|
12
|
+
|
13
|
+
def new
|
14
|
+
@content_type = ContentType.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def edit
|
18
|
+
end
|
19
|
+
|
20
|
+
def create
|
21
|
+
@content_type = ContentType.new(content_type_params)
|
22
|
+
|
23
|
+
if @content_type.save
|
24
|
+
redirect_to @content_type, notice: 'Content type was successfully created.'
|
25
|
+
else
|
26
|
+
respond_to do |format|
|
27
|
+
format.html { render :new, status: :unprocessable_entity }
|
28
|
+
format.turbo_stream {
|
29
|
+
render turbo_stream: turbo_stream.update("content_type_form",
|
30
|
+
partial: "form",
|
31
|
+
locals: { content_type: @content_type }
|
32
|
+
)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def update
|
39
|
+
if @content_type.update(content_type_params)
|
40
|
+
redirect_to @content_type, notice: 'Content type was successfully updated.'
|
41
|
+
else
|
42
|
+
render :edit
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def destroy
|
47
|
+
@content_type.destroy
|
48
|
+
redirect_to content_types_url, notice: 'Content type was successfully destroyed.'
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def set_content_type
|
54
|
+
@content_type = ContentType.find(params[:id])
|
55
|
+
end
|
56
|
+
|
57
|
+
def content_type_params
|
58
|
+
params.require(:content_type).permit(:name, :slug, :description, :is_single)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module RivetCms
|
2
|
+
class DashboardController < ApplicationController
|
3
|
+
def index
|
4
|
+
@content_types = ContentType.order(:name)
|
5
|
+
@recent_contents = Content.order(updated_at: :desc).limit(5)
|
6
|
+
@components = Component.all
|
7
|
+
@content_type = ContentType.new
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module RivetCms
|
2
|
+
class FieldsController < ApplicationController
|
3
|
+
before_action :set_content_type
|
4
|
+
before_action :set_field, only: [:edit, :update, :destroy, :update_width]
|
5
|
+
|
6
|
+
# GET /content_types/:content_type_id/fields
|
7
|
+
def index
|
8
|
+
@fields = @content_type.fields.includes(:component).order(:position)
|
9
|
+
@field = @content_type.fields.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# GET /content_types/:content_type_id/fields/new
|
13
|
+
def new
|
14
|
+
@field = @content_type.fields.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# POST /content_types/:content_type_id/fields
|
18
|
+
def create
|
19
|
+
@field = @content_type.fields.new(field_params)
|
20
|
+
|
21
|
+
if @field.save
|
22
|
+
redirect_to content_type_fields_path(@content_type), notice: "Field was successfully created."
|
23
|
+
else
|
24
|
+
respond_to do |format|
|
25
|
+
format.html { render :new, status: :unprocessable_entity }
|
26
|
+
format.turbo_stream {
|
27
|
+
render turbo_stream: turbo_stream.update("field_form",
|
28
|
+
partial: "form",
|
29
|
+
locals: { content_type: @content_type, field: @field }
|
30
|
+
)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# GET /content_types/:content_type_id/fields/:id/edit
|
37
|
+
def edit
|
38
|
+
end
|
39
|
+
|
40
|
+
# PATCH/PUT /content_types/:content_type_id/fields/:id
|
41
|
+
def update
|
42
|
+
if @field.update(field_params)
|
43
|
+
redirect_to content_type_fields_path(@content_type), notice: "Field was successfully updated."
|
44
|
+
else
|
45
|
+
respond_to do |format|
|
46
|
+
format.html { render :edit, status: :unprocessable_entity }
|
47
|
+
format.turbo_stream {
|
48
|
+
render turbo_stream: turbo_stream.update("field_form",
|
49
|
+
partial: "form",
|
50
|
+
locals: { content_type: @content_type, field: @field }
|
51
|
+
)
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# DELETE /content_types/:content_type_id/fields/:id
|
58
|
+
def destroy
|
59
|
+
@field.destroy
|
60
|
+
redirect_to content_type_fields_path(@content_type), notice: "Field was successfully deleted."
|
61
|
+
end
|
62
|
+
|
63
|
+
# POST /content_types/:content_type_id/fields/update_positions
|
64
|
+
def update_positions
|
65
|
+
if params[:positions].present?
|
66
|
+
ActiveRecord::Base.transaction do
|
67
|
+
params[:positions].each do |position_data|
|
68
|
+
field = @content_type.fields.find_by_prefix_id(position_data[:id])
|
69
|
+
if field
|
70
|
+
field.update!(
|
71
|
+
position: position_data[:position],
|
72
|
+
row_group: position_data[:row_group]
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
render json: { success: true }
|
79
|
+
else
|
80
|
+
render json: { error: "No positions data provided" }, status: :bad_request
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# PATCH /content_types/:content_type_id/fields/:id/update_width
|
85
|
+
def update_width
|
86
|
+
width_value = params[:width]
|
87
|
+
if width_value.present? && RivetCms::Field::WIDTHS.include?(width_value)
|
88
|
+
result = @field.update_columns(width: width_value, row_group: nil)
|
89
|
+
render json: { success: true, field: { id: @field.to_param, width: @field.width } }
|
90
|
+
else
|
91
|
+
render json: { error: "Invalid width value" }, status: :unprocessable_entity
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def set_content_type
|
98
|
+
@content_type = ContentType.find(params[:content_type_id])
|
99
|
+
end
|
100
|
+
|
101
|
+
def set_field
|
102
|
+
@field = @content_type.fields.find_by_prefix_id(params[:id])
|
103
|
+
end
|
104
|
+
|
105
|
+
def field_params
|
106
|
+
params.require(:field).permit(:name, :field_type, :required, :component_id, :description, :width, options: {})
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module RivetCms
|
2
|
+
module BrandColorHelper
|
3
|
+
# Generate CSS for brand colors based on the primary color
|
4
|
+
def brand_color_css
|
5
|
+
base_color = "#ff7043"
|
6
|
+
|
7
|
+
# Generate CSS variables for all the shades
|
8
|
+
<<~CSS
|
9
|
+
<style id="brand-colors">
|
10
|
+
:root {
|
11
|
+
--color-brand-50: #{lighten_color(base_color, 0.85)};
|
12
|
+
--color-brand-100: #{lighten_color(base_color, 0.7)};
|
13
|
+
--color-brand-200: #{lighten_color(base_color, 0.55)};
|
14
|
+
--color-brand-300: #{lighten_color(base_color, 0.4)};
|
15
|
+
--color-brand-400: #{lighten_color(base_color, 0.2)};
|
16
|
+
--color-brand-500: #{lighten_color(base_color, 0.1)};
|
17
|
+
--color-brand-600: #{base_color};
|
18
|
+
--color-brand-700: #{darken_color(base_color, 0.1)};
|
19
|
+
--color-brand-800: #{darken_color(base_color, 0.2)};
|
20
|
+
--color-brand-900: #{darken_color(base_color, 0.3)};
|
21
|
+
--color-brand-950: #{darken_color(base_color, 0.4)};
|
22
|
+
}
|
23
|
+
</style>
|
24
|
+
CSS
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Simple color manipulation helpers
|
30
|
+
def lighten_color(hex_color, amount)
|
31
|
+
manipulate_color(hex_color) do |r, g, b|
|
32
|
+
[
|
33
|
+
[(r + (255 - r) * amount).round, 255].min,
|
34
|
+
[(g + (255 - g) * amount).round, 255].min,
|
35
|
+
[(b + (255 - b) * amount).round, 255].min
|
36
|
+
]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def darken_color(hex_color, amount)
|
41
|
+
manipulate_color(hex_color) do |r, g, b|
|
42
|
+
[
|
43
|
+
[(r * (1 - amount)).round, 0].max,
|
44
|
+
[(g * (1 - amount)).round, 0].max,
|
45
|
+
[(b * (1 - amount)).round, 0].max
|
46
|
+
]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def manipulate_color(hex_color)
|
51
|
+
hex_color = hex_color.gsub('#', '')
|
52
|
+
|
53
|
+
# Convert to RGB
|
54
|
+
if hex_color.length == 3
|
55
|
+
r = hex_color[0].to_i(16) * 17
|
56
|
+
g = hex_color[1].to_i(16) * 17
|
57
|
+
b = hex_color[2].to_i(16) * 17
|
58
|
+
else
|
59
|
+
r = hex_color[0..1].to_i(16)
|
60
|
+
g = hex_color[2..3].to_i(16)
|
61
|
+
b = hex_color[4..5].to_i(16)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Apply transformation
|
65
|
+
new_r, new_g, new_b = yield(r, g, b)
|
66
|
+
|
67
|
+
# Convert back to hex
|
68
|
+
"#%02x%02x%02x" % [new_r, new_g, new_b]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module RivetCms
|
2
|
+
module FlashHelper
|
3
|
+
def flash_class_for(type)
|
4
|
+
case type.to_sym
|
5
|
+
when :notice, :success
|
6
|
+
"bg-green-50"
|
7
|
+
when :error, :alert
|
8
|
+
"bg-red-50"
|
9
|
+
when :warning
|
10
|
+
"bg-yellow-50"
|
11
|
+
else
|
12
|
+
"bg-gray-50"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def flash_icon_for(type)
|
17
|
+
case type.to_sym
|
18
|
+
when :notice, :success
|
19
|
+
content_tag(:svg, class: "h-5 w-5 text-green-400", viewBox: "0 0 20 20", fill: "currentColor") do
|
20
|
+
content_tag(:path, nil, fill_rule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z", clip_rule: "evenodd")
|
21
|
+
end
|
22
|
+
when :error, :alert
|
23
|
+
content_tag(:svg, class: "h-5 w-5 text-red-400", viewBox: "0 0 20 20", fill: "currentColor") do
|
24
|
+
content_tag(:path, nil, fill_rule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z", clip_rule: "evenodd")
|
25
|
+
end
|
26
|
+
when :warning
|
27
|
+
content_tag(:svg, class: "h-5 w-5 text-yellow-400", viewBox: "0 0 20 20", fill: "currentColor") do
|
28
|
+
content_tag(:path, nil, fill_rule: "evenodd", d: "M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z", clip_rule: "evenodd")
|
29
|
+
end
|
30
|
+
else
|
31
|
+
content_tag(:svg, class: "h-5 w-5 text-gray-400", viewBox: "0 0 20 20", fill: "currentColor") do
|
32
|
+
content_tag(:path, nil, fill_rule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z", clip_rule: "evenodd")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module RivetCms
|
2
|
+
module SignOutHelper
|
3
|
+
# Returns the sign-out path for the application.
|
4
|
+
# Uses RivetCmsAuth’s auth_sign_out_path if available, otherwise falls back to the configured path or root.
|
5
|
+
def rivet_sign_out_path
|
6
|
+
config = RivetCms.configuration
|
7
|
+
return config.sign_out_path.call if config.sign_out_path.respond_to?(:call)
|
8
|
+
config.sign_out_path || "/"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
import pluralize from "pluralize"
|
3
|
+
|
4
|
+
export default class extends Controller {
|
5
|
+
static targets = ["name", "slug", "isSingle"]
|
6
|
+
|
7
|
+
connect() {
|
8
|
+
this.nameTarget.addEventListener("input", this.updateSlug.bind(this))
|
9
|
+
this.isSingleTarget.addEventListener("change", this.updateSlug.bind(this))
|
10
|
+
this.lastGeneratedSlug = this.slugTarget.value
|
11
|
+
}
|
12
|
+
|
13
|
+
updateSlug() {
|
14
|
+
const name = this.nameTarget.value.trim()
|
15
|
+
if (!name) {
|
16
|
+
this.slugTarget.value = ""
|
17
|
+
return
|
18
|
+
}
|
19
|
+
|
20
|
+
let slug = this.generateSlug(name)
|
21
|
+
if (!slug) {
|
22
|
+
this.slugTarget.value = ""
|
23
|
+
return
|
24
|
+
}
|
25
|
+
|
26
|
+
const isSingle = this.isSingleTarget.checked
|
27
|
+
if (isSingle && pluralize.isPlural(slug)) {
|
28
|
+
slug = pluralize.singular(slug)
|
29
|
+
} else if (!isSingle && !pluralize.isPlural(slug)) {
|
30
|
+
slug = pluralize(slug)
|
31
|
+
}
|
32
|
+
|
33
|
+
if (slug !== this.lastGeneratedSlug) {
|
34
|
+
this.slugTarget.value = slug
|
35
|
+
this.lastGeneratedSlug = slug
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
generateSlug(text) {
|
40
|
+
return text
|
41
|
+
.toLowerCase()
|
42
|
+
.replace(/[\s_]+/g, "-") // Spaces or underscores to hyphen
|
43
|
+
.replace(/[^a-z0-9-]/g, "") // Keep letters, numbers, hyphens
|
44
|
+
.replace(/-+/g, "-") // Collapse hyphens
|
45
|
+
.replace(/^-|-$/g, "") // Trim leading/trailing hyphens
|
46
|
+
.trim()
|
47
|
+
}
|
48
|
+
|
49
|
+
disconnect() {
|
50
|
+
this.nameTarget.removeEventListener("input", this.updateSlug.bind(this))
|
51
|
+
this.isSingleTarget.removeEventListener("change", this.updateSlug.bind(this))
|
52
|
+
}
|
53
|
+
}
|