keystone_ui-pages 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
 
@@ -24,12 +25,39 @@ module KeystoneUi
24
25
  (session[:keystone_ui_pages_return_to] ||= {})[@page.id.to_s] = params[:return_to] if local_address?(params[:return_to])
25
26
  end
26
27
 
28
+ def edit
29
+ @page = owned_pages.find(params[:id])
30
+ end
31
+
32
+ def update
33
+ @page = owned_pages.find(params[:id])
34
+ return render(:edit, status: :unprocessable_entity) unless @page.update(params.require(:page).permit(:name, :slug, :place))
35
+
36
+ redirect_to manage_pages_path
37
+ end
38
+
27
39
  def publish
28
40
  page = owned_pages.find(params[:id])
29
41
  page.publish
30
42
  redirect_to manage_page_path(page)
31
43
  end
32
44
 
45
+ def unpublish
46
+ page = owned_pages.find(params[:id])
47
+ page.unpublish
48
+ redirect_to manage_page_path(page)
49
+ end
50
+
51
+ def archive
52
+ owned_pages.find(params[:id]).archive
53
+ redirect_to manage_pages_path
54
+ end
55
+
56
+ def restore
57
+ owned_pages.find(params[:id]).restore
58
+ redirect_to manage_pages_path
59
+ end
60
+
33
61
  private
34
62
 
35
63
  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,17 @@ 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 :slug, uniqueness: { scope: %i[owner_type owner_id] }
22
+ validates :place, inclusion: { in: -> { KeystoneUi::Pages.places.keys } }, allow_nil: true, if: -> { KeystoneUi::Pages.places.any? }
23
+ validates :access_exception, inclusion: { in: -> { KeystoneUi::Pages.access_exceptions.keys } }, allow_nil: true, if: -> { KeystoneUi::Pages.access_exceptions.any? }
18
24
 
19
25
  def publish
26
+ return if archived_at
27
+
20
28
  transaction do
21
29
  live_version&.update!(status: :superseded)
22
30
  versions.create!(number: next_number, document: document, status: :live)
@@ -43,6 +51,29 @@ module KeystoneUi
43
51
  update!(document: undone_changes.last, undone_changes: undone_changes[0...-1], undo_history: undo_history + [ document ])
44
52
  end
45
53
 
54
+ def unpublish
55
+ live_version&.update!(status: :superseded)
56
+ end
57
+
58
+ def archive
59
+ transaction do
60
+ unpublish
61
+ update!(archived_at: Time.current)
62
+ end
63
+ end
64
+
65
+ def restore
66
+ update!(archived_at: nil)
67
+ end
68
+
69
+ def state
70
+ return :archived if archived_at
71
+ return :published if live_version
72
+ return :unpublished if versions.exists?
73
+
74
+ :draft
75
+ end
76
+
46
77
  def live_version
47
78
  versions.find_by(status: :live)
48
79
  end
@@ -0,0 +1,22 @@
1
+ <%= ui_page(max_width: :xl) do %>
2
+ <%= ui_page_header(title: "Settings") %>
3
+
4
+ <% if @page.errors.any? %>
5
+ <%= ui_alert(message: @page.errors.full_messages.to_sentence, type: :error) %>
6
+ <% end %>
7
+
8
+ <%= ui_form(action: manage_page_path(@page), method: :patch) do %>
9
+ <label class="ks-label mb-1">Name</label>
10
+ <%= ui_input(name: "page[name]", value: @page.name) %>
11
+
12
+ <label class="ks-label mb-1">Address</label>
13
+ <%= ui_input(name: "page[slug]", value: @page.slug) %>
14
+
15
+ <% if KeystoneUi::Pages.places.any? %>
16
+ <label class="ks-label mb-1">Place</label>
17
+ <%= ui_select(name: "page[place]", options: KeystoneUi::Pages.places.invert.to_a, selected: @page.place) %>
18
+ <% end %>
19
+
20
+ <%= ui_button(label: "Save", type: :submit) %>
21
+ <% end %>
22
+ <% 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,16 +28,34 @@
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) %>
41
+ <%= link_to "Settings", edit_manage_page_path(page), class: "ml-4" %>
23
42
  <% if live %>
24
43
  <%= link_to "View live", manage_page_live_path(page), class: "ml-4" %>
25
44
  <% if @printable %>
26
45
  <%= link_to "PDF", manage_page_pdf_path(page), class: "ml-4" %>
27
46
  <% end %>
28
47
  <% end %>
48
+ <div class="ml-4 inline-block">
49
+ <% if page.archived_at %>
50
+ <%= ui_form(action: restore_manage_page_path(page), method: :post) do %>
51
+ <%= ui_button(label: "Restore", type: :submit, variant: :secondary, size: :sm) %>
52
+ <% end %>
53
+ <% else %>
54
+ <%= ui_form(action: archive_manage_page_path(page), method: :post) do %>
55
+ <%= ui_button(label: "Archive", type: :submit, variant: :secondary, size: :sm) %>
56
+ <% end %>
57
+ <% end %>
58
+ </div>
29
59
  </td>
30
60
  </tr>
31
61
  <% 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
@@ -2,8 +2,11 @@
2
2
 
3
3
  KeystoneUi::Pages::Engine.routes.draw do
4
4
  namespace :manage do
5
- resources :pages, only: %i[index new create show] do
5
+ resources :pages, only: %i[index new create show edit update] 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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module KeystoneUi
4
4
  module Pages
5
- VERSION = "0.1.1"
5
+ VERSION = "0.3.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,23 @@ 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 Settings and has
278
+ an Archive button, and a published page also links to View live and, when
279
+ Chrome or Chromium is found, to its PDF. Show archived lists archived pages
280
+ instead, each with a Restore button.
281
+ - **A page's settings** at `/pages/manage/pages/:id/edit` change its name, slug
282
+ and place after it is created. A slug another page of the same owner uses, or
283
+ a place the host does not name, is refused with a message and changes nothing.
284
+ A changed slug moves the published page to its new address, and the old
285
+ address is not redirected.
286
+ - **A page's state** is Draft before its first publish, Published while it has
287
+ a live version, Unpublished once that version is taken down, and Archived.
288
+ The builder's Unpublish button, shown next to Publish on a published page,
289
+ takes the live version down and keeps the page's history. Archiving takes the
290
+ live version down too and hides the page from the list. An archived page
291
+ cannot be published, and restoring it returns it to Draft or Unpublished,
292
+ never straight to Published. Visitors are refused every state but Published.
260
293
  - **View live** draws the live version in `KeystoneUi::Pages.layout` with the
261
294
  values of the sample chosen in the builder, behind the edit check. It is
262
295
  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.1
4
+ version: 0.3.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
@@ -113,6 +115,7 @@ files:
113
115
  - app/models/keystone_ui/pages/page.rb
114
116
  - app/models/keystone_ui/pages/pdf.rb
115
117
  - app/models/keystone_ui/pages/version.rb
118
+ - app/views/keystone_ui/pages/manage/pages/edit.html.erb
116
119
  - app/views/keystone_ui/pages/manage/pages/index.html.erb
117
120
  - app/views/keystone_ui/pages/manage/pages/new.html.erb
118
121
  - app/views/keystone_ui/pages/manage/pages/show.html.erb
@@ -125,6 +128,8 @@ files:
125
128
  - db/migrate/20261014120001_create_keystone_ui_pages_versions.rb
126
129
  - db/migrate/20261015130000_create_keystone_ui_pages_pdfs.rb
127
130
  - db/migrate/20261016120000_add_edit_history_to_keystone_ui_pages_pages.rb
131
+ - db/migrate/20261019120000_add_access_to_keystone_ui_pages_pages.rb
132
+ - db/migrate/20261019130000_add_archived_at_to_keystone_ui_pages_pages.rb
128
133
  - lib/keystone_ui-pages.rb
129
134
  - lib/keystone_ui/pages.rb
130
135
  - lib/keystone_ui/pages/catalog.rb