Skip to content

Commit 69008b5

Browse files
committed
feat: Add schedule element button
Replaces the public toggle with a split button which allows to schedule the element for publication.
1 parent e9b8a94 commit 69008b5

File tree

27 files changed

+416
-133
lines changed

27 files changed

+416
-133
lines changed

app/assets/builds/alchemy/admin.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/assets/builds/alchemy/dark-theme.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/assets/builds/alchemy/light-theme.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/assets/builds/alchemy/theme.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/assets/images/alchemy/icons-sprite.svg

Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<alchemy-publish-element-button id="publish-element-button-<%= element.id %>">
2+
<sl-button-group label="Example Button Group">
3+
<sl-tooltip
4+
content="<%= element.public? ? Alchemy.t(:hide_element) : Alchemy.t(:show_element) %>"
5+
<%= "disabled" if element.scheduled? || cannot?(:update, element) %>
6+
>
7+
<sl-button
8+
variant="<%= element.public? ? "default" : "primary" %>"
9+
type="submit"
10+
size="small"
11+
<%= "disabled" if element.scheduled? || cannot?(:update, element) %>
12+
<%= "outline" if element.public? || element.scheduled? %>
13+
pill
14+
>
15+
<alchemy-icon name="cloud-off" slot="prefix" size="1x"></alchemy-icon>
16+
</sl-button>
17+
</sl-tooltip>
18+
<sl-tooltip
19+
content="<%= Alchemy.t(:schedule_element) %>"
20+
>
21+
<sl-button
22+
href="<%= alchemy.schedule_admin_element_path(element) %>"
23+
variant="<%= element.scheduled? ? "primary" : "default" %>"
24+
size="small"
25+
<%= "outline" unless element.scheduled? %>
26+
<%= "disabled" if cannot?(:update, element) %>
27+
pill
28+
>
29+
<alchemy-icon name="calendar-schedule" slot="suffix" size="1x"></alchemy-icon>
30+
</sl-button>
31+
</sl-tooltip>
32+
</sl-button-group>
33+
</alchemy-publish-element-button>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module Alchemy
2+
module Admin
3+
class PublishElementButton < ViewComponent::Base
4+
delegate :alchemy, :cannot?, :render_icon, :link_to_dialog, to: :helpers
5+
6+
attr_reader :element
7+
8+
def initialize(element:)
9+
@element = element
10+
end
11+
end
12+
end
13+
end

app/controllers/alchemy/admin/elements_controller.rb

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class ElementsController < Alchemy::Admin::BaseController
88
before_action :load_page_and_version, only: [:index, :new]
99
include Alchemy::Admin::Clipboard
1010

11-
before_action :load_element, only: [:update, :destroy, :collapse, :expand, :publish]
11+
before_action :load_element, only: [:schedule, :update, :destroy, :collapse, :expand, :publish]
1212
authorize_resource class: Alchemy::Element
1313

1414
def index
@@ -89,12 +89,22 @@ def destroy
8989
end
9090

9191
def publish
92-
@element.public = !@element.public?
92+
if schedule_element_params.present?
93+
@element.assign_attributes(schedule_element_params)
94+
else
95+
@element.public = !@element.public?
96+
end
9397
@element.save(validate: false)
94-
render json: {
95-
public: @element.public?,
96-
label: @element.public? ? Alchemy.t(:hide_element) : Alchemy.t(:show_element)
97-
}.merge(pagePublicationData(@element.page))
98+
99+
respond_to do |format|
100+
format.json do
101+
render json: {
102+
public: @element.public?,
103+
tooltip: @element.public? ? Alchemy.t(:hide_element) : Alchemy.t(:show_element)
104+
}.merge(pagePublicationData(@element.page))
105+
end
106+
format.turbo_stream
107+
end
98108
end
99109

100110
def order
@@ -216,6 +226,10 @@ def element_params
216226
def create_element_params
217227
params.require(:element).permit(:name, :page_version_id, :parent_element_id)
218228
end
229+
230+
def schedule_element_params
231+
params[:element]&.permit(:public_on, :public_until)
232+
end
219233
end
220234
end
221235
end

app/javascript/alchemy_admin/components/element_editor/publish_element_button.js

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,76 @@ import { patch } from "alchemy_admin/utils/ajax"
22
import { reloadPreview } from "alchemy_admin/components/preview_window"
33
import { growl } from "alchemy_admin/growler"
44
import { dispatchPageDirtyEvent } from "alchemy_admin/components/element_editor"
5+
import { openDialog } from "alchemy_admin/dialog"
56

67
export class PublishElementButton extends HTMLElement {
7-
constructor() {
8-
super()
9-
10-
this.addEventListener("sl-change", this)
8+
connectedCallback() {
9+
this.publishButton.addEventListener("click", this)
10+
this.scheduleButton.addEventListener("click", this)
1111
}
1212

1313
handleEvent(event) {
14-
const elementEditor = event.target.closest("alchemy-element-editor")
15-
if (elementEditor === this.elementEditor) {
16-
patch(Alchemy.routes.publish_admin_element_path(this.elementId))
17-
.then((response) => {
18-
const data = response.data
19-
this.elementEditor.published = data.public
20-
this.tooltip.setAttribute("content", data.label)
21-
reloadPreview()
22-
if (data.pageHasUnpublishedChanges) {
23-
dispatchPageDirtyEvent(data)
24-
}
14+
switch (event.target) {
15+
case this.scheduleButton:
16+
event.preventDefault()
17+
openDialog(this.scheduleButton.href, {
18+
size: "450x260",
19+
title: this.scheduleButton.closest("sl-tooltip").content
2520
})
26-
.catch((error) => growl(error.message, "error"))
21+
break
22+
case this.publishButton:
23+
this.publishButton.loading = true
24+
patch(Alchemy.routes.publish_admin_element_path(this.elementId))
25+
.then((response) => this.afterPublish(response))
26+
.catch((error) => growl(error.message, "error"))
27+
.finally(() => (this.publishButton.loading = false))
28+
break
29+
}
30+
}
31+
32+
afterPublish(response) {
33+
const data = response.data
34+
if (data.public) {
35+
this.elementEditor.published = true
36+
this.publishButton.setAttribute("variant", "default")
37+
this.publishButton.setAttribute("outline", "")
38+
this.hiddenIcon.hidden = true
39+
} else {
40+
this.elementEditor.published = false
41+
this.publishButton.setAttribute("variant", "primary")
42+
this.publishButton.removeAttribute("outline")
43+
this.hiddenIcon.hidden = false
44+
}
45+
this.publishTooltip.setAttribute("content", data.tooltip)
46+
if (data.pageHasUnpublishedChanges) {
47+
dispatchPageDirtyEvent(data)
2748
}
49+
reloadPreview()
2850
}
2951

3052
get elementEditor() {
3153
return this.closest("alchemy-element-editor")
3254
}
3355

34-
get tooltip() {
35-
return this.closest("sl-tooltip")
56+
get publishButton() {
57+
return this.querySelector("sl-button[type='submit']")
58+
}
59+
60+
get scheduleButton() {
61+
return this.querySelector("sl-button[href]")
62+
}
63+
64+
get publishTooltip() {
65+
return this.querySelector("sl-tooltip")
3666
}
3767

3868
get elementId() {
3969
return this.elementEditor.elementId
4070
}
71+
72+
get hiddenIcon() {
73+
return this.elementEditor.querySelector(".element-hidden-icon")
74+
}
4175
}
4276

4377
customElements.define("alchemy-publish-element-button", PublishElementButton)

app/javascript/alchemy_admin/shoelace_theme.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,16 @@ const spriteUrl = document
4747
.getAttribute("href")
4848

4949
const iconMap = {
50-
"x-lg": "close"
50+
"x-lg": "close",
51+
caret: "arrow-down-s"
5152
}
5253

5354
const options = {
5455
resolver: (name) => `${spriteUrl}#ri-${iconMap[name] || name}-line`,
55-
mutator: (svg) => svg.setAttribute("fill", "currentColor"),
56+
mutator: (svg) => {
57+
svg.setAttribute("fill", "currentColor")
58+
svg.setAttribute("viewBox", "0 0 24 24")
59+
},
5660
spriteSheet: true
5761
}
5862

0 commit comments

Comments
 (0)