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.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/.slim-lint.yml +0 -3
  3. data/AGENTS.md +8 -1
  4. data/CHANGELOG.md +62 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +20 -3
  7. data/Makefile +11 -9
  8. data/README.md +1 -1
  9. data/app/assets/icons/trek/chevron-down.svg +1 -0
  10. data/app/assets/icons/trek/chevron-up.svg +1 -0
  11. data/app/assets/icons/trek/external.svg +1 -0
  12. data/app/assets/icons/trek/theme-auto.svg +1 -0
  13. data/app/assets/icons/trek/theme-dark.svg +1 -0
  14. data/app/assets/icons/trek/theme-light.svg +1 -0
  15. data/app/assets/stylesheets/trek/base.css +13 -0
  16. data/app/components/trek/button_component/button_component.css +9 -0
  17. data/app/components/trek/button_component.rb +13 -1
  18. data/app/components/trek/dashboard/tile_component/tile_component.html.slim +1 -1
  19. data/app/components/trek/dashboard/tile_component.rb +14 -0
  20. data/app/components/trek/dialog_component/dialog_component.html.slim +6 -2
  21. data/app/components/trek/dialog_component/dialog_component.js +62 -2
  22. data/app/components/trek/dropdown_component/dropdown_component.css +66 -0
  23. data/app/components/trek/dropdown_component/dropdown_component.html.slim +31 -0
  24. data/app/components/trek/dropdown_component/dropdown_component.js +114 -0
  25. data/app/components/trek/dropdown_component.rb +29 -0
  26. data/app/components/trek/form/group_component/group_component.css +34 -0
  27. data/app/components/trek/header_component/header_component.css +10 -4
  28. data/app/components/trek/header_component/header_component.html.slim +17 -10
  29. data/app/components/trek/header_component/header_component.js +29 -5
  30. data/app/components/trek/header_component.rb +20 -3
  31. data/app/components/trek/menu_component/menu_component.css +6 -0
  32. data/app/components/trek/menu_component.rb +29 -4
  33. data/app/components/trek/toaster/toast_component.rb +1 -1
  34. data/app/controllers/concerns/trek/authorization.rb +12 -0
  35. data/app/helpers/trek/page_path_helper.rb +4 -3
  36. data/app/helpers/trek/theme_helper.rb +16 -1
  37. data/app/models/concerns/trek/phonable.rb +15 -3
  38. data/app/models/concerns/trek/sectionable.rb +5 -0
  39. data/app/models/concerns/trek/users/validations.rb +1 -1
  40. data/app/uploaders/audio_uploader.rb +11 -0
  41. data/app/uploaders/record_image_uploader.rb +3 -0
  42. data/config/locales/trek.en.yml +3 -2
  43. data/config/locales/trek.fr.yml +1 -0
  44. data/docs/customize.md +18 -2
  45. data/docs/deploy.md +7 -2
  46. data/docs/getting-started.md +1 -1
  47. data/docs/index.md +1 -1
  48. data/docs/reference/components.md +15 -0
  49. data/docs/reference/concerns/phonable.md +27 -1
  50. data/docs/reference/environment-variables.md +2 -0
  51. data/docs/reference/forms.md +2 -0
  52. data/docs/reference/generators/install.md +4 -1
  53. data/docs/reference/icons.md +1 -1
  54. data/docs/reference/models/tag.md +1 -1
  55. data/docs/reference/models/user.md +4 -2
  56. data/docs/reference/uploaders.md +31 -6
  57. data/docs/what-is-trek.md +1 -1
  58. data/lib/generators/trek/install/admin/routes_generator.rb +7 -1
  59. data/lib/generators/trek/install/appsignal_generator.rb +102 -0
  60. data/lib/generators/trek/install/model_specs_generator.rb +64 -0
  61. data/lib/generators/trek/install/postmark_generator.rb +3 -0
  62. data/lib/generators/trek/install/ruby_generator.rb +1 -1
  63. data/lib/generators/trek/install/scalingo_generator.rb +1 -1
  64. data/lib/generators/trek/install/uploaders_generator.rb +2 -0
  65. data/lib/generators/trek/install_generator.rb +5 -0
  66. data/lib/generators/trek/taxonomies_generator.rb +7 -0
  67. data/lib/generators/trek/templates/.gitlab-ci.yml +1 -1
  68. data/lib/generators/trek/templates/config/appsignal.rb.tt +25 -0
  69. data/lib/generators/trek/templates/controllers/admin/uploads_controller.rb +4 -0
  70. data/lib/generators/trek/templates/controllers/admin/users_controller.rb +4 -1
  71. data/lib/generators/trek/templates/controllers/concerns/appsignal_context.rb +37 -0
  72. data/lib/generators/trek/templates/db/seeds/setup/fragments.rb +3 -0
  73. data/lib/generators/trek/templates/db/seeds/setup/users.rb +6 -0
  74. data/lib/generators/trek/templates/db/seeds/test/cases/page_tree.rb +5 -0
  75. data/lib/generators/trek/templates/initializers/appsignal_context.rb +5 -0
  76. data/lib/generators/trek/templates/locales/scaffold/model.en.yml.tt +1 -1
  77. data/lib/generators/trek/templates/migrations/fragments.rb.erb +1 -1
  78. data/lib/generators/trek/templates/migrations/menu_nodes.rb.erb +0 -1
  79. data/lib/generators/trek/templates/migrations/tag_categories.rb.erb +1 -1
  80. data/lib/generators/trek/templates/migrations/taggings.rb.erb +1 -1
  81. data/lib/generators/trek/templates/migrations/users.rb.erb +2 -1
  82. data/lib/generators/trek/templates/models/tag.rb +6 -1
  83. data/lib/generators/trek/templates/policies/admin/tag_policy.rb +1 -1
  84. data/lib/generators/trek/templates/policies/external_link_policy.rb +1 -1
  85. data/lib/generators/trek/templates/spec/fixtures/files/image.png +0 -0
  86. data/lib/generators/trek/templates/spec/fixtures/files/sound.mp3 +0 -0
  87. data/lib/generators/trek/templates/spec/models/page_spec.rb +24 -0
  88. data/lib/generators/trek/templates/spec/models/user_spec.rb +76 -0
  89. data/lib/generators/trek/templates/spec/policies/admin/external_link_policy_spec.rb +17 -0
  90. data/lib/generators/trek/templates/spec/policies/admin/fragment_policy_spec.rb +33 -0
  91. data/lib/generators/trek/templates/spec/policies/admin/page_policy_spec.rb +39 -0
  92. data/lib/generators/trek/templates/spec/policies/admin/preview_policy_spec.rb +9 -0
  93. data/lib/generators/trek/templates/spec/policies/admin/tag_category_policy_spec.rb +31 -0
  94. data/lib/generators/trek/templates/spec/policies/admin/tag_policy_spec.rb +31 -0
  95. data/lib/generators/trek/templates/spec/policies/admin/user_policy_spec.rb +58 -0
  96. data/lib/generators/trek/templates/spec/requests/admin/fragments_spec.rb +50 -0
  97. data/lib/generators/trek/templates/spec/requests/admin/pages_spec.rb +47 -0
  98. data/lib/generators/trek/templates/spec/requests/admin/uploads_spec.rb +22 -0
  99. data/lib/generators/trek/templates/spec/requests/admin/user_password_resets_spec.rb +71 -0
  100. data/lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb +45 -0
  101. data/lib/generators/trek/templates/spec/requests/admin/users_spec.rb +99 -0
  102. data/lib/generators/trek/templates/views/admin/user_password_resets/edit.html.slim +1 -1
  103. data/lib/generators/trek/templates/views/admin/user_password_resets/new.html.slim +1 -1
  104. data/lib/generators/trek/templates/views/admin/user_sessions/new.html.slim +2 -2
  105. data/lib/generators/trek/templates/views/admin/users/theme.turbo_stream.slim +2 -0
  106. data/lib/generators/trek/upgrade/v2_generator.rb +3 -0
  107. data/lib/tasks/trek/attachments.rake +18 -0
  108. data/lib/trek/cached_attachments.rb +53 -0
  109. data/lib/trek/saved_attachments_reload.rb +44 -0
  110. data/lib/trek/version.rb +1 -1
  111. data/lib/trek.rb +3 -0
  112. data/package.json +1 -1
  113. data/postcss.config.js +36 -0
  114. data/trek.gemspec +5 -0
  115. metadata +70 -3
  116. 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,6 +1,6 @@
1
1
  = render Trek::GateComponent.new
2
2
  = form_with url: admin_user_password_reset_path(@token), method: :put do |f|
3
- = f.group :password
3
+ = f.group :password, model_name: User.model_name
4
4
  = f.password_field :password,
5
5
  required: true,
6
6
  autocomplete: "new-password",
@@ -1,6 +1,6 @@
1
1
  = render Trek::GateComponent.new
2
2
  = form_with url: admin_user_password_resets_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: true,
@@ -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?,
@@ -0,0 +1,2 @@
1
+ = turbo_stream.replace Trek::ThemeHelper::THEME_ICON_ID do
2
+ = theme_icon(Current.user.theme)
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Trek
4
- VERSION = "2.2.0"
4
+ VERSION = "2.3.0"
5
5
  end
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trek",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "A modern content management & back-office system for Ruby on Rails",
5
5
  "homepage": "https://trek.etamin.studio",
6
6
  "license": "MIT",
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.2.0
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.16
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: []