keystone_ui-pages 0.1.0 → 0.2.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.
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KeystoneUi
4
+ module Pages
5
+ module Manage
6
+ class AccessExceptionsController < BaseController
7
+ include EditsPages
8
+
9
+ def update
10
+ if page.update(access_exception: params[:access_exception].presence)
11
+ shown
12
+ else
13
+ render json: { error: page.errors.full_messages.to_sentence }, status: :unprocessable_entity
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -7,7 +7,8 @@ module KeystoneUi
7
7
  module Manage
8
8
  class PagesController < BaseController
9
9
  def index
10
- @pages = owned_pages.order(:name)
10
+ @archived = params[:archived].present?
11
+ @pages = owned_pages.public_send(@archived ? :archived : :current).order(:name)
11
12
  @printable = Printer.chrome.present?
12
13
  end
13
14
 
@@ -15,7 +16,7 @@ module KeystoneUi
15
16
  end
16
17
 
17
18
  def create
18
- page = Page.create!(params.require(:page).permit(:name, :slug).merge(owner: page_owner))
19
+ page = Page.create!(params.require(:page).permit(:name, :slug, :place).merge(owner: page_owner))
19
20
  redirect_to manage_page_path(page)
20
21
  end
21
22
 
@@ -30,6 +31,22 @@ module KeystoneUi
30
31
  redirect_to manage_page_path(page)
31
32
  end
32
33
 
34
+ def unpublish
35
+ page = owned_pages.find(params[:id])
36
+ page.unpublish
37
+ redirect_to manage_page_path(page)
38
+ end
39
+
40
+ def archive
41
+ owned_pages.find(params[:id]).archive
42
+ redirect_to manage_pages_path
43
+ end
44
+
45
+ def restore
46
+ owned_pages.find(params[:id]).restore
47
+ redirect_to manage_pages_path
48
+ end
49
+
33
50
  private
34
51
 
35
52
  def local_address?(address)
@@ -11,11 +11,17 @@ module KeystoneUi
11
11
  def show
12
12
  page = owned_pages.find_by(slug: params[:slug])
13
13
  @version = page&.live_version
14
- refuse if @version.nil? || !viewable?(page)
14
+ return refuse if @version.nil? || !viewable?(page)
15
+
16
+ @values = visitor_values(page)
15
17
  end
16
18
 
17
19
  private
18
20
 
21
+ def visitor_values(page)
22
+ send(KeystoneUi::Pages.values_method, page) if KeystoneUi::Pages.values_method
23
+ end
24
+
19
25
  def viewable?(page)
20
26
  return true unless KeystoneUi::Pages.view_check_method
21
27
 
@@ -23,6 +23,9 @@ module KeystoneUi
23
23
  samples: Values.samples_for(page),
24
24
  sample: chosen_sample(page),
25
25
  samples_url: manage_page_sample_path(page),
26
+ access_exceptions: KeystoneUi::Pages.access_exceptions.invert.to_a,
27
+ access_exception: page.access_exception,
28
+ access_exception_url: manage_page_access_exception_path(page),
26
29
  history_url: manage_page_versions_path(page),
27
30
  undo_url: manage_page_undo_path(page),
28
31
  redo_url: manage_page_redo_path(page),
@@ -0,0 +1,16 @@
1
+ import React from "react"
2
+ import Select from "keystone_ui-react/src/Select.jsx"
3
+
4
+ export default function AccessExceptionChoice({ exceptions, chosen, onChoose }) {
5
+ if (exceptions.length === 0) return null
6
+
7
+ return (
8
+ <label className="flex items-center gap-2 text-sm">
9
+ <span className="whitespace-nowrap">Access exception</span>
10
+ <Select defaultValue={chosen ?? ""} onChange={(event) => onChoose(event.target.value)} className="py-1">
11
+ <option value="">None</option>
12
+ {exceptions.map(([label, value]) => <option key={value} value={value}>{label}</option>)}
13
+ </Select>
14
+ </label>
15
+ )
16
+ }
@@ -2,13 +2,14 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from "react"
2
2
  import { DragDropManager, Draggable, Droppable, KeyboardSensor, PointerSensor } from "@dnd-kit/dom"
3
3
  import { Sortable } from "@dnd-kit/dom/sortable"
4
4
  import Button from "keystone_ui-react/src/Button.jsx"
5
- import { addColumn, addElement, addRow, addSection, arrange, bindField, chooseSample, duplicateNode, moveNode, removeNode, setField, showWhen, stepHistory } from "./operations"
5
+ import { addColumn, addElement, addRow, addSection, arrange, bindField, chooseAccessException, chooseSample, duplicateNode, moveNode, removeNode, setField, showWhen, stepHistory } from "./operations"
6
6
  import LayoutPanel from "./LayoutPanel"
7
7
  import DeviceWidth, { DEVICES } from "./DeviceWidth"
8
8
  import SelectionToolbar from "./SelectionToolbar"
9
9
  import ElementPalette from "./ElementPalette"
10
10
  import SelectionPath from "./SelectionPath"
11
11
  import SampleChoice from "./SampleChoice"
12
+ import AccessExceptionChoice from "./AccessExceptionChoice"
12
13
  import ShowWhen from "./ShowWhen"
13
14
  import ThemeMode from "./ThemeMode"
14
15
  import LookChoice from "./LookChoice"
@@ -28,7 +29,7 @@ const innermost = (target) => NODES.map(([selector, kind]) => [target.closest(se
28
29
 
29
30
  const PLACED_SENSORS = [PointerSensor.configure({ preventActivation: () => false }), KeyboardSensor]
30
31
 
31
- export default function PageEditor({ name, publish_url, elements, canvas: drawn, document: saved, elements_url, sections_url, columns_url, rows_url, nodes_url, arrangements, defaults, shares, value_names, looks = [], samples = [], sample, samples_url, history_url, live_url, back_url, undo_url, redo_url, undoable: couldUndo, redoable: couldRedo, token }) {
32
+ export default function PageEditor({ name, publish_url, unpublish_url, elements, canvas: drawn, document: saved, elements_url, sections_url, columns_url, rows_url, nodes_url, arrangements, defaults, shares, value_names, looks = [], samples = [], sample, samples_url, access_exceptions = [], access_exception, access_exception_url, history_url, live_url, back_url, undo_url, redo_url, undoable: couldUndo, redoable: couldRedo, token }) {
32
33
  const [canvas, setCanvas] = useState(drawn)
33
34
  const [undoable, setUndoable] = useState(couldUndo)
34
35
  const [redoable, setRedoable] = useState(couldRedo)
@@ -210,12 +211,19 @@ export default function PageEditor({ name, publish_url, elements, canvas: drawn,
210
211
  <ThemeMode />
211
212
  <LookChoice looks={looks} />
212
213
  <SampleChoice samples={samples} chosen={sample} onChoose={(choice) => chooseSample(samples_url, token, choice).then(shown, refused)} />
214
+ <AccessExceptionChoice exceptions={access_exceptions} chosen={access_exception} onChoose={(choice) => chooseAccessException(access_exception_url, token, choice).then(shown, refused)} />
213
215
  </div>
214
216
  <div className="flex items-center gap-2">
215
217
  <Button type="button" size="sm" variant="secondary" disabled={!undoable} title={undoable ? "Undo the last change" : "Nothing to undo"} onClick={() => stepHistory(undo_url, token).then(shown, refused)}>↶ Undo</Button>
216
218
  <Button type="button" size="sm" variant="secondary" disabled={!redoable} title={redoable ? "Redo the change you undid" : "Nothing to redo"} onClick={() => stepHistory(redo_url, token).then(shown, refused)}>↷ Redo</Button>
217
219
  {live_url && <a href={live_url} className="text-sm whitespace-nowrap hover:underline">View live</a>}
218
220
  <a href={history_url} className="text-sm hover:underline">History</a>
221
+ {unpublish_url && (
222
+ <form method="post" action={unpublish_url}>
223
+ <input type="hidden" name="authenticity_token" value={token} />
224
+ <Button type="submit" size="sm" variant="secondary">Unpublish</Button>
225
+ </form>
226
+ )}
219
227
  <form method="post" action={publish_url}>
220
228
  <input type="hidden" name="authenticity_token" value={token} />
221
229
  <Button type="submit" size="sm">Publish</Button>
@@ -27,6 +27,8 @@ export const bindField = (url, token, elementId, field, name, fallback) => sent(
27
27
 
28
28
  export const chooseSample = (url, token, choice) => sent(url, token, "POST", { choice })
29
29
 
30
+ export const chooseAccessException = (url, token, accessException) => sent(url, token, "POST", { access_exception: accessException })
31
+
30
32
  export const showWhen = (url, token, nodeId, name, equals) => sent(`${url}/${nodeId}/condition`, token, "POST", { name, equals })
31
33
 
32
34
  export const stepHistory = (url, token) => sent(url, token, "POST")
@@ -14,9 +14,16 @@ module KeystoneUi
14
14
  attribute :undo_history, :json, default: -> { [] }
15
15
  attribute :undone_changes, :json, default: -> { [] }
16
16
 
17
+ scope :archived, -> { where.not(archived_at: nil) }
18
+ scope :current, -> { where(archived_at: nil) }
19
+
17
20
  validates :name, :slug, presence: true
21
+ validates :place, inclusion: { in: -> { KeystoneUi::Pages.places.keys } }, allow_nil: true, if: -> { KeystoneUi::Pages.places.any? }
22
+ validates :access_exception, inclusion: { in: -> { KeystoneUi::Pages.access_exceptions.keys } }, allow_nil: true, if: -> { KeystoneUi::Pages.access_exceptions.any? }
18
23
 
19
24
  def publish
25
+ return if archived_at
26
+
20
27
  transaction do
21
28
  live_version&.update!(status: :superseded)
22
29
  versions.create!(number: next_number, document: document, status: :live)
@@ -43,6 +50,29 @@ module KeystoneUi
43
50
  update!(document: undone_changes.last, undone_changes: undone_changes[0...-1], undo_history: undo_history + [ document ])
44
51
  end
45
52
 
53
+ def unpublish
54
+ live_version&.update!(status: :superseded)
55
+ end
56
+
57
+ def archive
58
+ transaction do
59
+ unpublish
60
+ update!(archived_at: Time.current)
61
+ end
62
+ end
63
+
64
+ def restore
65
+ update!(archived_at: nil)
66
+ end
67
+
68
+ def state
69
+ return :archived if archived_at
70
+ return :published if live_version
71
+ return :unpublished if versions.exists?
72
+
73
+ :draft
74
+ end
75
+
46
76
  def live_version
47
77
  versions.find_by(status: :live)
48
78
  end
@@ -1,5 +1,11 @@
1
1
  <%= ui_page(max_width: :xl) do %>
2
- <%= ui_page_header(title: "Pages") %>
2
+ <%= ui_page_header(title: @archived ? "Archived pages" : "Pages", action_url: (new_manage_page_path unless @archived), action_label: "Create") do |header| %>
3
+ <% header.action { ui_button(label: "Create", href: new_manage_page_path) } unless @archived %>
4
+ <% end %>
5
+
6
+ <p class="mb-4 text-sm">
7
+ <%= @archived ? link_to("Show current pages", manage_pages_path) : link_to("Show archived", manage_pages_path(archived: 1)) %>
8
+ </p>
3
9
 
4
10
  <div class="ks-table overflow-x-auto">
5
11
  <table class="relative min-w-full divide-y divide-gray-300 dark:divide-white/15">
@@ -8,6 +14,12 @@
8
14
  <th class="ks-table-header ks-table-header-first text-left text-sm">Name</th>
9
15
  <th class="ks-table-header ks-table-header-middle text-left text-sm">Status</th>
10
16
  <th class="ks-table-header ks-table-header-middle text-left text-sm">Last published</th>
17
+ <% if KeystoneUi::Pages.places.any? %>
18
+ <th class="ks-table-header ks-table-header-middle text-left text-sm">Place</th>
19
+ <% end %>
20
+ <% if KeystoneUi::Pages.access_exceptions.any? %>
21
+ <th class="ks-table-header ks-table-header-middle text-left text-sm">Access exception</th>
22
+ <% end %>
11
23
  <th class="ks-table-header ks-table-header-last text-right text-sm"><span class="sr-only">Open</span></th>
12
24
  </tr>
13
25
  </thead>
@@ -16,8 +28,14 @@
16
28
  <% live = page.live_version %>
17
29
  <tr data-page="<%= page.slug %>">
18
30
  <td class="ks-table-cell-first text-sm whitespace-nowrap"><%= page.name %></td>
19
- <td class="ks-table-cell-middle text-sm whitespace-nowrap"><%= ui_badge(label: live ? "Published" : "Draft", variant: live ? :success : :neutral) %></td>
31
+ <td class="ks-table-cell-middle text-sm whitespace-nowrap"><%= ui_badge(label: page.state.to_s.capitalize, variant: live ? :success : :neutral) %></td>
20
32
  <td class="ks-table-cell-middle text-sm whitespace-nowrap"><%= live&.created_at&.to_date&.to_fs(:long) %></td>
33
+ <% if KeystoneUi::Pages.places.any? %>
34
+ <td data-access class="ks-table-cell-middle text-sm whitespace-nowrap"><%= KeystoneUi::Pages.places[page.place] %></td>
35
+ <% end %>
36
+ <% if KeystoneUi::Pages.access_exceptions.any? %>
37
+ <td data-access class="ks-table-cell-middle text-sm whitespace-nowrap"><%= KeystoneUi::Pages.access_exceptions[page.access_exception] %></td>
38
+ <% end %>
21
39
  <td class="ks-table-cell-middle text-sm whitespace-nowrap text-right">
22
40
  <%= link_to "Edit", manage_page_path(page) %>
23
41
  <% if live %>
@@ -26,6 +44,17 @@
26
44
  <%= link_to "PDF", manage_page_pdf_path(page), class: "ml-4" %>
27
45
  <% end %>
28
46
  <% end %>
47
+ <div class="ml-4 inline-block">
48
+ <% if page.archived_at %>
49
+ <%= ui_form(action: restore_manage_page_path(page), method: :post) do %>
50
+ <%= ui_button(label: "Restore", type: :submit, variant: :secondary, size: :sm) %>
51
+ <% end %>
52
+ <% else %>
53
+ <%= ui_form(action: archive_manage_page_path(page), method: :post) do %>
54
+ <%= ui_button(label: "Archive", type: :submit, variant: :secondary, size: :sm) %>
55
+ <% end %>
56
+ <% end %>
57
+ </div>
29
58
  </td>
30
59
  </tr>
31
60
  <% end %>
@@ -8,6 +8,11 @@
8
8
  <label class="ks-label mb-1">Address</label>
9
9
  <%= ui_input(name: "page[slug]", placeholder: "e.g. pricing") %>
10
10
 
11
+ <% if KeystoneUi::Pages.places.any? %>
12
+ <label class="ks-label mb-1">Place</label>
13
+ <%= ui_select(name: "page[place]", options: KeystoneUi::Pages.places.invert.to_a) %>
14
+ <% end %>
15
+
11
16
  <%= ui_button(label: "Create", type: :submit) %>
12
17
  <% end %>
13
18
  <% end %>
@@ -4,5 +4,5 @@
4
4
  <% end %>
5
5
 
6
6
  <div data-page-builder>
7
- <%= react_ui("keystone_ui_pages/editor", editor_props(@page).merge(name: @page.name, publish_url: publish_manage_page_path(@page))) %>
7
+ <%= react_ui("keystone_ui_pages/editor", editor_props(@page).merge(name: @page.name, publish_url: publish_manage_page_path(@page), unpublish_url: (unpublish_manage_page_path(@page) if @page.live_version))) %>
8
8
  </div>
data/config/routes.rb CHANGED
@@ -4,6 +4,9 @@ KeystoneUi::Pages::Engine.routes.draw do
4
4
  namespace :manage do
5
5
  resources :pages, only: %i[index new create show] do
6
6
  post :publish, on: :member
7
+ post :unpublish, on: :member
8
+ post :archive, on: :member
9
+ post :restore, on: :member
7
10
  resources :elements, only: %i[create update] do
8
11
  post :bind, on: :member
9
12
  end
@@ -17,6 +20,7 @@ KeystoneUi::Pages::Engine.routes.draw do
17
20
  end
18
21
  resource :document, only: %i[update]
19
22
  post :sample, to: "samples#update"
23
+ post :access_exception, to: "access_exceptions#update"
20
24
  post :undo, to: "edit_history#undo"
21
25
  post :redo, to: "edit_history#redo"
22
26
  resource :live, only: %i[show]
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddAccessToKeystoneUiPagesPages < ActiveRecord::Migration[7.1]
4
+ def change
5
+ add_column :keystone_ui_pages_pages, :place, :string
6
+ add_column :keystone_ui_pages_pages, :access_exception, :string
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddArchivedAtToKeystoneUiPagesPages < ActiveRecord::Migration[7.1]
4
+ def change
5
+ add_column :keystone_ui_pages_pages, :archived_at, :datetime
6
+ end
7
+ end
@@ -10,7 +10,7 @@ module KeystoneUi
10
10
  class Engine < ::Rails::Engine
11
11
  isolate_namespace KeystoneUi::Pages
12
12
 
13
- initializer "keystone_ui_pages.host_helper" do
13
+ config.to_prepare do
14
14
  ActiveSupport.on_load(:action_view) { include KeystoneUi::Pages::HostHelper }
15
15
  end
16
16
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module KeystoneUi
4
4
  module Pages
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
@@ -9,8 +9,8 @@ require "keystone_ui/pages/engine"
9
9
  module KeystoneUi
10
10
  module Pages
11
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
12
+ attr_writer :base_controller, :layout, :editor_layout, :pdf_stylesheet, :places, :access_exceptions
13
+ attr_accessor :edit_check_method, :view_check_method, :values_method, :owner_method, :refusal_method, :chrome_path
14
14
 
15
15
  def base_controller
16
16
  @base_controller || "ActionController::Base"
@@ -27,6 +27,14 @@ module KeystoneUi
27
27
  def pdf_stylesheet
28
28
  @pdf_stylesheet || "tailwind.css"
29
29
  end
30
+
31
+ def places
32
+ @places || {}
33
+ end
34
+
35
+ def access_exceptions
36
+ @access_exceptions || {}
37
+ end
30
38
  end
31
39
 
32
40
  def self.pdf(page, values: {})
@@ -46,14 +46,32 @@ inherit from the base controller, so define it there.
46
46
  - `KeystoneUi::Pages.view_check_method = :may_view_page?` — a method taking the
47
47
  page and returning true when the current visitor may see the published page
48
48
  at `/pages/:slug`. Unset, every published page is shown.
49
+ - `KeystoneUi::Pages.places = { "marketing" => "Marketing site" }` — a hash of
50
+ the places a page can belong to, each stored value to the label editors see.
51
+ When it is not empty, the New page form offers them, a page with a place not
52
+ in it is refused, and the page list shows each page's place. The view check
53
+ reads it as `page.place`. Unset, pages have no place.
54
+ - `KeystoneUi::Pages.access_exceptions = { "public_preview" => "Public preview" }`
55
+ — a hash of the exceptions an editor can set on a page, each stored value to
56
+ its label. When it is not empty, the builder's top bar has an Access exception
57
+ choice that sets or clears one, an exception not in it is refused, and the page
58
+ list shows each page's exception. The view check reads it as
59
+ `page.access_exception`. Unset, the choice is not shown.
60
+ - `KeystoneUi::Pages.values_method = :page_values` — a method taking the page
61
+ and returning a hash of value names to values for the current visitor. The
62
+ engine calls it each time it draws a published page at `/pages/:slug`, so
63
+ bound fields and show-when conditions follow the visitor there. The drawing is
64
+ cached per version and per set of values, so no visitor gets a copy drawn for
65
+ other values. Unset, published pages draw with no values.
49
66
  - `KeystoneUi::Pages.edit_check_method = :may_edit_pages?` — a method with no
50
67
  arguments returning true when the current user may use the editor's screens:
51
68
  the page list, the builder, View live, History and the PDF link. It runs
52
69
  before every one of those requests. Unset, everyone may.
53
70
  - `KeystoneUi::Pages.refusal_method = :refuse_page` — a method with no
54
71
  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.
72
+ or edit check, an unknown slug, or a page with no live version, which covers a
73
+ draft, an unpublished page and an archived page. Unset, the response is an
74
+ empty 404.
57
75
  - `KeystoneUi::Pages.layout = "application"` — the host layout published pages
58
76
  and View live are drawn in. Defaults to `"application"`.
59
77
  - `KeystoneUi::Pages.editor_layout = "editor"` — the host layout the page
@@ -186,9 +204,10 @@ The catalog offers these Keystone UI components by default: `button`,
186
204
 
187
205
  The drawing is kept in `Rails.cache` under the version and the values, so
188
206
  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.
207
+ should be drawn this way unless the host sets `values_method`, since without
208
+ it the published address `/pages/:slug` draws with no values: bound fields
209
+ show their fallback text there, and conditions on a value hide what they
210
+ guard unless they compare against blank.
192
211
 
193
212
  6. To make a PDF:
194
213
 
@@ -254,9 +273,18 @@ The catalog offers these Keystone UI components by default: `button`,
254
273
  `values:` passed to the helper and to PDFs all use the same names. The gem
255
274
  turns symbol keys into strings, so either works, but the names must match.
256
275
  - **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.
276
+ its state, and when it was last published, plus its place and access
277
+ exception when the host names any. Each row links to Edit and has an Archive
278
+ button, and a published page also links to View live and, when Chrome or
279
+ Chromium is found, to its PDF. Show archived lists archived pages instead,
280
+ each with a Restore button.
281
+ - **A page's state** is Draft before its first publish, Published while it has
282
+ a live version, Unpublished once that version is taken down, and Archived.
283
+ The builder's Unpublish button, shown next to Publish on a published page,
284
+ takes the live version down and keeps the page's history. Archiving takes the
285
+ live version down too and hides the page from the list. An archived page
286
+ cannot be published, and restoring it returns it to Draft or Unpublished,
287
+ never straight to Published. Visitors are refused every state but Published.
260
288
  - **View live** draws the live version in `KeystoneUi::Pages.layout` with the
261
289
  values of the sample chosen in the builder, behind the edit check. It is
262
290
  linked from the page list and from the builder's top bar once the page is
@@ -9,6 +9,9 @@ develop:
9
9
  - KeystoneUi::Pages.base_controller
10
10
  - KeystoneUi::Pages.owner_method
11
11
  - KeystoneUi::Pages.view_check_method
12
+ - KeystoneUi::Pages.places
13
+ - KeystoneUi::Pages.access_exceptions
14
+ - KeystoneUi::Pages.values_method
12
15
  - KeystoneUi::Pages.edit_check_method
13
16
  - KeystoneUi::Pages.refusal_method
14
17
  - KeystoneUi::Pages.layout
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keystone_ui-pages
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Schneider
@@ -77,6 +77,7 @@ files:
77
77
  - app/assets/builds/keystone_ui_pages/editor.js
78
78
  - app/controllers/concerns/keystone_ui/pages/edits_pages.rb
79
79
  - app/controllers/concerns/keystone_ui/pages/host_checks.rb
80
+ - app/controllers/keystone_ui/pages/manage/access_exceptions_controller.rb
80
81
  - app/controllers/keystone_ui/pages/manage/base_controller.rb
81
82
  - app/controllers/keystone_ui/pages/manage/columns_controller.rb
82
83
  - app/controllers/keystone_ui/pages/manage/documents_controller.rb
@@ -94,6 +95,7 @@ files:
94
95
  - app/helpers/keystone_ui/pages/drawing_helper.rb
95
96
  - app/helpers/keystone_ui/pages/editor_helper.rb
96
97
  - app/helpers/keystone_ui/pages/host_helper.rb
98
+ - app/javascript/keystone_ui_pages/AccessExceptionChoice.jsx
97
99
  - app/javascript/keystone_ui_pages/DeviceWidth.jsx
98
100
  - app/javascript/keystone_ui_pages/ElementFields.jsx
99
101
  - app/javascript/keystone_ui_pages/ElementPalette.jsx
@@ -125,6 +127,8 @@ files:
125
127
  - db/migrate/20261014120001_create_keystone_ui_pages_versions.rb
126
128
  - db/migrate/20261015130000_create_keystone_ui_pages_pdfs.rb
127
129
  - db/migrate/20261016120000_add_edit_history_to_keystone_ui_pages_pages.rb
130
+ - db/migrate/20261019120000_add_access_to_keystone_ui_pages_pages.rb
131
+ - db/migrate/20261019130000_add_archived_at_to_keystone_ui_pages_pages.rb
128
132
  - lib/keystone_ui-pages.rb
129
133
  - lib/keystone_ui/pages.rb
130
134
  - lib/keystone_ui/pages/catalog.rb