keystone_ui-pages 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/app/assets/builds/keystone_ui_pages/editor.js +67 -0
- data/app/controllers/concerns/keystone_ui/pages/edits_pages.rb +32 -0
- data/app/controllers/concerns/keystone_ui/pages/host_checks.rb +25 -0
- data/app/controllers/keystone_ui/pages/manage/base_controller.rb +28 -0
- data/app/controllers/keystone_ui/pages/manage/columns_controller.rb +15 -0
- data/app/controllers/keystone_ui/pages/manage/documents_controller.rb +15 -0
- data/app/controllers/keystone_ui/pages/manage/edit_history_controller.rb +21 -0
- data/app/controllers/keystone_ui/pages/manage/elements_controller.rb +30 -0
- data/app/controllers/keystone_ui/pages/manage/lives_controller.rb +20 -0
- data/app/controllers/keystone_ui/pages/manage/nodes_controller.rb +31 -0
- data/app/controllers/keystone_ui/pages/manage/pages_controller.rb +41 -0
- data/app/controllers/keystone_ui/pages/manage/pdfs_controller.rb +23 -0
- data/app/controllers/keystone_ui/pages/manage/rows_controller.rb +15 -0
- data/app/controllers/keystone_ui/pages/manage/samples_controller.rb +16 -0
- data/app/controllers/keystone_ui/pages/manage/sections_controller.rb +15 -0
- data/app/controllers/keystone_ui/pages/manage/versions_controller.rb +25 -0
- data/app/controllers/keystone_ui/pages/pages_controller.rb +26 -0
- data/app/helpers/keystone_ui/pages/drawing_helper.rb +115 -0
- data/app/helpers/keystone_ui/pages/editor_helper.rb +46 -0
- data/app/helpers/keystone_ui/pages/host_helper.rb +17 -0
- data/app/javascript/keystone_ui_pages/DeviceWidth.jsx +21 -0
- data/app/javascript/keystone_ui_pages/ElementFields.jsx +78 -0
- data/app/javascript/keystone_ui_pages/ElementPalette.jsx +62 -0
- data/app/javascript/keystone_ui_pages/LayoutPanel.jsx +41 -0
- data/app/javascript/keystone_ui_pages/ListField.jsx +49 -0
- data/app/javascript/keystone_ui_pages/LookChoice.jsx +17 -0
- data/app/javascript/keystone_ui_pages/PageEditor.jsx +260 -0
- data/app/javascript/keystone_ui_pages/SampleChoice.jsx +15 -0
- data/app/javascript/keystone_ui_pages/SelectionPath.jsx +19 -0
- data/app/javascript/keystone_ui_pages/SelectionToolbar.jsx +14 -0
- data/app/javascript/keystone_ui_pages/ShowWhen.jsx +32 -0
- data/app/javascript/keystone_ui_pages/ThemeMode.jsx +23 -0
- data/app/javascript/keystone_ui_pages/document.js +10 -0
- data/app/javascript/keystone_ui_pages/editor.jsx +8 -0
- data/app/javascript/keystone_ui_pages/operations.js +32 -0
- data/app/jobs/keystone_ui/pages/pdf_job.rb +11 -0
- data/app/models/keystone_ui/pages/page.rb +55 -0
- data/app/models/keystone_ui/pages/pdf.rb +12 -0
- data/app/models/keystone_ui/pages/version.rb +13 -0
- data/app/views/keystone_ui/pages/manage/pages/index.html.erb +35 -0
- data/app/views/keystone_ui/pages/manage/pages/new.html.erb +13 -0
- data/app/views/keystone_ui/pages/manage/pages/show.html.erb +8 -0
- data/app/views/keystone_ui/pages/manage/versions/index.html.erb +28 -0
- data/app/views/keystone_ui/pages/manage/versions/show.html.erb +11 -0
- data/app/views/keystone_ui/pages/pages/show.html.erb +3 -0
- data/app/views/keystone_ui/pages/pdfs/show.html.erb +12 -0
- data/config/routes.rb +31 -0
- data/db/migrate/20261014120000_create_keystone_ui_pages_pages.rb +17 -0
- data/db/migrate/20261014120001_create_keystone_ui_pages_versions.rb +18 -0
- data/db/migrate/20261015130000_create_keystone_ui_pages_pdfs.rb +14 -0
- data/db/migrate/20261016120000_add_edit_history_to_keystone_ui_pages_pages.rb +8 -0
- data/lib/keystone_ui/pages/catalog.rb +29 -0
- data/lib/keystone_ui/pages/document.rb +29 -0
- data/lib/keystone_ui/pages/engine.rb +25 -0
- data/lib/keystone_ui/pages/keystone_elements.rb +109 -0
- data/lib/keystone_ui/pages/operations.rb +246 -0
- data/lib/keystone_ui/pages/pdf_document.rb +17 -0
- data/lib/keystone_ui/pages/pdfs.rb +20 -0
- data/lib/keystone_ui/pages/printer.rb +43 -0
- data/lib/keystone_ui/pages/values.rb +28 -0
- data/lib/keystone_ui/pages/version.rb +7 -0
- data/lib/keystone_ui/pages.rb +45 -0
- data/lib/keystone_ui-pages.rb +3 -0
- data/the_local/agents/keystone_ui-pages-develop.md +277 -0
- data/the_local/agents/keystone_ui-pages-info.md +71 -0
- data/the_local/agents/keystone_ui-pages-install.md +167 -0
- data/the_local/interface.yml +56 -0
- metadata +169 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "keystone_ui/pages/version"
|
|
4
|
+
require "keystone_ui/pages/catalog"
|
|
5
|
+
require "keystone_ui/pages/values"
|
|
6
|
+
require "keystone_ui/pages/keystone_elements"
|
|
7
|
+
require "keystone_ui/pages/engine"
|
|
8
|
+
|
|
9
|
+
module KeystoneUi
|
|
10
|
+
module Pages
|
|
11
|
+
class << self
|
|
12
|
+
attr_writer :base_controller, :layout, :editor_layout, :pdf_stylesheet
|
|
13
|
+
attr_accessor :edit_check_method, :view_check_method, :owner_method, :refusal_method, :chrome_path
|
|
14
|
+
|
|
15
|
+
def base_controller
|
|
16
|
+
@base_controller || "ActionController::Base"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def layout
|
|
20
|
+
@layout || "application"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def editor_layout
|
|
24
|
+
@editor_layout || "application"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def pdf_stylesheet
|
|
28
|
+
@pdf_stylesheet || "tailwind.css"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.pdf(page, values: {})
|
|
33
|
+
require "keystone_ui/pages/pdfs"
|
|
34
|
+
Pdfs.for(page, values: values)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.prepare_pdf(page, values: {})
|
|
38
|
+
PdfJob.perform_later(page, values.to_h.stringify_keys)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.catalog
|
|
42
|
+
@catalog ||= Catalog.new.tap { |catalog| KeystoneElements.register(catalog) }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: keystone_ui-pages-develop
|
|
3
|
+
description: Use PROACTIVELY for working with Keystone UI Pages in a host app — its settings, host values, drawing a page in the host's own screens, making PDFs, linking editors to the builder, and adding or leaving out catalog elements — MUST BE USED instead of hand-building page drawing or PDF printing.
|
|
4
|
+
tools: Read, Write, Edit, Grep
|
|
5
|
+
scope: Pages — building, publishing and printing pages drawn with Keystone UI
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
This local works with Keystone UI Pages from the host app's side. It always
|
|
9
|
+
works the same way: set what the engine asks the host for, hand it values, and
|
|
10
|
+
draw or print the live version through the gem's own calls rather than reading
|
|
11
|
+
page documents directly.
|
|
12
|
+
|
|
13
|
+
## What Keystone UI Pages is
|
|
14
|
+
|
|
15
|
+
Keystone UI Pages is a Rails engine that adds a page builder to a host app. A
|
|
16
|
+
page is a JSON document of sections, rows, columns and elements, each element
|
|
17
|
+
drawn by a Keystone UI component. Editors change a page's draft in the builder,
|
|
18
|
+
and Publish saves it as a numbered live version. The host draws the live
|
|
19
|
+
version in its own screens, prints it to PDF, and supplies values such as a
|
|
20
|
+
client's name that bound fields and conditions read.
|
|
21
|
+
|
|
22
|
+
Fire on any request to draw a page in a host screen, make or send a page as a
|
|
23
|
+
PDF, give editors a link to the builder, supply values to pages, add a
|
|
24
|
+
host-drawn element or remove one from the builder, or change who may view or
|
|
25
|
+
edit pages. If the engine is not mounted or its migrations have not run, that
|
|
26
|
+
is the `keystone_ui-pages-install` local's job, not this one's.
|
|
27
|
+
|
|
28
|
+
## Interface
|
|
29
|
+
|
|
30
|
+
All settings are plain writers, set in `config/initializers/keystone_ui_pages.rb`.
|
|
31
|
+
Every method a setting names is called on the engine's controllers, which
|
|
32
|
+
inherit from the base controller, so define it there.
|
|
33
|
+
|
|
34
|
+
### Access and layout settings
|
|
35
|
+
|
|
36
|
+
- `KeystoneUi::Pages.base_controller = "ApplicationController"` — a string
|
|
37
|
+
naming the class every engine controller inherits from. Defaults to
|
|
38
|
+
`"ActionController::Base"`. Its filters run on every engine request,
|
|
39
|
+
including published pages.
|
|
40
|
+
- `KeystoneUi::Pages.owner_method = :current_team` — a method with no
|
|
41
|
+
arguments returning the record that owns pages, such as a team or account.
|
|
42
|
+
Every lookup, in the editor's screens and on published pages, is then limited
|
|
43
|
+
to `KeystoneUi::Pages::Page.where(owner: <that record>)`, and a new page is
|
|
44
|
+
created with it as owner. Unset, every page is visible to everyone and new
|
|
45
|
+
pages have no owner.
|
|
46
|
+
- `KeystoneUi::Pages.view_check_method = :may_view_page?` — a method taking the
|
|
47
|
+
page and returning true when the current visitor may see the published page
|
|
48
|
+
at `/pages/:slug`. Unset, every published page is shown.
|
|
49
|
+
- `KeystoneUi::Pages.edit_check_method = :may_edit_pages?` — a method with no
|
|
50
|
+
arguments returning true when the current user may use the editor's screens:
|
|
51
|
+
the page list, the builder, View live, History and the PDF link. It runs
|
|
52
|
+
before every one of those requests. Unset, everyone may.
|
|
53
|
+
- `KeystoneUi::Pages.refusal_method = :refuse_page` — a method with no
|
|
54
|
+
arguments that renders the response when a request is refused: a failed view
|
|
55
|
+
or edit check, an unknown slug, or a page with no live version. Unset, the
|
|
56
|
+
response is an empty 404.
|
|
57
|
+
- `KeystoneUi::Pages.layout = "application"` — the host layout published pages
|
|
58
|
+
and View live are drawn in. Defaults to `"application"`.
|
|
59
|
+
- `KeystoneUi::Pages.editor_layout = "editor"` — the host layout the page
|
|
60
|
+
list, the builder and the version history are drawn in. Defaults to
|
|
61
|
+
`"application"`.
|
|
62
|
+
|
|
63
|
+
### PDF settings
|
|
64
|
+
|
|
65
|
+
- `KeystoneUi::Pages.chrome_path = "/usr/bin/chromium"` — the Chrome or
|
|
66
|
+
Chromium program that prints PDFs. Unset, the gem uses the `CHROME_BIN`
|
|
67
|
+
environment variable, then the first of `/usr/bin/google-chrome`,
|
|
68
|
+
`/usr/bin/google-chrome-stable`, `/usr/bin/chromium`,
|
|
69
|
+
`/usr/bin/chromium-browser`, and the Google Chrome and Chromium apps in
|
|
70
|
+
`/Applications` that can be run.
|
|
71
|
+
- `KeystoneUi::Pages.pdf_stylesheet = "tailwind.css"` — the name of the host's
|
|
72
|
+
compiled stylesheet in Propshaft, written into every PDF. Defaults to
|
|
73
|
+
`"tailwind.css"`. A name Propshaft cannot find prints the PDF with no styles.
|
|
74
|
+
|
|
75
|
+
### Host values
|
|
76
|
+
|
|
77
|
+
- `KeystoneUi::Pages::Values.names = { "client_name" => "Client name" }` — a
|
|
78
|
+
hash of the value names the host supplies, each to the label the builder
|
|
79
|
+
shows. When it is not empty, an editor can bind a text, long text, address,
|
|
80
|
+
number, list or data field to one, with fallback text drawn when the value is
|
|
81
|
+
blank, and can show a section, row or element only when a value equals some
|
|
82
|
+
text.
|
|
83
|
+
- `KeystoneUi::Pages::Values.chooser = ->(page) { [["Acme report", "acme"]] }`
|
|
84
|
+
— a callable taking the page and returning `[label, choice]` pairs. The
|
|
85
|
+
builder shows them in a "Preview with" choice, opening on the first pair, and
|
|
86
|
+
remembers the editor's choice per page for the session. Unset, the choice is
|
|
87
|
+
not shown.
|
|
88
|
+
- `KeystoneUi::Pages::Values.provider = ->(page, choice) { { "client_name" => "Acme" } }`
|
|
89
|
+
— a callable taking the page and a choice from the chooser and returning a
|
|
90
|
+
hash of value names to values. The builder's canvas, View live and the page
|
|
91
|
+
list's PDF link draw the page with what it returns for the chosen choice.
|
|
92
|
+
Unset, they draw with no values.
|
|
93
|
+
|
|
94
|
+
### Drawing and printing
|
|
95
|
+
|
|
96
|
+
- `keystone_ui_page(page, values: {})` — a view helper, available in every host
|
|
97
|
+
view, that draws the page's live version with the values given, inside a
|
|
98
|
+
`<div class="@container">`. It returns nil when the page has never been
|
|
99
|
+
published. `values:` takes value names to values, with string or symbol keys.
|
|
100
|
+
- `KeystoneUi::Pages.pdf(page, values: {})` — returns a
|
|
101
|
+
`KeystoneUi::Pages::Pdf` record for the page's live version drawn with the
|
|
102
|
+
values given. Its `file` is an Active Storage attachment, ready to download,
|
|
103
|
+
link or email. The same live version and values return the record already
|
|
104
|
+
kept, and a newly published version or different values print a new one.
|
|
105
|
+
When nothing is kept it prints in the current request.
|
|
106
|
+
- `KeystoneUi::Pages.prepare_pdf(page, values: {})` — queues
|
|
107
|
+
`KeystoneUi::Pages::PdfJob`, which calls `KeystoneUi::Pages.pdf` with the same
|
|
108
|
+
page and values, so the file is kept before anyone asks for it.
|
|
109
|
+
|
|
110
|
+
### The builder and the editor's screens
|
|
111
|
+
|
|
112
|
+
- `/manage/pages` under the engine's mount path — the editor's page list, which
|
|
113
|
+
at the `/pages` mount is `/pages/manage/pages`, or
|
|
114
|
+
`keystone_ui_pages.manage_pages_path` from the host.
|
|
115
|
+
- `/pages/manage/pages/:id?return_to=/admin/marketing` — the builder for one
|
|
116
|
+
page, or `keystone_ui_pages.manage_page_path(page, return_to: "/admin/marketing")`.
|
|
117
|
+
`return_to` must be a path on the host's own site, starting with a single
|
|
118
|
+
`/`. The builder's Back link returns there, even after Publish reloads the
|
|
119
|
+
builder. With no `return_to`, or one pointing at another site, Back goes to
|
|
120
|
+
the page list.
|
|
121
|
+
|
|
122
|
+
### The element catalog
|
|
123
|
+
|
|
124
|
+
- `KeystoneUi::Pages.catalog.register(key:, name:, component:, fields:, body: nil, icon: nil, group: nil, align: nil)`
|
|
125
|
+
— adds an element to the builder's palette, or replaces the one with the same
|
|
126
|
+
key. `key:` is a symbol stored in page documents. `name:` is the label in the
|
|
127
|
+
palette. `component:` is the name of a method the drawing calls with one
|
|
128
|
+
keyword argument per field, such as a `helper_method` on the base controller.
|
|
129
|
+
`body:` names one field whose value is passed as the method's block instead
|
|
130
|
+
of a keyword. `icon:` is one of `:cursor` `:tag` `:copy` `:heading` `:text`
|
|
131
|
+
`:list` `:info` `:code` `:alert` `:card` `:megaphone` `:chart` `:bars`
|
|
132
|
+
`:number` `:progress`, and any other value shows a box. `group:` is the
|
|
133
|
+
palette heading it sits under, `"Other"` when unset. `align:` is the default
|
|
134
|
+
placement in its column: `"stretch"`, `"start"`, `"center"` or `"end"`.
|
|
135
|
+
- `KeystoneUi::Pages.catalog.leave_out(:copy_button)` — removes elements from
|
|
136
|
+
the palette by key. Takes several keys.
|
|
137
|
+
|
|
138
|
+
Each entry in `fields:` is a hash. Its `key:` is the keyword the component
|
|
139
|
+
receives and its `label:` is the label in the builder's field panel. Its
|
|
140
|
+
`kind:` is one of `:text` (the default when no kind is given), `:long_text`,
|
|
141
|
+
`:address`, `:number`, `:one_of`, `:yes_or_no`, `:list` or `:data`. A number
|
|
142
|
+
arrives as an Integer, or a Float when it has a decimal point, a `:one_of`
|
|
143
|
+
arrives as a symbol, and a `:yes_or_no` as true or false. Its `default:` is the
|
|
144
|
+
value used until the editor sets one. A `:one_of` field takes `options:`, the
|
|
145
|
+
list of choices as strings. A `:list` field takes `entry:`, an array of field
|
|
146
|
+
hashes describing each entry's parts, and each entry arrives as a hash with
|
|
147
|
+
symbol keys.
|
|
148
|
+
|
|
149
|
+
A `:data` field cannot be typed in the builder. It holds data the host
|
|
150
|
+
supplies through a bound value.
|
|
151
|
+
|
|
152
|
+
The catalog offers these Keystone UI components by default: `button`,
|
|
153
|
+
`progress`, `alert`, `code`, `heading`, `label`, `paragraph`, `hero`,
|
|
154
|
+
`cta_banner`, `card`, `stat_card`, `figure`, `info`, `badge`, `copy_button`,
|
|
155
|
+
`feature_grid`, `accordion`, `data_table`, `bar_chart`, `line_chart` and
|
|
156
|
+
`value_list`.
|
|
157
|
+
|
|
158
|
+
## How to use it
|
|
159
|
+
|
|
160
|
+
1. Confirm the engine is mounted and its migrations have run: `config/routes.rb`
|
|
161
|
+
mounts `KeystoneUi::Pages::Engine` and `db/schema.rb` has
|
|
162
|
+
`keystone_ui_pages_pages`. If not, stop and hand off to
|
|
163
|
+
`keystone_ui-pages-install`.
|
|
164
|
+
|
|
165
|
+
2. Read `config/initializers/keystone_ui_pages.rb` before changing a setting.
|
|
166
|
+
Every setting from the Interface is set there, and the methods they name are
|
|
167
|
+
on the controller named by `base_controller`.
|
|
168
|
+
|
|
169
|
+
3. To change who may see or edit pages, set `owner_method`,
|
|
170
|
+
`view_check_method`, `edit_check_method` or `refusal_method`, and write the
|
|
171
|
+
method on the base controller. Who may see and change what is the
|
|
172
|
+
developer's decision. Ask before choosing a rule.
|
|
173
|
+
|
|
174
|
+
4. To let pages use the host's data, set `Values.names` to the values the host
|
|
175
|
+
can supply. To let editors preview a page with real data, also set
|
|
176
|
+
`Values.chooser` and `Values.provider`. Ask the developer which records the
|
|
177
|
+
chooser lists, since it decides what editors see in the builder.
|
|
178
|
+
|
|
179
|
+
5. To draw a page in a host screen with one client's values, find the page and
|
|
180
|
+
call the helper:
|
|
181
|
+
|
|
182
|
+
```erb
|
|
183
|
+
<% page = KeystoneUi::Pages::Page.find_by(slug: "report", owner: current_team) %>
|
|
184
|
+
<%= keystone_ui_page(page, values: { "client_name" => @client.name, "score" => @client.score }) %>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The drawing is kept in `Rails.cache` under the version and the values, so
|
|
188
|
+
the same version and values are drawn once. A page that uses host values
|
|
189
|
+
should be drawn this way, since the published address `/pages/:slug` draws
|
|
190
|
+
it with no values: bound fields show their fallback text there, and
|
|
191
|
+
conditions on a value hide what they guard unless they compare against blank.
|
|
192
|
+
|
|
193
|
+
6. To make a PDF:
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
pdf = KeystoneUi::Pages.pdf(page, values: { "client_name" => "Acme" })
|
|
197
|
+
pdf.file
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
To have it ready before anyone downloads it, such as when an invoice is
|
|
201
|
+
created, call `KeystoneUi::Pages.prepare_pdf(page, values: ...)` instead and
|
|
202
|
+
read `KeystoneUi::Pages.pdf` later with the same values. Publishing a page
|
|
203
|
+
that uses no host values queues its PDF straight away, since it looks the
|
|
204
|
+
same for everyone.
|
|
205
|
+
|
|
206
|
+
The page must be published, since only the live version is printed. The PDF
|
|
207
|
+
is the page drawn in light mode with the stylesheet named by
|
|
208
|
+
`pdf_stylesheet`, printed by Chrome or Chromium with no header or footer.
|
|
209
|
+
Printing keeps chart and cell colours on a white background and does not
|
|
210
|
+
split a section, a table row or a label and value row across pages.
|
|
211
|
+
|
|
212
|
+
When no Chrome or Chromium is found, the page list leaves out its PDF links
|
|
213
|
+
and `KeystoneUi::Pages.pdf` raises `KeystoneUi::Pages::PrintFailed` with a
|
|
214
|
+
message saying to install one or name it with `chrome_path` or `CHROME_BIN`.
|
|
215
|
+
It raises the same error when Chrome cannot be started or prints nothing.
|
|
216
|
+
Called from `prepare_pdf`, the error is raised inside the job, so the job
|
|
217
|
+
backend's retry rules apply. Ask the developer how a failed print should
|
|
218
|
+
reach the user before rescuing it.
|
|
219
|
+
|
|
220
|
+
7. To give editors a way in, link to the page list, or to one page's builder
|
|
221
|
+
with a `return_to` of the screen they came from:
|
|
222
|
+
|
|
223
|
+
```erb
|
|
224
|
+
<%= link_to "Edit page", keystone_ui_pages.manage_page_path(page, return_to: request.fullpath) %>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
8. To add an element the host draws, write its component as a method that takes
|
|
228
|
+
the fields as keyword arguments, make it callable from views, and register
|
|
229
|
+
it:
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
Rails.application.config.after_initialize do
|
|
233
|
+
KeystoneUi::Pages.catalog.register(key: :testimonial, name: "Testimonial", component: :testimonial,
|
|
234
|
+
fields: [ { key: :quote, label: "Quote", kind: :long_text, default: "It works." } ])
|
|
235
|
+
KeystoneUi::Pages.catalog.leave_out(:copy_button)
|
|
236
|
+
end
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The method must be reachable in the engine's views and in any host view that
|
|
240
|
+
calls `keystone_ui_page`. A `helper_method` on the base controller covers
|
|
241
|
+
both. Leaving out an element that pages already use makes those pages fail
|
|
242
|
+
to draw, so search the pages' documents for its key before leaving it out.
|
|
243
|
+
|
|
244
|
+
## Conventions
|
|
245
|
+
|
|
246
|
+
- **Draw through the gem.** Use `keystone_ui_page` and `KeystoneUi::Pages.pdf`.
|
|
247
|
+
Never draw `page.document` or a version's document by hand, and never print a
|
|
248
|
+
PDF another way, because the gem's drawing applies bound values, conditions,
|
|
249
|
+
layout and print rules together.
|
|
250
|
+
- **Only the live version leaves the builder.** The draft is what editors
|
|
251
|
+
change. Visitors, `keystone_ui_page` and PDFs show the live version, so an
|
|
252
|
+
unpublished change shows nowhere else.
|
|
253
|
+
- **Value names are strings.** `Values.names`, the provider's hash and the
|
|
254
|
+
`values:` passed to the helper and to PDFs all use the same names. The gem
|
|
255
|
+
turns symbol keys into strings, so either works, but the names must match.
|
|
256
|
+
- **The editor's page list** at `/pages/manage/pages` shows each page's name,
|
|
257
|
+
whether it is published or a draft, and when it was last published. Each row
|
|
258
|
+
links to Edit, and a published page also links to View live and, when Chrome
|
|
259
|
+
or Chromium is found, to its PDF.
|
|
260
|
+
- **View live** draws the live version in `KeystoneUi::Pages.layout` with the
|
|
261
|
+
values of the sample chosen in the builder, behind the edit check. It is
|
|
262
|
+
linked from the page list and from the builder's top bar once the page is
|
|
263
|
+
published.
|
|
264
|
+
- **History** lists every published version with its number, status and date.
|
|
265
|
+
Opening one draws it, and "Restore this version as the draft" replaces the
|
|
266
|
+
draft with that version's document. Restoring publishes nothing.
|
|
267
|
+
- **Undo and Redo** are the builder's ↶ Undo and ↷ Redo buttons. They step
|
|
268
|
+
through the page's edits one at a time. The history is kept on the page, so
|
|
269
|
+
it survives a reload and a publish, and a new edit after an undo clears what
|
|
270
|
+
can be redone.
|
|
271
|
+
- **The builder's top bar** also has a Mode choice of light, dark and system
|
|
272
|
+
(and custom for an editor in custom mode), a Look choice of the looks the host
|
|
273
|
+
registers with Keystone UI, and a Print choice that previews the printed
|
|
274
|
+
layout. None of them is saved.
|
|
275
|
+
- Out of scope for this local: installing the gem, its migrations, the mount and
|
|
276
|
+
the layouts, which belong to `keystone_ui-pages-install`. Changing the
|
|
277
|
+
builder or the built-in elements belongs in the gem, not the host app.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: keystone_ui-pages-info
|
|
3
|
+
description: Use to learn what Keystone UI Pages offers — the page builder, published versions, host values, PDFs and the element catalog — and the vocabulary the install and develop locals assume.
|
|
4
|
+
tools: Read
|
|
5
|
+
scope: Pages — building, publishing and printing pages drawn with Keystone UI
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
This local explains Keystone UI Pages and sends you to the local that does the
|
|
9
|
+
work. It changes nothing and gives no steps.
|
|
10
|
+
|
|
11
|
+
## What Keystone UI Pages is
|
|
12
|
+
|
|
13
|
+
Keystone UI Pages is a Rails engine gem that adds a page builder to a host app.
|
|
14
|
+
A page is a JSON document of sections, rows, columns and elements, and every
|
|
15
|
+
element is drawn by a Keystone UI component, so a page looks like the rest of an
|
|
16
|
+
app built with Keystone UI. Editors change the draft in a builder in the
|
|
17
|
+
browser, and publishing it saves a numbered version. The live version is what
|
|
18
|
+
visitors, the host's own screens and PDFs show.
|
|
19
|
+
|
|
20
|
+
Reach for it when an app needs pages that people other than developers lay out
|
|
21
|
+
and change: marketing pages, report templates, invoices. A page can hold values
|
|
22
|
+
the host app supplies, such as a client's name, so one page serves as a template
|
|
23
|
+
drawn with each client's values, on screen or as a PDF.
|
|
24
|
+
|
|
25
|
+
## Interface
|
|
26
|
+
|
|
27
|
+
This local declares no commands. The two working surfaces belong elsewhere:
|
|
28
|
+
|
|
29
|
+
- **Getting the gem into a host app** — adding it, running the engine's and
|
|
30
|
+
Active Storage's migrations, mounting the engine, the layouts and the settings
|
|
31
|
+
file → **`keystone_ui-pages-install`**.
|
|
32
|
+
- **Working with it** — what each setting does, host values, drawing a page in
|
|
33
|
+
the host's own screens, PDFs, linking to the builder and changing the element
|
|
34
|
+
catalog → **`keystone_ui-pages-develop`**.
|
|
35
|
+
|
|
36
|
+
## How to use it
|
|
37
|
+
|
|
38
|
+
One decision: is Keystone UI Pages already set up in the app?
|
|
39
|
+
|
|
40
|
+
- No, or the setup is out of date → **`keystone_ui-pages-install`**.
|
|
41
|
+
- Yes, and there is a page to draw, a PDF to make, a setting to change or an
|
|
42
|
+
element to add → **`keystone_ui-pages-develop`**.
|
|
43
|
+
|
|
44
|
+
Keystone UI itself must be installed first. Its own install local,
|
|
45
|
+
`keystone_ui-install`, does that.
|
|
46
|
+
|
|
47
|
+
## Conventions
|
|
48
|
+
|
|
49
|
+
- **Draft and versions.** A page's document is its draft. Publish copies the
|
|
50
|
+
draft into a new version marked live and marks the previous live version
|
|
51
|
+
superseded. Only the live version is ever shown outside the builder. A page
|
|
52
|
+
with no live version shows nothing.
|
|
53
|
+
- **Pages belong to an owner.** A page has a name, a slug that is its address,
|
|
54
|
+
and an optional owner, which is any record. When the host names an owner
|
|
55
|
+
method, every lookup is limited to the current owner's pages, and a slug is
|
|
56
|
+
unique per owner.
|
|
57
|
+
- **Host values.** The host names the values a page can use, such as a client's
|
|
58
|
+
name or a score. An editor binds an element's field to one, with fallback text
|
|
59
|
+
for when it is blank, or shows a section, row or element only when a value
|
|
60
|
+
equals something. The host supplies the values when it draws the page.
|
|
61
|
+
- **The catalog.** The builder offers a fixed list of Keystone UI components as
|
|
62
|
+
elements. A host adds elements drawn by its own components and leaves out ones
|
|
63
|
+
it does not want. Navigation, form inputs, page frames and components whose
|
|
64
|
+
data the catalog cannot describe are not offered.
|
|
65
|
+
- **PDFs are printed by Chrome or Chromium on the server** and kept in Active
|
|
66
|
+
Storage, one per live version and set of values.
|
|
67
|
+
- **The editor's screens.** The page list, the builder, the version history and
|
|
68
|
+
View live all sit under the engine's `manage` routes and are open to whoever
|
|
69
|
+
passes the host's edit check.
|
|
70
|
+
- Ruby >= 3.2. Rails >= 7.1. It depends on keystone_ui >= 0.49.0,
|
|
71
|
+
keystone_ui-styles >= 0.24.0 and keystone_ui-react >= 0.1.3.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: keystone_ui-pages-install
|
|
3
|
+
description: Use to hook Keystone UI Pages into a project — adding the gem, installing and running the engine's migrations and Active Storage's, mounting the engine, preparing the live-page and editor layouts, and writing the settings file.
|
|
4
|
+
tools: Bash, Read, Edit
|
|
5
|
+
scope: Pages — building, publishing and printing pages drawn with Keystone UI
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
This local carries the steps for wiring Keystone UI Pages into a host app.
|
|
9
|
+
Follow them in order, exactly as written, and invent none.
|
|
10
|
+
|
|
11
|
+
## What Keystone UI Pages is
|
|
12
|
+
|
|
13
|
+
A Rails engine gem that adds a page builder whose pages are drawn with Keystone
|
|
14
|
+
UI components, published as versions and printed to PDF; hook it in before
|
|
15
|
+
linking editors to the builder or drawing a page in the host's screens.
|
|
16
|
+
|
|
17
|
+
## Interface
|
|
18
|
+
|
|
19
|
+
- `bin/rails keystone_ui_pages:install:migrations` — copies the engine's
|
|
20
|
+
migrations into the host's `db/migrate`. They create the pages, versions and
|
|
21
|
+
PDFs tables and add the edit history columns that Undo and Redo use. Safe to
|
|
22
|
+
re-run, since a migration already copied is skipped.
|
|
23
|
+
- `bin/rails active_storage:install` — Rails' own task that copies Active
|
|
24
|
+
Storage's migration. PDFs are kept as Active Storage attachments.
|
|
25
|
+
- `mount KeystoneUi::Pages::Engine => "/pages"` — the line in
|
|
26
|
+
`config/routes.rb` that serves the builder, the editor's screens and the live
|
|
27
|
+
pages under the path given.
|
|
28
|
+
|
|
29
|
+
## How to use it
|
|
30
|
+
|
|
31
|
+
1. Confirm the prerequisites:
|
|
32
|
+
|
|
33
|
+
- Ruby >= 3.2 and Rails >= 7.1.
|
|
34
|
+
- Keystone UI installed and working. The gem brings keystone_ui,
|
|
35
|
+
keystone_ui-styles and keystone_ui-react with it, but Keystone UI's own
|
|
36
|
+
setup is the `keystone_ui-install` local's job. Do it first if it is
|
|
37
|
+
missing.
|
|
38
|
+
- Propshaft serving the host's stylesheet, since PDFs read the compiled
|
|
39
|
+
stylesheet from it.
|
|
40
|
+
- An Active Job backend. Publishing a page that uses no host values makes its
|
|
41
|
+
PDF in a job, and so does `KeystoneUi::Pages.prepare_pdf`.
|
|
42
|
+
|
|
43
|
+
2. Add the gem to the Gemfile and install it:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
gem "keystone_ui-pages"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
bundle install
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If the project sources its own gems from somewhere other than RubyGems (a git
|
|
54
|
+
or path reference), ask the developer which to use rather than choosing.
|
|
55
|
+
|
|
56
|
+
3. Install the migrations and run them:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
bin/rails keystone_ui_pages:install:migrations
|
|
60
|
+
bin/rails active_storage:install
|
|
61
|
+
bin/rails db:migrate
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Skip `active_storage:install` when the app already has the
|
|
65
|
+
`active_storage_blobs` table. Active Storage also needs a service set in each
|
|
66
|
+
environment's `config.active_storage.service` and defined in
|
|
67
|
+
`config/storage.yml`. If the app has none, ask the developer which service
|
|
68
|
+
production uses before choosing one.
|
|
69
|
+
|
|
70
|
+
The engine's migrations create `keystone_ui_pages_pages`,
|
|
71
|
+
`keystone_ui_pages_versions` and `keystone_ui_pages_pdfs`, and add
|
|
72
|
+
`undo_history` and `undone_changes` to the pages table. Without those two
|
|
73
|
+
columns, every edit in the builder fails, since each edit is recorded for
|
|
74
|
+
Undo and Redo.
|
|
75
|
+
|
|
76
|
+
4. Mount the engine in `config/routes.rb`:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
mount KeystoneUi::Pages::Engine => "/pages"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Ask the developer which path to use; `/pages` is the README's choice and the
|
|
83
|
+
one the other locals assume. With it:
|
|
84
|
+
|
|
85
|
+
- `/pages/manage/pages` is the editor's page list.
|
|
86
|
+
- `/pages/manage/pages/new` creates a page.
|
|
87
|
+
- `/pages/manage/pages/:id` is the builder for one page.
|
|
88
|
+
- `/pages/:slug` is a published page, drawn for visitors.
|
|
89
|
+
|
|
90
|
+
Inside the host app, the engine's route helpers are reached through
|
|
91
|
+
`keystone_ui_pages`, such as `keystone_ui_pages.manage_pages_path`.
|
|
92
|
+
|
|
93
|
+
5. Prepare the two layouts. Live pages are drawn in the layout named by
|
|
94
|
+
`KeystoneUi::Pages.layout`, and the page list, the builder and the version
|
|
95
|
+
history in the one named by `KeystoneUi::Pages.editor_layout`. Both default
|
|
96
|
+
to `application`. Each layout used must:
|
|
97
|
+
|
|
98
|
+
- add `<%= keystone_theme_attributes %>` inside its `<html` tag, so the page
|
|
99
|
+
follows the viewer's light, dark or custom mode and look;
|
|
100
|
+
- link the host's stylesheet, which carries keystone_ui-styles, the host's
|
|
101
|
+
looks and colours, and its own styles;
|
|
102
|
+
- render `<%= yield :head %>` inside `<head>`, because the builder puts its
|
|
103
|
+
two script tags, `keystone_ui/react` and `keystone_ui_pages/editor`, there.
|
|
104
|
+
|
|
105
|
+
The builder fills the whole window, so the host's own navigation in the
|
|
106
|
+
editor layout sits under it. The gem's test app uses a separate `editor`
|
|
107
|
+
layout holding only the head and the page's content. Ask the developer
|
|
108
|
+
whether to make one or use `application` for both.
|
|
109
|
+
|
|
110
|
+
6. Write `config/initializers/keystone_ui_pages.rb`. Each setting is described
|
|
111
|
+
in `keystone_ui-pages-develop`. Ask the developer about each rather than
|
|
112
|
+
copying this whole block:
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
require "keystone_ui/pages"
|
|
116
|
+
|
|
117
|
+
KeystoneUi::Pages.base_controller = "ApplicationController"
|
|
118
|
+
KeystoneUi::Pages.owner_method = :current_team
|
|
119
|
+
KeystoneUi::Pages.view_check_method = :may_view_page?
|
|
120
|
+
KeystoneUi::Pages.edit_check_method = :may_edit_pages?
|
|
121
|
+
KeystoneUi::Pages.refusal_method = :refuse_page
|
|
122
|
+
KeystoneUi::Pages.layout = "application"
|
|
123
|
+
KeystoneUi::Pages.editor_layout = "editor"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- `base_controller` is a string, read when the engine's controllers load, so
|
|
127
|
+
it belongs in this file. Set it to the controller that defines the methods
|
|
128
|
+
the other settings name, normally `ApplicationController`. Its filters,
|
|
129
|
+
such as a sign-in requirement, then run on every engine request, including
|
|
130
|
+
published pages.
|
|
131
|
+
- Without `edit_check_method`, anyone who can reach `/pages/manage` can
|
|
132
|
+
create, edit and publish pages. Without `view_check_method`, every
|
|
133
|
+
published page is public. Without `owner_method`, all pages are shared
|
|
134
|
+
across the whole app. Each of these is a decision about who may see and
|
|
135
|
+
change what, so put each to the developer.
|
|
136
|
+
- Host values, catalog changes, `chrome_path` and `pdf_stylesheet` go in the
|
|
137
|
+
same file once the developer wants them. The develop local carries them.
|
|
138
|
+
|
|
139
|
+
7. Install Chrome or Chromium wherever the app makes PDFs, including the
|
|
140
|
+
production server or image. The gem looks for `KeystoneUi::Pages.chrome_path`,
|
|
141
|
+
then the `CHROME_BIN` environment variable, then these places:
|
|
142
|
+
`/usr/bin/google-chrome`, `/usr/bin/google-chrome-stable`,
|
|
143
|
+
`/usr/bin/chromium`, `/usr/bin/chromium-browser`, and the Google Chrome and
|
|
144
|
+
Chromium apps in `/Applications` on macOS. Ask the developer how the
|
|
145
|
+
deployment installs it. Without one, everything except PDFs still works.
|
|
146
|
+
|
|
147
|
+
8. Check the install. Boot the app, open `/pages/manage/pages`, create a page,
|
|
148
|
+
add an element in the builder, publish it, and open `/pages/<slug>`. The page
|
|
149
|
+
list shows a PDF link only when Chrome or Chromium was found.
|
|
150
|
+
|
|
151
|
+
## Conventions
|
|
152
|
+
|
|
153
|
+
- **Re-run the migration install after upgrading the gem.** A later version can
|
|
154
|
+
add migrations, as the edit history one was added, and they reach the host
|
|
155
|
+
only through `bin/rails keystone_ui_pages:install:migrations` followed by
|
|
156
|
+
`bin/rails db:migrate`.
|
|
157
|
+
- **The engine registers itself with Keystone UI's Tailwind build.** On boot it
|
|
158
|
+
appends its views, helpers, JavaScript and Ruby files to Keystone UI's
|
|
159
|
+
Tailwind sources, so the host adds nothing to its Tailwind setup for the
|
|
160
|
+
engine.
|
|
161
|
+
- **The engine adds `keystone_ui_page` to every host view.** No helper include
|
|
162
|
+
is needed in the host.
|
|
163
|
+
- **The methods the settings name live on the base controller.** The engine
|
|
164
|
+
calls them on its own controllers, which inherit from it, so a method defined
|
|
165
|
+
anywhere else is not found.
|
|
166
|
+
- Drawing pages, making PDFs, host values and the catalog are out of scope here
|
|
167
|
+
and belong to `keystone_ui-pages-develop`.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
scope: Pages — building, publishing and printing pages drawn with Keystone UI
|
|
2
|
+
|
|
3
|
+
install:
|
|
4
|
+
- bin/rails keystone_ui_pages:install:migrations
|
|
5
|
+
- bin/rails active_storage:install
|
|
6
|
+
- mount KeystoneUi::Pages::Engine
|
|
7
|
+
|
|
8
|
+
develop:
|
|
9
|
+
- KeystoneUi::Pages.base_controller
|
|
10
|
+
- KeystoneUi::Pages.owner_method
|
|
11
|
+
- KeystoneUi::Pages.view_check_method
|
|
12
|
+
- KeystoneUi::Pages.edit_check_method
|
|
13
|
+
- KeystoneUi::Pages.refusal_method
|
|
14
|
+
- KeystoneUi::Pages.layout
|
|
15
|
+
- KeystoneUi::Pages.editor_layout
|
|
16
|
+
- KeystoneUi::Pages.chrome_path
|
|
17
|
+
- KeystoneUi::Pages.pdf_stylesheet
|
|
18
|
+
- KeystoneUi::Pages::Values.names
|
|
19
|
+
- KeystoneUi::Pages::Values.chooser
|
|
20
|
+
- KeystoneUi::Pages::Values.provider
|
|
21
|
+
- keystone_ui_page
|
|
22
|
+
- KeystoneUi::Pages.pdf
|
|
23
|
+
- KeystoneUi::Pages.prepare_pdf
|
|
24
|
+
- KeystoneUi::Pages.catalog.register
|
|
25
|
+
- KeystoneUi::Pages.catalog.leave_out
|
|
26
|
+
- /manage/pages
|
|
27
|
+
|
|
28
|
+
sources:
|
|
29
|
+
- README.md
|
|
30
|
+
- config/routes.rb
|
|
31
|
+
- lib/keystone_ui/pages.rb
|
|
32
|
+
- lib/keystone_ui/pages/engine.rb
|
|
33
|
+
- lib/keystone_ui/pages/values.rb
|
|
34
|
+
- lib/keystone_ui/pages/catalog.rb
|
|
35
|
+
- lib/keystone_ui/pages/keystone_elements.rb
|
|
36
|
+
- lib/keystone_ui/pages/pdfs.rb
|
|
37
|
+
- lib/keystone_ui/pages/pdf_document.rb
|
|
38
|
+
- lib/keystone_ui/pages/printer.rb
|
|
39
|
+
- app/helpers/keystone_ui/pages/host_helper.rb
|
|
40
|
+
- app/helpers/keystone_ui/pages/drawing_helper.rb
|
|
41
|
+
- app/helpers/keystone_ui/pages/editor_helper.rb
|
|
42
|
+
- app/controllers/concerns/keystone_ui/pages/host_checks.rb
|
|
43
|
+
- app/controllers/keystone_ui/pages/pages_controller.rb
|
|
44
|
+
- app/controllers/keystone_ui/pages/manage/base_controller.rb
|
|
45
|
+
- app/controllers/keystone_ui/pages/manage/pages_controller.rb
|
|
46
|
+
- app/controllers/keystone_ui/pages/manage/lives_controller.rb
|
|
47
|
+
- app/controllers/keystone_ui/pages/manage/pdfs_controller.rb
|
|
48
|
+
- app/controllers/keystone_ui/pages/manage/versions_controller.rb
|
|
49
|
+
- app/controllers/keystone_ui/pages/manage/edit_history_controller.rb
|
|
50
|
+
- app/models/keystone_ui/pages/page.rb
|
|
51
|
+
- app/models/keystone_ui/pages/pdf.rb
|
|
52
|
+
- app/jobs/keystone_ui/pages/pdf_job.rb
|
|
53
|
+
- db/migrate/20261014120000_create_keystone_ui_pages_pages.rb
|
|
54
|
+
- db/migrate/20261014120001_create_keystone_ui_pages_versions.rb
|
|
55
|
+
- db/migrate/20261015130000_create_keystone_ui_pages_pdfs.rb
|
|
56
|
+
- db/migrate/20261016120000_add_edit_history_to_keystone_ui_pages_pages.rb
|