trek 2.2.0 → 2.3.1
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 +9 -2
- data/CHANGELOG.md +73 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +23 -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 +69 -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 +34 -0
- data/app/components/trek/form/group_component/group_component.css +34 -0
- data/app/components/trek/header_component/header_component.css +9 -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 +24 -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/controllers/concerns/trek/filters.rb +18 -1
- 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 +15 -6
- 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/.vitepress/config.mjs +20 -1
- data/docs/.vitepress/theme/style.css +36 -8
- data/docs/changelog.md +9 -0
- data/docs/customize.md +18 -2
- data/docs/deploy.md +7 -2
- data/docs/getting-started.md +1 -1
- data/docs/index.md +8 -8
- data/docs/public/apple-touch-icon.png +0 -0
- data/docs/public/favicon.ico +0 -0
- data/docs/public/favicon.png +0 -0
- data/docs/public/trek-mark.webp +0 -0
- data/docs/reference/components.md +15 -0
- data/docs/reference/concerns/phonable.md +27 -1
- data/docs/reference/controllers.md +8 -3
- 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 +6 -4
- 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/user_password_resets_controller.rb +7 -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 +109 -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 +78 -0
- data/lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb +51 -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 +4 -0
- data/package.json +1 -1
- data/postcss.config.js +36 -0
- data/trek.gemspec +6 -0
- metadata +89 -3
- data/app/assets/icons/trek/theme.svg +0 -11
|
@@ -9,6 +9,11 @@ module Trek
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
included do
|
|
12
|
+
# Sections are the host app's own `Section` model, which Trek doesn't generate: without one, a
|
|
13
|
+
# record has no sections, and declaring the association anyway would make `destroy` raise
|
|
14
|
+
# (`dependent: :destroy`). `const_defined?` also sees a model Zeitwerk hasn't loaded yet.
|
|
15
|
+
next unless Object.const_defined?(:Section)
|
|
16
|
+
|
|
12
17
|
has_many :sections,
|
|
13
18
|
-> { ordered },
|
|
14
19
|
as: :sectionable,
|
|
@@ -4,14 +4,23 @@ module Trek
|
|
|
4
4
|
extend ActiveSupport::Concern
|
|
5
5
|
|
|
6
6
|
included do
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
# Also applies to lookups (`find_by(email:)`, `authenticate_by`), so signing in and asking
|
|
8
|
+
# for a password reset work whatever the case typed.
|
|
9
|
+
normalizes :email, with: ->(email) { email.strip.downcase }
|
|
10
|
+
|
|
11
11
|
validates :email,
|
|
12
12
|
presence: true,
|
|
13
|
-
uniqueness:
|
|
14
|
-
|
|
13
|
+
uniqueness: { case_sensitive: false },
|
|
14
|
+
email: { mode: :strict }
|
|
15
|
+
# Only when a password was assigned: `password` is nil otherwise.
|
|
16
|
+
validates :password, length: { minimum: 8 }, allow_nil: true
|
|
17
|
+
validate :password_not_only_spaces
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def password_not_only_spaces
|
|
23
|
+
errors.add(:password, :blank) if password&.strip&.empty?
|
|
15
24
|
end
|
|
16
25
|
end
|
|
17
26
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class AudioUploader < Shrine
|
|
2
|
+
plugin :determine_mime_type
|
|
3
|
+
plugin :validation_helpers
|
|
4
|
+
# The admin's sound field accepts files up to 200 MB (`Trek::Form::SoundFieldComponent`), above
|
|
5
|
+
# the 20 MB the Shrine initializer allows images.
|
|
6
|
+
plugin :upload_endpoint, url: true, max_size: 200.megabytes
|
|
7
|
+
|
|
8
|
+
Attacher.validate do
|
|
9
|
+
validate_mime_type %w[audio/mpeg audio/mp4 audio/aac audio/ogg audio/wav audio/x-wav audio/webm audio/flac]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
class RecordImageUploader < ImageUploader
|
|
2
2
|
plugin :activerecord
|
|
3
|
+
# Keeps a file attached during a save that reloads the record (closure_tree reorders siblings
|
|
4
|
+
# that way) pending, so it is still promoted out of the cache storage on commit.
|
|
5
|
+
plugin Trek::SavedAttachmentsReload
|
|
3
6
|
end
|
data/config/locales/trek.en.yml
CHANGED
|
@@ -69,8 +69,8 @@ en:
|
|
|
69
69
|
self: the same tab
|
|
70
70
|
prompt:
|
|
71
71
|
targets:
|
|
72
|
-
blank:
|
|
73
|
-
self:
|
|
72
|
+
blank: a new tab
|
|
73
|
+
self: the same tab
|
|
74
74
|
contents: Contents
|
|
75
75
|
dashboard: Dashboard
|
|
76
76
|
dashboards:
|
|
@@ -264,3 +264,4 @@ en:
|
|
|
264
264
|
notices:
|
|
265
265
|
authentication_needed: Please log in
|
|
266
266
|
rate_limited: Too many attempts, please try again later
|
|
267
|
+
unauthorized: You are not allowed to do that
|
data/config/locales/trek.fr.yml
CHANGED
data/docs/.vitepress/config.mjs
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { defineConfig } from 'vitepress'
|
|
2
2
|
import llmstxt from 'vitepress-plugin-llms'
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
|
|
5
|
+
// The released version: `make release` keeps package.json in sync with lib/trek/version.rb.
|
|
6
|
+
const { version } = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'))
|
|
3
7
|
|
|
4
8
|
|
|
5
9
|
// https://vitepress.dev/reference/site-config
|
|
@@ -11,6 +15,13 @@ export default defineConfig({
|
|
|
11
15
|
description: "A modern content management & back-office system for Ruby on Rails",
|
|
12
16
|
lang: 'en-US',
|
|
13
17
|
lastUpdated: true,
|
|
18
|
+
// The header logo doubles as the favicon (PNG versions for older browsers and iOS home screens,
|
|
19
|
+
// favicon.ico for clients that only request /favicon.ico).
|
|
20
|
+
head: [
|
|
21
|
+
['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }],
|
|
22
|
+
['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon.png' }],
|
|
23
|
+
['link', { rel: 'apple-touch-icon', href: '/apple-touch-icon.png' }],
|
|
24
|
+
],
|
|
14
25
|
themeConfig: {
|
|
15
26
|
logo: { src: '/logo.svg', width: 24, height: 24 },
|
|
16
27
|
|
|
@@ -18,7 +29,15 @@ export default defineConfig({
|
|
|
18
29
|
nav: [
|
|
19
30
|
{ text: 'Home', link: '/' },
|
|
20
31
|
{ text: 'Guide', link: '/what-is-trek' },
|
|
21
|
-
{ text: 'Reference', link: '/reference/' }
|
|
32
|
+
{ text: 'Reference', link: '/reference/' },
|
|
33
|
+
{
|
|
34
|
+
text: `v${version}`,
|
|
35
|
+
items: [
|
|
36
|
+
{ text: 'Changelog', link: '/changelog' },
|
|
37
|
+
{ text: 'RubyGems', link: `https://rubygems.org/gems/trek/versions/${version}` },
|
|
38
|
+
{ text: 'npm', link: `https://www.npmjs.com/package/trek/v/${version}` }
|
|
39
|
+
]
|
|
40
|
+
}
|
|
22
41
|
],
|
|
23
42
|
|
|
24
43
|
sidebar: {
|
|
@@ -75,15 +75,18 @@
|
|
|
75
75
|
* -------------------------------------------------------------------------- */
|
|
76
76
|
|
|
77
77
|
:root {
|
|
78
|
+
/* The violet end of the hero name gradient, a step darker on hover and when pressed */
|
|
79
|
+
--trek-button-brand: #7a4df0;
|
|
80
|
+
|
|
78
81
|
--vp-button-brand-border: transparent;
|
|
79
82
|
--vp-button-brand-text: var(--vp-c-white);
|
|
80
|
-
--vp-button-brand-bg: var(--
|
|
83
|
+
--vp-button-brand-bg: var(--trek-button-brand);
|
|
81
84
|
--vp-button-brand-hover-border: transparent;
|
|
82
85
|
--vp-button-brand-hover-text: var(--vp-c-white);
|
|
83
|
-
--vp-button-brand-hover-bg: var(--
|
|
86
|
+
--vp-button-brand-hover-bg: color-mix(in srgb, var(--trek-button-brand) 88%, black);
|
|
84
87
|
--vp-button-brand-active-border: transparent;
|
|
85
88
|
--vp-button-brand-active-text: var(--vp-c-white);
|
|
86
|
-
--vp-button-brand-active-bg: var(--
|
|
89
|
+
--vp-button-brand-active-bg: color-mix(in srgb, var(--trek-button-brand) 76%, black);
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
/**
|
|
@@ -91,19 +94,44 @@
|
|
|
91
94
|
* -------------------------------------------------------------------------- */
|
|
92
95
|
|
|
93
96
|
:root {
|
|
97
|
+
--trek-green: #63e0a6;
|
|
98
|
+
--trek-purple: #9b7bff;
|
|
99
|
+
|
|
94
100
|
--vp-home-hero-name-color: transparent;
|
|
95
|
-
|
|
101
|
+
/* Darker stops on the light background, the brand colors themselves on the dark one */
|
|
102
|
+
--vp-home-hero-name-background: linear-gradient(
|
|
96
103
|
120deg,
|
|
97
|
-
#
|
|
98
|
-
#
|
|
104
|
+
#1fa971 15%,
|
|
105
|
+
#7a4df0 100%
|
|
99
106
|
);
|
|
100
107
|
|
|
101
108
|
--vp-home-hero-image-background-image: linear-gradient(
|
|
102
109
|
-45deg,
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
var(--trek-purple) 50%,
|
|
111
|
+
var(--trek-green) 50%
|
|
105
112
|
);
|
|
106
113
|
--vp-home-hero-image-filter: blur(44px);
|
|
114
|
+
|
|
115
|
+
/* Feature icons (docs/index.md): the ink follows the text color, light or dark */
|
|
116
|
+
--trek-icon-ink: currentColor;
|
|
117
|
+
--trek-icon-accent: var(--trek-green);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Text selection in the brand violet rather than the browser's default blue */
|
|
121
|
+
::selection {
|
|
122
|
+
background-color: color-mix(in srgb, var(--trek-purple) 30%, transparent);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.dark ::selection {
|
|
126
|
+
background-color: color-mix(in srgb, var(--trek-purple) 45%, transparent);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.dark {
|
|
130
|
+
--vp-home-hero-name-background: linear-gradient(
|
|
131
|
+
120deg,
|
|
132
|
+
var(--trek-green) 15%,
|
|
133
|
+
var(--trek-purple) 100%
|
|
134
|
+
);
|
|
107
135
|
}
|
|
108
136
|
|
|
109
137
|
@media (min-width: 640px) {
|
data/docs/changelog.md
ADDED
data/docs/customize.md
CHANGED
|
@@ -27,7 +27,21 @@ Replace Trek's favicon with your own:
|
|
|
27
27
|
Trek's UI components accept options where it matters:
|
|
28
28
|
|
|
29
29
|
- **`Trek::Form::ActionsComponent`** — pick an `aspect` and add your own `classnames` to restyle form action bars.
|
|
30
|
-
- **`Trek::DialogComponent`** — set the `title` and the buttons of modal dialogs.
|
|
30
|
+
- **`Trek::DialogComponent`** — set the `title` and the buttons of modal dialogs. The confirm dialog wired to `data-turbo-confirm` can also be customized per action from the confirming element's data attributes, with the delete wording as fallback:
|
|
31
|
+
|
|
32
|
+
```slim
|
|
33
|
+
= render Trek::ButtonComponent.new(
|
|
34
|
+
text: "Run the prize draw",
|
|
35
|
+
to: admin_event_draw_path(event), method: :post,
|
|
36
|
+
data: {
|
|
37
|
+
turbo_confirm: "Run the prize draw now? Winners will be picked and emails sent.",
|
|
38
|
+
turbo_confirm_title: "Prize draw",
|
|
39
|
+
turbo_confirm_button: "Run the draw",
|
|
40
|
+
turbo_confirm_color: "grass",
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
31
45
|
- **`Trek::Form::ContentEditorComponent`** — toggle the `nodes`, `blocks` and `floating` options to slim down the editor where full rich text isn't needed:
|
|
32
46
|
|
|
33
47
|
```ruby
|
|
@@ -62,9 +76,11 @@ Each scaffolded resource gets its own policy in `app/policies/admin`. Adjust the
|
|
|
62
76
|
|
|
63
77
|
Trek's admin supports light, dark and auto themes, selectable per user. The design tokens are based on [Radix colors](https://www.radix-ui.com/colors); override the CSS custom properties in your own stylesheet to adapt the palette.
|
|
64
78
|
|
|
79
|
+
The auto theme follows the system setting (`prefers-color-scheme`). The `autoDarkTheme` plugin in the generated `postcss.config.js` copies every rule targeting `.dark-theme` into a `@media (prefers-color-scheme: dark)` block, with `.auto-theme` in its place, so dark overrides in your own stylesheets (`.dark-theme { --accent-9: … }`, `.dark-theme .card { … }`) apply to the auto theme too.
|
|
80
|
+
|
|
65
81
|
## Dependencies configuration
|
|
66
82
|
|
|
67
|
-
The installer configures Shrine, Postmark,
|
|
83
|
+
The installer configures Shrine, Postmark, ActionPolicy, Mobility and friends with sensible defaults — through initializers generated **in your app**. You can edit them at any time:
|
|
68
84
|
|
|
69
85
|
- `config/initializers/shrine.rb` — storage backends, upload limits
|
|
70
86
|
- `config/credentials.yml.enc` — Postmark API token (`EDITOR="code --wait" bin/rails credentials:edit`)
|
data/docs/deploy.md
CHANGED
|
@@ -20,14 +20,19 @@ Generate the Scalingo files (an `Aptfile` and a `Procfile` with the web process
|
|
|
20
20
|
rails g trek:install:scalingo
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Then configure the Scalingo app's environment, pinning the buildpack to a release
|
|
23
|
+
Then configure the Scalingo app's environment, pinning the buildpack to a release through its archive:
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
scalingo --app <app> env-set BUILDPACK_URL=https://github.com/etaminstudio/trek-buildpack
|
|
26
|
+
scalingo --app <app> env-set BUILDPACK_URL=https://github.com/etaminstudio/trek-buildpack/archive/refs/tags/v0.1.0.tar.gz
|
|
27
27
|
scalingo --app <app> env-set 'DATABASE_URL=$SCALINGO_POSTGRESQL_URL'
|
|
28
28
|
scalingo --app <app> env-set RAILS_MASTER_KEY=$(cat config/master.key)
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
> Pin through the archive, not a `#tag` suffix: Scalingo reads `#ref` on `BUILDPACK_URL` as a **branch**, so
|
|
32
|
+
> `…/trek-buildpack#v0.1.0` asks for a branch named `v0.1.0`, which does not exist, and the build fails with
|
|
33
|
+
> `fail to download and extract the custom buildpack: Invalid status code: 404 Not Found`. The `.tar.gz` form above is
|
|
34
|
+
> downloaded as is and stays pinned to the release.
|
|
35
|
+
|
|
31
36
|
Make sure `config/database.yml` reads `DATABASE_URL` in production — the section `rails new` generates targets a local socket with a password variable instead:
|
|
32
37
|
|
|
33
38
|
```yml
|
data/docs/getting-started.md
CHANGED
|
@@ -68,7 +68,7 @@ The installer will:
|
|
|
68
68
|
- create the `Fragment` model and include Trek's concerns
|
|
69
69
|
- create the relevant policies
|
|
70
70
|
- generate and run the relevant migrations
|
|
71
|
-
- set up authentication (
|
|
71
|
+
- set up authentication (`has_secure_password` with database-backed sessions), authorization (ActionPolicy), uploads (Shrine), emails (Postmark), i18n, linting, CI and more
|
|
72
72
|
|
|
73
73
|
### Configure Postmark (optional)
|
|
74
74
|
|
data/docs/index.md
CHANGED
|
@@ -7,7 +7,7 @@ hero:
|
|
|
7
7
|
text: "Take the easy path"
|
|
8
8
|
tagline: "A modern content management & back-office system for Ruby on Rails"
|
|
9
9
|
image:
|
|
10
|
-
src: /
|
|
10
|
+
src: /trek-mark.webp
|
|
11
11
|
alt: Trek
|
|
12
12
|
actions:
|
|
13
13
|
- theme: brand
|
|
@@ -18,22 +18,22 @@ hero:
|
|
|
18
18
|
link: /getting-started
|
|
19
19
|
|
|
20
20
|
features:
|
|
21
|
-
- icon:
|
|
21
|
+
- icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="32" height="32" aria-hidden="true"><path fill-rule="evenodd" fill="var(--trek-icon-ink, currentColor)" d="M16 14A8 8 0 0 1 32 14V38H16ZM21 18A3 3 0 1 0 27 18A3 3 0 1 0 21 18ZM16 26V38H4A12 12 0 0 1 16 26ZM32 26A12 12 0 0 1 44 38H32Z"/><path fill="var(--trek-icon-accent, #63E0A6)" d="M18 40H30A6 6 0 0 1 18 40Z"/></svg>'
|
|
22
22
|
title: Ready out of the box
|
|
23
23
|
details: One installer sets up authentication, authorization, file uploads, emails, i18n and a polished admin UI — with sensible conventions you can override later.
|
|
24
|
-
- icon:
|
|
24
|
+
- icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="32" height="32" aria-hidden="true"><path fill-rule="evenodd" fill="var(--trek-icon-ink, currentColor)" d="M6 6H30V30A12 12 0 0 1 18 42H6ZM12 14H24V18H12ZM12 22H20V26H12Z"/><path fill="var(--trek-icon-accent, #63E0A6)" d="M34 6H42V32A4 4 0 0 1 34 32Z"/></svg>'
|
|
25
25
|
title: Modern content editing
|
|
26
26
|
details: A Tiptap-based rich text editor, hierarchical pages with versioning and SEO-friendly paths, plus reusable content fragments.
|
|
27
|
-
- icon:
|
|
27
|
+
- icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="32" height="32" aria-hidden="true"><path fill-rule="evenodd" fill="var(--trek-icon-ink, currentColor)" d="M6 18H30V30H42V42H6Z"/><path fill="var(--trek-icon-accent, #63E0A6)" d="M6 18V6A12 12 0 0 1 18 18Z"/></svg>'
|
|
28
28
|
title: Scaffold-driven development
|
|
29
29
|
details: One command generates the model, admin panel, views, policy, routes and locales for any resource — just like Rails scaffolding, but for your back-office.
|
|
30
|
-
- icon:
|
|
30
|
+
- icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="32" height="32" aria-hidden="true"><path fill-rule="evenodd" fill="var(--trek-icon-ink, currentColor)" d="M22 22H5A17 17 0 0 1 22 5ZM22 26V43A17 17 0 0 1 5 26ZM26 26H43A17 17 0 0 1 26 43Z"/><path fill="var(--trek-icon-accent, #63E0A6)" d="M26 22V5A17 17 0 0 1 43 22Z"/></svg>'
|
|
31
31
|
title: Multilingual by default
|
|
32
32
|
details: Content and admin UI translations powered by Mobility and i18n-tasks, with optional automatic translation via DeepL.
|
|
33
|
-
- icon:
|
|
33
|
+
- icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="32" height="32" aria-hidden="true"><path fill-rule="evenodd" fill="var(--trek-icon-ink, currentColor)" d="M14 22V16A10 10 0 0 1 34 16V22H28V16A4 4 0 0 0 20 16V22ZM8 22H24V42H16A8 8 0 0 1 8 34Z"/><path fill="var(--trek-icon-accent, #63E0A6)" d="M24 22H40V34A8 8 0 0 1 32 42H24Z"/></svg>'
|
|
34
34
|
title: Roles & policies built in
|
|
35
|
-
details:
|
|
36
|
-
- icon:
|
|
35
|
+
details: Authentication on Rails' own has_secure_password and ActionPolicy authorization with admin, editor, user, reader and guest roles, enforced across every panel.
|
|
36
|
+
- icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="32" height="32" aria-hidden="true"><path fill-rule="evenodd" fill="var(--trek-icon-ink, currentColor)" d="M6 6H22V22H6ZM26 22V6A16 16 0 0 1 42 22ZM6 26H22V42A16 16 0 0 1 6 26Z"/><path fill="var(--trek-icon-accent, #63E0A6)" d="M26 26H42V42H26Z"/></svg>'
|
|
37
37
|
title: Hotwire-native UI
|
|
38
38
|
details: ViewComponents, Stimulus and Turbo under the hood, with light & dark themes, Radix colors and a customizable design system.
|
|
39
39
|
---
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -22,6 +22,21 @@ The main building blocks, all in the `Trek::` namespace:
|
|
|
22
22
|
| `HeadingComponent` | headings |
|
|
23
23
|
| `GateComponent` | authorization-based visibility gate |
|
|
24
24
|
| `IconComponent` | SVG [icon](/reference/icons) renderer |
|
|
25
|
+
| `Dashboard::SectionComponent` / `Dashboard::TileComponent` | dashboard sections of shortcut tiles |
|
|
26
|
+
|
|
27
|
+
## Dashboard tiles
|
|
28
|
+
|
|
29
|
+
A tile is a link by default. With `method:`, it issues another HTTP method through Turbo (as a `data-turbo-method` link), so a tile can create a record in one click rather than open a form; `data:` adds attributes to the link, such as a confirmation:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
s.with_tile(
|
|
33
|
+
text: "New weekly draft",
|
|
34
|
+
icon: "trek/add",
|
|
35
|
+
href: [:draft_current_week, :admin, :newsletters],
|
|
36
|
+
method: :post,
|
|
37
|
+
data: { turbo_confirm: "Create this week's draft?" }
|
|
38
|
+
)
|
|
39
|
+
```
|
|
25
40
|
|
|
26
41
|
## Form components
|
|
27
42
|
|
|
@@ -13,11 +13,37 @@ end
|
|
|
13
13
|
|
|
14
14
|
For each declared attribute:
|
|
15
15
|
|
|
16
|
-
- a validation (`phone: { possible: true, allow_blank: true }
|
|
16
|
+
- a validation (`phone: { possible: true, allow_blank: true }`, see [Strictness](#strictness))
|
|
17
17
|
- normalization to E.164 international format on save
|
|
18
18
|
- `formatted_{attr}` — national format (`"01 23 45 67 89"`)
|
|
19
19
|
- `{attr}_with_country_code` — E.164 (`"+33123456789"`)
|
|
20
20
|
|
|
21
|
+
## Strictness
|
|
22
|
+
|
|
23
|
+
`strictness:` picks how hard Phonelib looks at a number. It applies to every attribute of the call.
|
|
24
|
+
|
|
25
|
+
| value | what is accepted |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `:possible` (default) | the digits **could** form a number for the country — right length and shape |
|
|
28
|
+
| `:valid` | the digits fall in a range the regulator has actually **assigned** |
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
class Organization < ApplicationRecord
|
|
32
|
+
include Trek::Phonable
|
|
33
|
+
phonable_attributes :phone, strictness: :valid
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The gap between the two is worth knowing before leaving the default in place. With
|
|
38
|
+
`Phonelib.default_country = "FR"`, `"0012345678"` is *possible*: under `:possible` it is accepted and
|
|
39
|
+
normalized to `"+12345678"` — the stray leading zero read as an international prefix, the number
|
|
40
|
+
silently rehomed to country code +1, and the original input gone, since normalization happens on
|
|
41
|
+
save. Under `:valid` the same input is rejected and the person is asked to correct it.
|
|
42
|
+
|
|
43
|
+
`:possible` is the right default for a number stored as a way to reach someone, where a false
|
|
44
|
+
rejection is worse than a number that never gets dialled. Choose `:valid` when the application
|
|
45
|
+
intends to actually call or text what it collects.
|
|
46
|
+
|
|
21
47
|
## Usage
|
|
22
48
|
|
|
23
49
|
```ruby
|
|
@@ -26,9 +26,10 @@ end
|
|
|
26
26
|
|
|
27
27
|
### Filters
|
|
28
28
|
|
|
29
|
-
`Trek::Filters` lets users narrow an index by attribute values.
|
|
30
|
-
|
|
31
|
-
`"nil"` matches `NULL`)
|
|
29
|
+
`Trek::Filters` lets users narrow an index by attribute values. Declare the columns users may filter on via
|
|
30
|
+
`available_filters`, then call `apply_filters!` in your `index` action — it rewrites `@objects` from
|
|
31
|
+
`params[:filters]`, applying a `where(column => value)` per non-blank filter (a value of `"nil"` matches `NULL`). The filters are read
|
|
32
|
+
through strong params (`filter_params`): any other key, and any value that isn't a single value, is ignored:
|
|
32
33
|
|
|
33
34
|
```ruby
|
|
34
35
|
module Admin
|
|
@@ -44,6 +45,10 @@ module Admin
|
|
|
44
45
|
|
|
45
46
|
private
|
|
46
47
|
|
|
48
|
+
def available_filters
|
|
49
|
+
%i[country_code]
|
|
50
|
+
end
|
|
51
|
+
|
|
47
52
|
def countries
|
|
48
53
|
City.distinct.pluck(:country_code).index_with { |code| I18n.t("countries.#{code}") }
|
|
49
54
|
end
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
| --- | --- |
|
|
5
5
|
| `ADMIN_EMAIL` / `ADMIN_PASSWORD` | credentials used by [`trek:admin:user`](/reference/generators/admin-user) |
|
|
6
6
|
| `POSTMARK_API_TOKEN` | Postmark email delivery (auto-injected into credentials by the [installer](/reference/generators/install)) |
|
|
7
|
+
| `APPSIGNAL_PUSH_API_KEY` | AppSignal error tracking and performance monitoring (auto-injected into credentials by the [installer](/reference/generators/install)) |
|
|
8
|
+
| `APPSIGNAL_APP_ENV` | environment name reported to AppSignal (optional, defaults to `RAILS_ENV`) |
|
|
7
9
|
| `DEEPL_AUTH_KEY` | automatic locale translation in the [scaffold generator](/reference/generators/scaffold) |
|
|
8
10
|
| `FEATURE_STORAGE` | `local` (development) or `s3` (production) |
|
|
9
11
|
| `S3_BUCKET`, `S3_REGION`, `S3_ENDPOINT`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_BASE_URL` | S3 storage configuration for [uploads](/reference/uploaders) |
|
data/docs/reference/forms.md
CHANGED
|
@@ -13,6 +13,8 @@ Trek ships `Trek::FormBuilder`, an extension of [`ViewComponent::Form`](https://
|
|
|
13
13
|
|
|
14
14
|
Each helper renders the matching component from the [`Trek::Form` namespace](/reference/components#form-components).
|
|
15
15
|
|
|
16
|
+
Standard helpers are styled too: a plain `f.file_field` inside an `f.group` gets its native `::file-selector-button` styled like a narrow `Trek::ButtonComponent` (following the current `data-accent-color`), keeping the browser's built-in "file chosen" feedback with no JS — handy for one-off uploads like a spreadsheet import.
|
|
17
|
+
|
|
16
18
|
```slim
|
|
17
19
|
= form_with model: [:admin, @book] do |f|
|
|
18
20
|
= f.group :title
|
|
@@ -21,7 +21,7 @@ The installer orchestrates dedicated sub-generators, all under the `trek:install
|
|
|
21
21
|
|
|
22
22
|
| Area | What gets set up |
|
|
23
23
|
| --- | --- |
|
|
24
|
-
| Authentication |
|
|
24
|
+
| Authentication | bcrypt and `has_secure_password` on `User`, the `UserSession` model (one per signed-in browser, found through a signed `session_id` cookie), the `Trek::Authentication` controller concern, login/logout & password reset panels |
|
|
25
25
|
| Authorization | ActionPolicy and the base policies |
|
|
26
26
|
| Models | `User`, `Page`, `PagePath`, `PageVersion`, `Fragment`, `MenuNode`, `ExternalLink`, `Current` — with migrations |
|
|
27
27
|
| Attachments | Shrine configuration and the uploaders |
|
|
@@ -32,6 +32,8 @@ The installer orchestrates dedicated sub-generators, all under the `trek:install
|
|
|
32
32
|
| Tooling | esbuild, PostCSS, EditorConfig, Procfiles, Makefile, health check endpoint |
|
|
33
33
|
| Linting | Standard, ESLint, Stylelint, Prettier, Slim Lint, Lefthook git hooks |
|
|
34
34
|
| Security | Brakeman, bundler-audit, ruby_audit |
|
|
35
|
+
| Monitoring | AppSignal error tracking and performance monitoring — `config/appsignal.rb`, the push API key in the credentials, and the `AppsignalContext` concern tagging transactions per area and user |
|
|
36
|
+
| Specs | RSpec, and specs for the models Trek installs (`trek:install:model_specs`): `spec/models/page_spec.rb` and `spec/models/user_spec.rb`, with their test data as [Oaken](https://github.com/kaspth/oaken) seed scripts — defaults in `db/seeds/setup/`, scenarios in `db/seeds/test/cases/` loaded with `seed "cases/…"`. They check Trek's behaviour in your app — slugs and paths, user validations and roles — so a customization that breaks it fails your suite; edit or remove the ones you deliberately change. Trek's own suite runs the same files |
|
|
35
37
|
| N+1 detection | Prosopite (development/test), scanning every request — see [Controllers › N+1 detection](/reference/controllers#n-1-detection) |
|
|
36
38
|
| CI | GitLab CI configuration |
|
|
37
39
|
| Deployment | Scalingo files (`trek:install:scalingo`, run on demand) — see [Deploying to Scalingo](/deploy) |
|
|
@@ -41,4 +43,5 @@ The installer orchestrates dedicated sub-generators, all under the `trek:install
|
|
|
41
43
|
Define these **before** running the installer to have them injected automatically:
|
|
42
44
|
|
|
43
45
|
- `POSTMARK_API_TOKEN` — injected into the Rails credentials for email delivery
|
|
46
|
+
- `APPSIGNAL_PUSH_API_KEY` — injected into the Rails credentials for error tracking and performance monitoring
|
|
44
47
|
- `DEEPL_AUTH_KEY` — enables automatic locale translation in the [scaffold generator](/reference/generators/scaffold)
|
data/docs/reference/icons.md
CHANGED
|
@@ -6,6 +6,6 @@ Trek bundles ~40 SVG icons, rendered with `Trek::IconComponent`:
|
|
|
6
6
|
render Trek::IconComponent.new(:search)
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Available keys include: `add`, `arrow-bar-left`, `arrow-bar-right`, `bold`, `check`, `chevron-left`, `chevron-right`, `chevron-double`, `close`, `cursor`, `danger`, `download`, `eraser`, `eye`, `grid`, `hr`, `italic`, `layout`, `link`, `list`, `logout`, `paragraph`, `pic`, `profile`, `quotation`, `rating`, `redo`, `undo`, `search`…
|
|
9
|
+
Available keys include: `add`, `arrow-bar-left`, `arrow-bar-right`, `bold`, `check`, `chevron-left`, `chevron-right`, `chevron-double`, `close`, `cursor`, `danger`, `download`, `eraser`, `external`, `eye`, `grid`, `hr`, `italic`, `layout`, `link`, `list`, `logout`, `paragraph`, `pic`, `profile`, `quotation`, `rating`, `redo`, `undo`, `search`…
|
|
10
10
|
|
|
11
11
|
[MenuNode](/reference/models/menu-node) records can reference an icon via their `icon_key` to display it in the admin sidebar.
|
|
@@ -12,7 +12,7 @@ The taxonomy models, installed by the [taxonomies generator](/reference/generato
|
|
|
12
12
|
| `position` | integer | ordering within the category |
|
|
13
13
|
| `taggings_count` | integer | counter cache |
|
|
14
14
|
|
|
15
|
-
- `belongs_to :category`, `has_many :taggings`, `
|
|
15
|
+
- `belongs_to :category`, `has_many :taggings`, and `taggables`, the tagged records of any model
|
|
16
16
|
- Scopes: `ordered`, `in_category(key)`, `in_categories(keys)`
|
|
17
17
|
|
|
18
18
|
## TagCategory
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# User
|
|
2
2
|
|
|
3
|
-
The host application's `User` model, into which Trek injects authentication (
|
|
3
|
+
The host application's `User` model, into which Trek injects authentication (`has_secure_password` and database-backed sessions), roles, theme & locale preferences and the invitation workflow.
|
|
4
4
|
|
|
5
5
|
## Columns added by Trek
|
|
6
6
|
|
|
@@ -10,8 +10,10 @@ The host application's `User` model, into which Trek injects authentication (Sor
|
|
|
10
10
|
| `role` | integer | enum, default `guest` |
|
|
11
11
|
| `theme` | integer | enum, default `auto` |
|
|
12
12
|
| `locale` | string | admin UI language |
|
|
13
|
+
| `password_digest` | string | `has_secure_password` (bcrypt), required |
|
|
14
|
+
| `last_activity_at` | datetime | updated after each signed-in admin request |
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
When the app has no `User` model yet, Trek creates the `users` table with its `email` column. Signed-in browsers are `UserSession` records (`user_agent`, `ip_address`) in a `user_sessions` table, found through a signed `session_id` cookie. Password reset (15 minutes) and invitation (1 week) links carry tokens from `generates_token_for`, so no column stores them.
|
|
15
17
|
|
|
16
18
|
## Roles
|
|
17
19
|
|
|
@@ -50,8 +52,8 @@ User.create(email: "new@example.com", role: :editor, send_invite: true)
|
|
|
50
52
|
|
|
51
53
|
`Trek::Users::Validations` enforces:
|
|
52
54
|
|
|
53
|
-
- `email` —
|
|
54
|
-
- `password` —
|
|
55
|
+
- `email` — stored stripped and in lower case (lookups too, so sign-in and password resets ignore case), present, unique whatever its case, and well-formed by [`email_validator`](https://github.com/K-and-R/email_validator)'s strict mode (no consecutive or leading/trailing dots, a domain with a TLD)
|
|
56
|
+
- `password` — when one is assigned: at least 8 characters, and not spaces only. A blank password is ignored by `has_secure_password` (the current one is kept); the password reset form refuses it
|
|
55
57
|
|
|
56
58
|
## Scopes
|
|
57
59
|
|
data/docs/reference/uploaders.md
CHANGED
|
@@ -5,15 +5,40 @@ File uploads are handled by [Shrine](https://shrinerb.com), with Uppy on the fro
|
|
|
5
5
|
- `ImageUploader` — base image uploader
|
|
6
6
|
- `RecordImageUploader` — attach an image to any record: `include RecordImageUploader::Attachment(:image)`
|
|
7
7
|
- `ModelImageUploader` — model-level image uploader (used by content editor images)
|
|
8
|
+
- `AudioUploader` — sounds for `f.sound_field` (MP3, AAC/M4A, Ogg, WAV, WebM, FLAC): `include AudioUploader::Attachment(:audio)` on a record with an `audio_data` jsonb column
|
|
8
9
|
|
|
9
10
|
## Storage
|
|
10
11
|
|
|
11
12
|
Configured per environment in the generated Shrine initializer (`config/initializers/shrine.rb`):
|
|
12
13
|
|
|
13
|
-
| Environment | Storage
|
|
14
|
-
|
|
|
15
|
-
| production
|
|
16
|
-
| development | local filesystem (`public/uploads/`)
|
|
17
|
-
| test
|
|
14
|
+
| Environment | Storage |
|
|
15
|
+
| ----------- | --------------------------------------------------------------------- |
|
|
16
|
+
| production | S3 (via the [`S3_*` ENV variables](/reference/environment-variables)) |
|
|
17
|
+
| development | local filesystem (`public/uploads/`) |
|
|
18
|
+
| test | in-memory |
|
|
18
19
|
|
|
19
|
-
Image processing uses libvips, blurhash placeholders are generated for progressive loading, and direct uploads go through the Shrine `upload_endpoint` (20 MB max).
|
|
20
|
+
Image processing uses libvips, blurhash placeholders are generated for progressive loading, and direct uploads go through the Shrine `upload_endpoint`: `POST /admin/upload/image` (20 MB max) and `POST /admin/upload/audio` (200 MB max), handled by the generated `Admin::UploadsController`. The endpoint only checks the size; each uploader validates the file type when it is attached to a record.
|
|
21
|
+
|
|
22
|
+
## Attachments and records reloaded mid-save
|
|
23
|
+
|
|
24
|
+
`RecordImageUploader` loads `Trek::SavedAttachmentsReload` on top of Shrine's `activerecord` plugin:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
class RecordImageUploader < ImageUploader
|
|
28
|
+
plugin :activerecord
|
|
29
|
+
plugin Trek::SavedAttachmentsReload
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Shrine resets every attacher when a record is reloaded, which is right for an explicit reload but loses a pending attachment when something reloads the record _during_ the save — closure_tree does exactly that in its `after_save` callback when it reorders the siblings of a numerically ordered tree, as the generated `Page` is. The file would then never be promoted out of the `cache` storage. The plugin keeps, across such a reload, the attachers whose file was saved with the record; an attachment assigned but not saved is still discarded.
|
|
34
|
+
|
|
35
|
+
## Files left in the cache storage
|
|
36
|
+
|
|
37
|
+
An uploaded file first lands in the temporary `cache` storage, and is promoted to the permanent `store` after the record is committed. A record whose promotion was skipped keeps pointing to the cache, and its file breaks as soon as the cache is cleaned up. To find those records, for every model and attachment, and promote the ones whose file still exists:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
DRY_RUN=1 bin/rails trek:attachments:promote_cached # only lists them
|
|
41
|
+
bin/rails trek:attachments:promote_cached
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Each line gives the status (`promoted`, `promotable` on a dry run, `missing` when the cached file is gone, `changed` when the record changed meanwhile), the record and attachment, and the file id. Missing files have to be uploaded again.
|
data/docs/what-is-trek.md
CHANGED
|
@@ -10,7 +10,7 @@ Trek ships as a Rails engine (a gem plus an NPM package) that integrates into yo
|
|
|
10
10
|
- **Pages** — hierarchical, translatable pages with version history, SEO metadata and stable, redirect-aware URL paths.
|
|
11
11
|
- **Fragments** — reusable chunks of content (a footer text, a hero tagline…) editable from the admin and usable anywhere in your views.
|
|
12
12
|
- **A rich text editor** — based on [Tiptap](https://tiptap.dev), with image uploads (Uppy), link insertion and content stored as structured ProseMirror JSON.
|
|
13
|
-
- **Authentication & authorization** — [
|
|
13
|
+
- **Authentication & authorization** — sign in built on Rails' [`has_secure_password`](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html) with database-backed sessions, password resets, invitations, and [ActionPolicy](https://actionpolicy.evilmartians.io) policies with `admin`, `editor`, `user`, `reader` and `guest` roles.
|
|
14
14
|
- **Internationalization** — content translations with [Mobility](https://github.com/shioyama/mobility), locale management with [i18n-tasks](https://github.com/glebm/i18n-tasks), and optional automatic translation through DeepL.
|
|
15
15
|
- **File uploads** — [Shrine](https://shrinerb.com)-backed attachments with S3 or local storage, image processing via libvips, and blurhash placeholders.
|
|
16
16
|
- **Generators** — an installer that sets up everything above, and a scaffold generator that creates a full admin panel for any model in one command.
|
|
@@ -14,7 +14,13 @@ module Trek
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def upload_image
|
|
17
|
-
|
|
17
|
+
# The endpoint `Trek::Form::ImageFieldComponent` uploads to: /admin/upload/image
|
|
18
|
+
route 'post "upload/image", to: "uploads#image"', namespace: :admin
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def upload_audio
|
|
22
|
+
# The endpoint `Trek::Form::SoundFieldComponent` uploads to: /admin/upload/audio
|
|
23
|
+
route 'post "upload/audio", to: "uploads#audio"', namespace: :admin
|
|
18
24
|
end
|
|
19
25
|
|
|
20
26
|
def lookbook
|