trek 2.2.0 → 2.3.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/.slim-lint.yml +0 -3
- data/AGENTS.md +8 -1
- data/CHANGELOG.md +62 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +20 -3
- data/Makefile +11 -9
- data/README.md +1 -1
- data/app/assets/icons/trek/chevron-down.svg +1 -0
- data/app/assets/icons/trek/chevron-up.svg +1 -0
- data/app/assets/icons/trek/external.svg +1 -0
- data/app/assets/icons/trek/theme-auto.svg +1 -0
- data/app/assets/icons/trek/theme-dark.svg +1 -0
- data/app/assets/icons/trek/theme-light.svg +1 -0
- data/app/assets/stylesheets/trek/base.css +13 -0
- data/app/components/trek/button_component/button_component.css +9 -0
- data/app/components/trek/button_component.rb +13 -1
- data/app/components/trek/dashboard/tile_component/tile_component.html.slim +1 -1
- data/app/components/trek/dashboard/tile_component.rb +14 -0
- data/app/components/trek/dialog_component/dialog_component.html.slim +6 -2
- data/app/components/trek/dialog_component/dialog_component.js +62 -2
- data/app/components/trek/dropdown_component/dropdown_component.css +66 -0
- data/app/components/trek/dropdown_component/dropdown_component.html.slim +31 -0
- data/app/components/trek/dropdown_component/dropdown_component.js +114 -0
- data/app/components/trek/dropdown_component.rb +29 -0
- data/app/components/trek/form/group_component/group_component.css +34 -0
- data/app/components/trek/header_component/header_component.css +10 -4
- data/app/components/trek/header_component/header_component.html.slim +17 -10
- data/app/components/trek/header_component/header_component.js +29 -5
- data/app/components/trek/header_component.rb +20 -3
- data/app/components/trek/menu_component/menu_component.css +6 -0
- data/app/components/trek/menu_component.rb +29 -4
- data/app/components/trek/toaster/toast_component.rb +1 -1
- data/app/controllers/concerns/trek/authorization.rb +12 -0
- data/app/helpers/trek/page_path_helper.rb +4 -3
- data/app/helpers/trek/theme_helper.rb +16 -1
- data/app/models/concerns/trek/phonable.rb +15 -3
- data/app/models/concerns/trek/sectionable.rb +5 -0
- data/app/models/concerns/trek/users/validations.rb +1 -1
- data/app/uploaders/audio_uploader.rb +11 -0
- data/app/uploaders/record_image_uploader.rb +3 -0
- data/config/locales/trek.en.yml +3 -2
- data/config/locales/trek.fr.yml +1 -0
- data/docs/customize.md +18 -2
- data/docs/deploy.md +7 -2
- data/docs/getting-started.md +1 -1
- data/docs/index.md +1 -1
- data/docs/reference/components.md +15 -0
- data/docs/reference/concerns/phonable.md +27 -1
- data/docs/reference/environment-variables.md +2 -0
- data/docs/reference/forms.md +2 -0
- data/docs/reference/generators/install.md +4 -1
- data/docs/reference/icons.md +1 -1
- data/docs/reference/models/tag.md +1 -1
- data/docs/reference/models/user.md +4 -2
- data/docs/reference/uploaders.md +31 -6
- data/docs/what-is-trek.md +1 -1
- data/lib/generators/trek/install/admin/routes_generator.rb +7 -1
- data/lib/generators/trek/install/appsignal_generator.rb +102 -0
- data/lib/generators/trek/install/model_specs_generator.rb +64 -0
- data/lib/generators/trek/install/postmark_generator.rb +3 -0
- data/lib/generators/trek/install/ruby_generator.rb +1 -1
- data/lib/generators/trek/install/scalingo_generator.rb +1 -1
- data/lib/generators/trek/install/uploaders_generator.rb +2 -0
- data/lib/generators/trek/install_generator.rb +5 -0
- data/lib/generators/trek/taxonomies_generator.rb +7 -0
- data/lib/generators/trek/templates/.gitlab-ci.yml +1 -1
- data/lib/generators/trek/templates/config/appsignal.rb.tt +25 -0
- data/lib/generators/trek/templates/controllers/admin/uploads_controller.rb +4 -0
- data/lib/generators/trek/templates/controllers/admin/users_controller.rb +4 -1
- data/lib/generators/trek/templates/controllers/concerns/appsignal_context.rb +37 -0
- data/lib/generators/trek/templates/db/seeds/setup/fragments.rb +3 -0
- data/lib/generators/trek/templates/db/seeds/setup/users.rb +6 -0
- data/lib/generators/trek/templates/db/seeds/test/cases/page_tree.rb +5 -0
- data/lib/generators/trek/templates/initializers/appsignal_context.rb +5 -0
- data/lib/generators/trek/templates/locales/scaffold/model.en.yml.tt +1 -1
- data/lib/generators/trek/templates/migrations/fragments.rb.erb +1 -1
- data/lib/generators/trek/templates/migrations/menu_nodes.rb.erb +0 -1
- data/lib/generators/trek/templates/migrations/tag_categories.rb.erb +1 -1
- data/lib/generators/trek/templates/migrations/taggings.rb.erb +1 -1
- data/lib/generators/trek/templates/migrations/users.rb.erb +2 -1
- data/lib/generators/trek/templates/models/tag.rb +6 -1
- data/lib/generators/trek/templates/policies/admin/tag_policy.rb +1 -1
- data/lib/generators/trek/templates/policies/external_link_policy.rb +1 -1
- data/lib/generators/trek/templates/spec/fixtures/files/image.png +0 -0
- data/lib/generators/trek/templates/spec/fixtures/files/sound.mp3 +0 -0
- data/lib/generators/trek/templates/spec/models/page_spec.rb +24 -0
- data/lib/generators/trek/templates/spec/models/user_spec.rb +76 -0
- data/lib/generators/trek/templates/spec/policies/admin/external_link_policy_spec.rb +17 -0
- data/lib/generators/trek/templates/spec/policies/admin/fragment_policy_spec.rb +33 -0
- data/lib/generators/trek/templates/spec/policies/admin/page_policy_spec.rb +39 -0
- data/lib/generators/trek/templates/spec/policies/admin/preview_policy_spec.rb +9 -0
- data/lib/generators/trek/templates/spec/policies/admin/tag_category_policy_spec.rb +31 -0
- data/lib/generators/trek/templates/spec/policies/admin/tag_policy_spec.rb +31 -0
- data/lib/generators/trek/templates/spec/policies/admin/user_policy_spec.rb +58 -0
- data/lib/generators/trek/templates/spec/requests/admin/fragments_spec.rb +50 -0
- data/lib/generators/trek/templates/spec/requests/admin/pages_spec.rb +47 -0
- data/lib/generators/trek/templates/spec/requests/admin/uploads_spec.rb +22 -0
- data/lib/generators/trek/templates/spec/requests/admin/user_password_resets_spec.rb +71 -0
- data/lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb +45 -0
- data/lib/generators/trek/templates/spec/requests/admin/users_spec.rb +99 -0
- data/lib/generators/trek/templates/views/admin/user_password_resets/edit.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin/user_password_resets/new.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin/user_sessions/new.html.slim +2 -2
- data/lib/generators/trek/templates/views/admin/users/theme.turbo_stream.slim +2 -0
- data/lib/generators/trek/upgrade/v2_generator.rb +3 -0
- data/lib/tasks/trek/attachments.rake +18 -0
- data/lib/trek/cached_attachments.rb +53 -0
- data/lib/trek/saved_attachments_reload.rb +44 -0
- data/lib/trek/version.rb +1 -1
- data/lib/trek.rb +3 -0
- data/package.json +1 -1
- data/postcss.config.js +36 -0
- data/trek.gemspec +5 -0
- metadata +70 -3
- data/app/assets/icons/trek/theme.svg +0 -11
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require "rails_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe "Admin password resets", type: :request do
|
|
4
|
+
let(:editor) { users.create(role: :editor) }
|
|
5
|
+
|
|
6
|
+
it "renders the reset request form" do
|
|
7
|
+
get new_admin_user_password_reset_path
|
|
8
|
+
|
|
9
|
+
expect(response).to have_http_status(:ok)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "emails a reset link to a privileged user" do
|
|
13
|
+
expect { post admin_user_password_resets_path, params: { email: editor.email } }
|
|
14
|
+
.to have_enqueued_mail(Admin::PasswordsMailer, :reset)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "sends nothing for an unknown email" do
|
|
18
|
+
expect { post admin_user_password_resets_path, params: { email: "nobody@example.com" } }
|
|
19
|
+
.not_to have_enqueued_mail
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "answers an unknown email as a known one, so it can't be used to find accounts" do
|
|
23
|
+
post admin_user_password_resets_path, params: { email: "nobody@example.com" }
|
|
24
|
+
|
|
25
|
+
expect(response).to redirect_to(new_admin_user_session_path)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "renders the new password form from a reset link" do
|
|
29
|
+
get edit_admin_user_password_reset_path(editor.password_reset_token)
|
|
30
|
+
|
|
31
|
+
expect(response).to have_http_status(:ok)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "renders the new password form from an invitation link" do
|
|
35
|
+
get edit_admin_user_password_reset_path(editor.generate_token_for(:invite))
|
|
36
|
+
|
|
37
|
+
expect(response).to have_http_status(:ok)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "sends an invalid link back to the reset request form" do
|
|
41
|
+
get edit_admin_user_password_reset_path("not-a-token")
|
|
42
|
+
|
|
43
|
+
expect(response).to redirect_to(new_admin_user_password_reset_path)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "sets the new password" do
|
|
47
|
+
patch admin_user_password_reset_path(editor.password_reset_token), params: { password: "a brand new password" }
|
|
48
|
+
|
|
49
|
+
expect(editor.reload.authenticate("a brand new password")).to be_truthy
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "signs the user out everywhere" do
|
|
53
|
+
post admin_user_session_path, params: { email: editor.email, password: users.password }
|
|
54
|
+
patch admin_user_password_reset_path(editor.password_reset_token), params: { password: "a brand new password" }
|
|
55
|
+
|
|
56
|
+
expect(editor.user_sessions).to be_empty
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "sends a password shorter than 8 characters back to the form" do
|
|
60
|
+
token = editor.password_reset_token
|
|
61
|
+
patch admin_user_password_reset_path(token), params: { password: "short" }
|
|
62
|
+
|
|
63
|
+
expect(response).to redirect_to(edit_admin_user_password_reset_path(token))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "keeps the old password when the new one is too short" do
|
|
67
|
+
patch admin_user_password_reset_path(editor.password_reset_token), params: { password: "short" }
|
|
68
|
+
|
|
69
|
+
expect(editor.reload.authenticate(users.password)).to be_truthy
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require "rails_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe "Admin sign-in", type: :request do
|
|
4
|
+
let(:editor) { users.create(role: :editor) }
|
|
5
|
+
|
|
6
|
+
it "renders the sign-in form" do
|
|
7
|
+
get new_admin_user_session_path
|
|
8
|
+
|
|
9
|
+
expect(response).to have_http_status(:ok)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "signs a privileged user in and opens the dashboard" do
|
|
13
|
+
post admin_user_session_path, params: { email: editor.email, password: users.password }
|
|
14
|
+
follow_redirect!
|
|
15
|
+
|
|
16
|
+
expect(response).to have_http_status(:ok)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "refuses a wrong password" do
|
|
20
|
+
post admin_user_session_path, params: { email: editor.email, password: "not the password" }
|
|
21
|
+
|
|
22
|
+
expect(response).to redirect_to(new_admin_user_session_path)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "refuses a user who is not privileged" do
|
|
26
|
+
reader = users.create(role: :reader)
|
|
27
|
+
post admin_user_session_path, params: { email: reader.email, password: users.password }
|
|
28
|
+
|
|
29
|
+
expect(response).to redirect_to(new_admin_user_session_path)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "sends a signed-out visitor to the sign-in form" do
|
|
33
|
+
get admin_root_path
|
|
34
|
+
|
|
35
|
+
expect(response).to redirect_to(new_admin_user_session_path)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "signs out" do
|
|
39
|
+
post admin_user_session_path, params: { email: editor.email, password: users.password }
|
|
40
|
+
delete admin_user_session_path
|
|
41
|
+
get admin_root_path
|
|
42
|
+
|
|
43
|
+
expect(response).to redirect_to(new_admin_user_session_path)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require "rails_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe "Admin users", type: :request do
|
|
4
|
+
let(:admin) { users.create(role: :admin, name: "Ada") }
|
|
5
|
+
let(:editor) { users.create(role: :editor, name: "Grace") }
|
|
6
|
+
|
|
7
|
+
def sign_in(user)
|
|
8
|
+
post admin_user_session_path, params: { email: user.email, password: users.password }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
context "when signed in as an admin" do
|
|
12
|
+
before { sign_in(admin) }
|
|
13
|
+
|
|
14
|
+
it "lists the privileged users" do
|
|
15
|
+
editor
|
|
16
|
+
get admin_users_path
|
|
17
|
+
|
|
18
|
+
expect(response.body).to include("Grace")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "leaves the other users out" do
|
|
22
|
+
users.create(role: :reader, name: "Linus")
|
|
23
|
+
get admin_users_path
|
|
24
|
+
|
|
25
|
+
expect(response.body).not_to include("Linus")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "renders the new user form" do
|
|
29
|
+
get new_admin_user_path
|
|
30
|
+
|
|
31
|
+
expect(response).to have_http_status(:ok)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "creates a user and sends them an invitation" do
|
|
35
|
+
params = { user: { email: "new@example.com", role: "editor", send_invite: "1" } }
|
|
36
|
+
|
|
37
|
+
expect { post admin_users_path, params: }
|
|
38
|
+
.to change(User, :count).by(1).and have_enqueued_mail(AdminMailer, :invite)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "changes a user's role" do
|
|
42
|
+
patch admin_user_path(editor), params: { user: { role: "admin" } }
|
|
43
|
+
|
|
44
|
+
expect(editor.reload.role).to eq("admin")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "deletes a user" do
|
|
48
|
+
editor
|
|
49
|
+
|
|
50
|
+
expect { delete admin_user_path(editor) }.to change(User, :count).by(-1)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "when signed in as an editor" do
|
|
55
|
+
before { sign_in(editor) }
|
|
56
|
+
|
|
57
|
+
it "is sent back to the dashboard from the users list" do
|
|
58
|
+
get admin_users_path
|
|
59
|
+
|
|
60
|
+
expect(response).to redirect_to(admin_root_path)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "is sent back to the dashboard from another user's profile" do
|
|
64
|
+
get admin_user_path(admin)
|
|
65
|
+
|
|
66
|
+
expect(response).to redirect_to(admin_root_path)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "is told why they were sent back" do
|
|
70
|
+
get admin_users_path
|
|
71
|
+
|
|
72
|
+
expect(flash[:alert]).to eq(I18n.t!("notices.unauthorized"))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "gets a 403 for a JSON request they aren't allowed" do
|
|
76
|
+
get admin_users_path(format: :json)
|
|
77
|
+
|
|
78
|
+
expect(response).to have_http_status(:forbidden)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "edits their own profile" do
|
|
82
|
+
patch admin_user_path(editor), params: { user: { name: "Grace H." } }
|
|
83
|
+
|
|
84
|
+
expect(editor.reload.name).to eq("Grace H.")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "cannot change their own role" do
|
|
88
|
+
patch admin_user_path(editor), params: { user: { role: "admin" } }
|
|
89
|
+
|
|
90
|
+
expect(editor.reload.role).to eq("editor")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "switches their admin theme" do
|
|
94
|
+
patch theme_admin_users_path(format: :turbo_stream), params: { theme: "dark" }
|
|
95
|
+
|
|
96
|
+
expect(editor.reload.theme).to eq("dark")
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
= render Trek::GateComponent.new
|
|
2
2
|
= form_with url: admin_user_session_path do |f|
|
|
3
|
-
= f.group :email
|
|
3
|
+
= f.group :email, model_name: User.model_name
|
|
4
4
|
= f.email_field :email,
|
|
5
5
|
required: true,
|
|
6
6
|
autofocus: @email.nil?,
|
|
7
7
|
autocomplete: "username",
|
|
8
8
|
value: @email
|
|
9
9
|
|
|
10
|
-
= f.group :password
|
|
10
|
+
= f.group :password, model_name: User.model_name
|
|
11
11
|
= f.password_field :password,
|
|
12
12
|
required: true,
|
|
13
13
|
autofocus: @email.present?,
|
|
@@ -60,6 +60,9 @@ module Trek
|
|
|
60
60
|
content.sub!(/^#{key}:\n/, "") if YAML.safe_load(content)&.fetch(key, :absent).nil?
|
|
61
61
|
end
|
|
62
62
|
content.gsub!(/\n{3,}/, "\n\n")
|
|
63
|
+
# No blank line left at the end where a removed block was: `preapprove_npm_package` appends
|
|
64
|
+
# its own, and a second run would otherwise collapse the double blank line it makes.
|
|
65
|
+
content.sub!(/\n+\z/, "\n")
|
|
63
66
|
|
|
64
67
|
create_file ".yarnrc.yml", content, force: true
|
|
65
68
|
rescue Psych::Exception
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
namespace :trek do
|
|
2
|
+
namespace :attachments do
|
|
3
|
+
desc "Promote Shrine attachments left in the cache storage (DRY_RUN=1 only lists them)"
|
|
4
|
+
task promote_cached: :environment do
|
|
5
|
+
# Loads every model, so that all attachments are found
|
|
6
|
+
Rails.application.eager_load!
|
|
7
|
+
|
|
8
|
+
statuses = Hash.new(0)
|
|
9
|
+
|
|
10
|
+
Trek::CachedAttachments.new.promote(dry_run: ENV["DRY_RUN"].present?) do |entry|
|
|
11
|
+
statuses[entry.status] += 1
|
|
12
|
+
puts [entry.status, "#{entry.record.class.name}##{entry.record.id}.#{entry.name}", entry.file.id].join("\t")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
puts statuses.empty? ? "No attachment left in the cache storage." : statuses.map { "#{_2} #{_1}" }.join(", ")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Trek
|
|
2
|
+
# Finds the records whose Shrine attachments still point to the temporary cache storage —
|
|
3
|
+
# files whose promotion was skipped — and promotes those whose cached file still exists.
|
|
4
|
+
class CachedAttachments
|
|
5
|
+
Entry = Struct.new(:record, :name, :file, :status, keyword_init: true)
|
|
6
|
+
|
|
7
|
+
def self.default_models
|
|
8
|
+
ActiveRecord::Base.descendants.select do |model|
|
|
9
|
+
!model.abstract_class? && model == model.base_class && model.table_exists?
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(models = self.class.default_models)
|
|
14
|
+
@models = models
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Yields an Entry per cached attachment, whose status is :promoted, :promotable (dry run),
|
|
18
|
+
# :missing (the cached file is gone) or :changed (the record changed during promotion).
|
|
19
|
+
def promote(dry_run: false)
|
|
20
|
+
return enum_for(:promote, dry_run:) unless block_given?
|
|
21
|
+
|
|
22
|
+
each_cached_attachment do |record, name|
|
|
23
|
+
attacher = record.public_send(:"#{name}_attacher")
|
|
24
|
+
|
|
25
|
+
yield Entry.new(record:, name:, file: attacher.file, status: promote_attacher(attacher, dry_run:))
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def promote_attacher(attacher, dry_run:)
|
|
32
|
+
return :missing unless attacher.file.exists?
|
|
33
|
+
return :promotable if dry_run
|
|
34
|
+
|
|
35
|
+
attacher.atomic_promote
|
|
36
|
+
:promoted
|
|
37
|
+
rescue Shrine::AttachmentChanged
|
|
38
|
+
:changed
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def each_cached_attachment(&)
|
|
42
|
+
@models.each do |model|
|
|
43
|
+
model.ancestors.grep(Shrine::Attachment).each do |attachment|
|
|
44
|
+
name = attachment.attachment_name
|
|
45
|
+
column = model.connection.quote_column_name(:"#{name}_data")
|
|
46
|
+
cache_key = attachment.options.fetch(:cache, :cache).to_s
|
|
47
|
+
|
|
48
|
+
model.where("#{column}::jsonb ->> 'storage' = ?", cache_key).find_each { |record| yield record, name }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Trek
|
|
4
|
+
# Shrine plugin keeping, across a reload, the attachers whose file was saved with the record.
|
|
5
|
+
#
|
|
6
|
+
# Shrine's `activerecord` plugin resets every attacher when the record is reloaded, which is
|
|
7
|
+
# right for an explicit reload but loses a pending attachment when something reloads the record
|
|
8
|
+
# *during* the save: closure_tree does exactly that in its `after_save` callback when it
|
|
9
|
+
# reorders siblings (on create, or when the parent changes) on a numerically ordered tree.
|
|
10
|
+
# The attacher is then no longer `changed?` when Shrine's `after_commit` runs, so the file is
|
|
11
|
+
# never promoted from the temporary `cache` storage to `store` and breaks once the cache is
|
|
12
|
+
# cleaned up.
|
|
13
|
+
#
|
|
14
|
+
# An attachment that was *not* saved is still discarded, as on any reload.
|
|
15
|
+
module SavedAttachmentsReload
|
|
16
|
+
module ReloadGuard
|
|
17
|
+
def reload(...)
|
|
18
|
+
saved_attachers = instance_variables.filter_map do |ivar|
|
|
19
|
+
attacher = instance_variable_get(ivar)
|
|
20
|
+
[ivar, attacher.dup] if attacher.is_a?(::Shrine::Attacher) && attacher.changed?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
super.tap do
|
|
24
|
+
saved_attachers.each do |ivar, attacher|
|
|
25
|
+
# Only the attacher whose file is the one now in the column: anything else was
|
|
26
|
+
# assigned but not saved, and a reload drops it.
|
|
27
|
+
instance_variable_set(ivar, attacher) if attacher.file == instance_variable_get(ivar).file
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
module AttachmentMethods
|
|
34
|
+
def included(model)
|
|
35
|
+
super
|
|
36
|
+
|
|
37
|
+
# Prepended rather than included: Shrine's own `reload` is defined on this attachment
|
|
38
|
+
# module, which the model includes — a prepended module wraps it whatever the order the
|
|
39
|
+
# model includes its concerns in.
|
|
40
|
+
model.prepend(ReloadGuard)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/trek/version.rb
CHANGED
data/lib/trek.rb
CHANGED
|
@@ -19,12 +19,15 @@ require "shrine"
|
|
|
19
19
|
require "shrine/plugins/blurhash"
|
|
20
20
|
require "slim-rails"
|
|
21
21
|
require "strip_attributes"
|
|
22
|
+
require "turbo-rails"
|
|
22
23
|
require "view_component"
|
|
23
24
|
require "view_component/form"
|
|
24
25
|
|
|
25
26
|
module Trek
|
|
27
|
+
require_relative "trek/cached_attachments"
|
|
26
28
|
require_relative "trek/engine"
|
|
27
29
|
require_relative "trek/i18n_keys_resolver"
|
|
30
|
+
require_relative "trek/saved_attachments_reload"
|
|
28
31
|
require_relative "trek/typography_formatter"
|
|
29
32
|
require_relative "trek/version"
|
|
30
33
|
|
data/package.json
CHANGED
data/postcss.config.js
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
|
+
// Radix Colors only ship light (`:root, .light-theme`) and dark (`.dark-theme`)
|
|
2
|
+
// variables, with no way to follow the system preference
|
|
3
|
+
// (https://github.com/radix-ui/colors/issues/26). Copy every rule targeting
|
|
4
|
+
// `.dark-theme` into a `prefers-color-scheme: dark` media query, with
|
|
5
|
+
// `.auto-theme` in place of `.dark-theme`. The media query is appended last so
|
|
6
|
+
// it wins over the light defaults, and each rule keeps its enclosing at-rules
|
|
7
|
+
// (e.g. the Display P3 `@supports`/`@media` blocks).
|
|
8
|
+
const DARK_THEME = /\.dark-theme(?![\w-])/;
|
|
9
|
+
const autoDarkTheme = {
|
|
10
|
+
postcssPlugin: "auto-dark-theme",
|
|
11
|
+
OnceExit(root, { AtRule }) {
|
|
12
|
+
const media = new AtRule({
|
|
13
|
+
name: "media",
|
|
14
|
+
params: "(prefers-color-scheme: dark)",
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
root.walkRules((rule) => {
|
|
18
|
+
const selectors = rule.selectors
|
|
19
|
+
.filter((selector) => DARK_THEME.test(selector))
|
|
20
|
+
.map((selector) => selector.replace(DARK_THEME, ".auto-theme"));
|
|
21
|
+
if (!selectors.length) return;
|
|
22
|
+
|
|
23
|
+
let node = rule.clone({ selectors });
|
|
24
|
+
let parent = rule.parent;
|
|
25
|
+
while (parent.type === "atrule") {
|
|
26
|
+
node = parent.clone({ nodes: [] }).append(node);
|
|
27
|
+
parent = parent.parent;
|
|
28
|
+
}
|
|
29
|
+
media.append(node);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (media.nodes?.length) root.append(media);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
1
36
|
module.exports = {
|
|
2
37
|
plugins: [
|
|
3
38
|
require("postcss-import"),
|
|
39
|
+
autoDarkTheme,
|
|
4
40
|
// call the `postcss-modules` plugin ourselves
|
|
5
41
|
require("postcss-modules")({
|
|
6
42
|
generateScopedName: (name, filename, _css) => {
|
data/trek.gemspec
CHANGED
|
@@ -39,6 +39,10 @@ Gem::Specification.new do |spec|
|
|
|
39
39
|
spec.add_dependency "http_accept_language", "~> 2.1"
|
|
40
40
|
spec.add_dependency "i18n-tasks", "~> 1.0"
|
|
41
41
|
spec.add_dependency "image_processing", "~> 1.8"
|
|
42
|
+
# json 3's `JSON.parse` takes keyword options only, but ActiveSupport (up to 8.1.3.1) passes them
|
|
43
|
+
# positionally, which breaks `db/schema.rb` for jsonb columns with a default. Lift once Rails
|
|
44
|
+
# supports json 3 (spec/dummy/generated_files_spec.rb checks the schema dump).
|
|
45
|
+
spec.add_dependency "json", "< 3"
|
|
42
46
|
spec.add_dependency "kaminari"
|
|
43
47
|
spec.add_dependency "listen"
|
|
44
48
|
spec.add_dependency "lookbook", ">= 2.3.13"
|
|
@@ -53,6 +57,7 @@ Gem::Specification.new do |spec|
|
|
|
53
57
|
spec.add_dependency "shrine-blurhash", "~> 0.2"
|
|
54
58
|
spec.add_dependency "slim-rails", "~> 4.0"
|
|
55
59
|
spec.add_dependency "strip_attributes", "~> 2.0"
|
|
60
|
+
spec.add_dependency "turbo-rails", ">= 1.0"
|
|
56
61
|
spec.add_dependency "view_component", "< 5.0"
|
|
57
62
|
spec.add_dependency "view_component-form", "~> 0.3"
|
|
58
63
|
spec.add_dependency "zeitwerk", "~> 2.6"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: trek
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mohamed Bengrich
|
|
@@ -180,6 +180,20 @@ dependencies:
|
|
|
180
180
|
- - "~>"
|
|
181
181
|
- !ruby/object:Gem::Version
|
|
182
182
|
version: '1.8'
|
|
183
|
+
- !ruby/object:Gem::Dependency
|
|
184
|
+
name: json
|
|
185
|
+
requirement: !ruby/object:Gem::Requirement
|
|
186
|
+
requirements:
|
|
187
|
+
- - "<"
|
|
188
|
+
- !ruby/object:Gem::Version
|
|
189
|
+
version: '3'
|
|
190
|
+
type: :runtime
|
|
191
|
+
prerelease: false
|
|
192
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
193
|
+
requirements:
|
|
194
|
+
- - "<"
|
|
195
|
+
- !ruby/object:Gem::Version
|
|
196
|
+
version: '3'
|
|
183
197
|
- !ruby/object:Gem::Dependency
|
|
184
198
|
name: kaminari
|
|
185
199
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -382,6 +396,20 @@ dependencies:
|
|
|
382
396
|
- - "~>"
|
|
383
397
|
- !ruby/object:Gem::Version
|
|
384
398
|
version: '2.0'
|
|
399
|
+
- !ruby/object:Gem::Dependency
|
|
400
|
+
name: turbo-rails
|
|
401
|
+
requirement: !ruby/object:Gem::Requirement
|
|
402
|
+
requirements:
|
|
403
|
+
- - ">="
|
|
404
|
+
- !ruby/object:Gem::Version
|
|
405
|
+
version: '1.0'
|
|
406
|
+
type: :runtime
|
|
407
|
+
prerelease: false
|
|
408
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
409
|
+
requirements:
|
|
410
|
+
- - ">="
|
|
411
|
+
- !ruby/object:Gem::Version
|
|
412
|
+
version: '1.0'
|
|
385
413
|
- !ruby/object:Gem::Dependency
|
|
386
414
|
name: view_component
|
|
387
415
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -459,13 +487,16 @@ files:
|
|
|
459
487
|
- app/assets/icons/trek/check.svg
|
|
460
488
|
- app/assets/icons/trek/chevron-double-left.svg
|
|
461
489
|
- app/assets/icons/trek/chevron-double-right.svg
|
|
490
|
+
- app/assets/icons/trek/chevron-down.svg
|
|
462
491
|
- app/assets/icons/trek/chevron-left.svg
|
|
463
492
|
- app/assets/icons/trek/chevron-right.svg
|
|
493
|
+
- app/assets/icons/trek/chevron-up.svg
|
|
464
494
|
- app/assets/icons/trek/close.svg
|
|
465
495
|
- app/assets/icons/trek/cursor.svg
|
|
466
496
|
- app/assets/icons/trek/danger.svg
|
|
467
497
|
- app/assets/icons/trek/download.svg
|
|
468
498
|
- app/assets/icons/trek/eraser.svg
|
|
499
|
+
- app/assets/icons/trek/external.svg
|
|
469
500
|
- app/assets/icons/trek/eye.svg
|
|
470
501
|
- app/assets/icons/trek/grid.svg
|
|
471
502
|
- app/assets/icons/trek/hr.svg
|
|
@@ -487,7 +518,9 @@ files:
|
|
|
487
518
|
- app/assets/icons/trek/reorder.svg
|
|
488
519
|
- app/assets/icons/trek/return.svg
|
|
489
520
|
- app/assets/icons/trek/search.svg
|
|
490
|
-
- app/assets/icons/trek/theme.svg
|
|
521
|
+
- app/assets/icons/trek/theme-auto.svg
|
|
522
|
+
- app/assets/icons/trek/theme-dark.svg
|
|
523
|
+
- app/assets/icons/trek/theme-light.svg
|
|
491
524
|
- app/assets/icons/trek/undo.svg
|
|
492
525
|
- app/assets/images/trek/favicon.png
|
|
493
526
|
- app/assets/images/trek/favicon.svg
|
|
@@ -528,6 +561,10 @@ files:
|
|
|
528
561
|
- app/components/trek/dialog_component/dialog_component.css
|
|
529
562
|
- app/components/trek/dialog_component/dialog_component.html.slim
|
|
530
563
|
- app/components/trek/dialog_component/dialog_component.js
|
|
564
|
+
- app/components/trek/dropdown_component.rb
|
|
565
|
+
- app/components/trek/dropdown_component/dropdown_component.css
|
|
566
|
+
- app/components/trek/dropdown_component/dropdown_component.html.slim
|
|
567
|
+
- app/components/trek/dropdown_component/dropdown_component.js
|
|
531
568
|
- app/components/trek/entries/item_component.rb
|
|
532
569
|
- app/components/trek/entries/item_component/item_component.css
|
|
533
570
|
- app/components/trek/entries/item_component/item_component.html.slim
|
|
@@ -715,6 +752,7 @@ files:
|
|
|
715
752
|
- app/policies/trek/preview_policy.rb
|
|
716
753
|
- app/policies/trek/resource_policy.rb
|
|
717
754
|
- app/policies/trek/user_policy.rb
|
|
755
|
+
- app/uploaders/audio_uploader.rb
|
|
718
756
|
- app/uploaders/image_uploader.rb
|
|
719
757
|
- app/uploaders/model_image_uploader.rb
|
|
720
758
|
- app/uploaders/record_image_uploader.rb
|
|
@@ -800,6 +838,7 @@ files:
|
|
|
800
838
|
- lib/generators/trek/install/admin_generator.rb
|
|
801
839
|
- lib/generators/trek/install/agents_generator.rb
|
|
802
840
|
- lib/generators/trek/install/application_generator.rb
|
|
841
|
+
- lib/generators/trek/install/appsignal_generator.rb
|
|
803
842
|
- lib/generators/trek/install/assets_generator.rb
|
|
804
843
|
- lib/generators/trek/install/attachments_generator.rb
|
|
805
844
|
- lib/generators/trek/install/authentication_generator.rb
|
|
@@ -823,6 +862,7 @@ files:
|
|
|
823
862
|
- lib/generators/trek/install/mailer_generator.rb
|
|
824
863
|
- lib/generators/trek/install/makefile_generator.rb
|
|
825
864
|
- lib/generators/trek/install/menu_node_generator.rb
|
|
865
|
+
- lib/generators/trek/install/model_specs_generator.rb
|
|
826
866
|
- lib/generators/trek/install/package_json_generator.rb
|
|
827
867
|
- lib/generators/trek/install/page_generator.rb
|
|
828
868
|
- lib/generators/trek/install/postcss_generator.rb
|
|
@@ -850,6 +890,7 @@ files:
|
|
|
850
890
|
- lib/generators/trek/templates/Makefile
|
|
851
891
|
- lib/generators/trek/templates/README.md
|
|
852
892
|
- lib/generators/trek/templates/components/index.js
|
|
893
|
+
- lib/generators/trek/templates/config/appsignal.rb.tt
|
|
853
894
|
- lib/generators/trek/templates/config/i18n-tasks.yml.erb
|
|
854
895
|
- lib/generators/trek/templates/controllers/admin/base_controller.rb
|
|
855
896
|
- lib/generators/trek/templates/controllers/admin/dashboards_controller.rb
|
|
@@ -861,9 +902,14 @@ files:
|
|
|
861
902
|
- lib/generators/trek/templates/controllers/admin/user_password_resets_controller.rb
|
|
862
903
|
- lib/generators/trek/templates/controllers/admin/user_sessions_controller.rb
|
|
863
904
|
- lib/generators/trek/templates/controllers/admin/users_controller.rb
|
|
905
|
+
- lib/generators/trek/templates/controllers/concerns/appsignal_context.rb
|
|
864
906
|
- lib/generators/trek/templates/controllers/concerns/trek/authentication.rb
|
|
907
|
+
- lib/generators/trek/templates/db/seeds/setup/fragments.rb
|
|
908
|
+
- lib/generators/trek/templates/db/seeds/setup/users.rb
|
|
909
|
+
- lib/generators/trek/templates/db/seeds/test/cases/page_tree.rb
|
|
865
910
|
- lib/generators/trek/templates/helpers/application_helper.rb
|
|
866
911
|
- lib/generators/trek/templates/helpers/external_link_path_helper.rb
|
|
912
|
+
- lib/generators/trek/templates/initializers/appsignal_context.rb
|
|
867
913
|
- lib/generators/trek/templates/initializers/healthcheck.rb
|
|
868
914
|
- lib/generators/trek/templates/initializers/shrine.rb
|
|
869
915
|
- lib/generators/trek/templates/javascript/application.js
|
|
@@ -911,7 +957,24 @@ files:
|
|
|
911
957
|
- lib/generators/trek/templates/policies/page.rb
|
|
912
958
|
- lib/generators/trek/templates/policies/preview.rb
|
|
913
959
|
- lib/generators/trek/templates/policies/user.rb
|
|
960
|
+
- lib/generators/trek/templates/spec/fixtures/files/image.png
|
|
961
|
+
- lib/generators/trek/templates/spec/fixtures/files/sound.mp3
|
|
962
|
+
- lib/generators/trek/templates/spec/models/page_spec.rb
|
|
963
|
+
- lib/generators/trek/templates/spec/models/user_spec.rb
|
|
964
|
+
- lib/generators/trek/templates/spec/policies/admin/external_link_policy_spec.rb
|
|
965
|
+
- lib/generators/trek/templates/spec/policies/admin/fragment_policy_spec.rb
|
|
966
|
+
- lib/generators/trek/templates/spec/policies/admin/page_policy_spec.rb
|
|
967
|
+
- lib/generators/trek/templates/spec/policies/admin/preview_policy_spec.rb
|
|
968
|
+
- lib/generators/trek/templates/spec/policies/admin/tag_category_policy_spec.rb
|
|
969
|
+
- lib/generators/trek/templates/spec/policies/admin/tag_policy_spec.rb
|
|
970
|
+
- lib/generators/trek/templates/spec/policies/admin/user_policy_spec.rb
|
|
971
|
+
- lib/generators/trek/templates/spec/requests/admin/fragments_spec.rb
|
|
914
972
|
- lib/generators/trek/templates/spec/requests/admin/n_plus_one_spec.rb
|
|
973
|
+
- lib/generators/trek/templates/spec/requests/admin/pages_spec.rb
|
|
974
|
+
- lib/generators/trek/templates/spec/requests/admin/uploads_spec.rb
|
|
975
|
+
- lib/generators/trek/templates/spec/requests/admin/user_password_resets_spec.rb
|
|
976
|
+
- lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb
|
|
977
|
+
- lib/generators/trek/templates/spec/requests/admin/users_spec.rb
|
|
915
978
|
- lib/generators/trek/templates/views/admin/dashboards/index.html.slim
|
|
916
979
|
- lib/generators/trek/templates/views/admin/fragments/_form.html.slim
|
|
917
980
|
- lib/generators/trek/templates/views/admin/fragments/_fragment.json.jb
|
|
@@ -944,14 +1007,18 @@ files:
|
|
|
944
1007
|
- lib/generators/trek/templates/views/admin/users/index.html.slim
|
|
945
1008
|
- lib/generators/trek/templates/views/admin/users/new.html.slim
|
|
946
1009
|
- lib/generators/trek/templates/views/admin/users/show.html.slim
|
|
1010
|
+
- lib/generators/trek/templates/views/admin/users/theme.turbo_stream.slim
|
|
947
1011
|
- lib/generators/trek/templates/views/admin_mailer/invite.en.html.slim
|
|
948
1012
|
- lib/generators/trek/templates/views/admin_mailer/invite.en.text.erb
|
|
949
1013
|
- lib/generators/trek/templates/views/admin_mailer/invite.fr.html.slim
|
|
950
1014
|
- lib/generators/trek/templates/views/admin_mailer/invite.fr.text.erb
|
|
951
1015
|
- lib/generators/trek/upgrade/v2_generator.rb
|
|
1016
|
+
- lib/tasks/trek/attachments.rake
|
|
952
1017
|
- lib/trek.rb
|
|
1018
|
+
- lib/trek/cached_attachments.rb
|
|
953
1019
|
- lib/trek/engine.rb
|
|
954
1020
|
- lib/trek/i18n_keys_resolver.rb
|
|
1021
|
+
- lib/trek/saved_attachments_reload.rb
|
|
955
1022
|
- lib/trek/typography_formatter.rb
|
|
956
1023
|
- lib/trek/version.rb
|
|
957
1024
|
- netlify.toml
|
|
@@ -981,7 +1048,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
981
1048
|
- !ruby/object:Gem::Version
|
|
982
1049
|
version: '0'
|
|
983
1050
|
requirements: []
|
|
984
|
-
rubygems_version: 4.0.
|
|
1051
|
+
rubygems_version: 4.0.20
|
|
985
1052
|
specification_version: 4
|
|
986
1053
|
summary: A modern content management & back-office system for Ruby on Rails
|
|
987
1054
|
test_files: []
|