🎉 Add page multi-selection in the workspace sitemap (#10581)

* 🎉 Add page multi-selection in the workspace sitemap

* ♻️ Simplify page selection state updates with single assoc

* 🐛 Fix SCSS issue

* ♻️ Update some components to new syntax

* ♻️ Adapt SCSS to the new guidelines

* ♻️ SCSS cleanup

---------

Co-authored-by: Luis de Dios <luis.dedios@kaleidos.net>
This commit is contained in:
Juan de la Cruz 2026-07-21 14:26:03 +02:00 committed by GitHub
parent fa996ab240
commit 5dff551f31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 372 additions and 271 deletions

View File

@ -1207,9 +1207,20 @@
(dm/assert! (gpt/point? position))
(ptk/reify ::show-page-item-context-menu
ptk/WatchEvent
(watch [_ _ _]
(rx/of (show-context-menu
(-> params (assoc :kind :page :selected (:id page))))))))
(watch [_ state _]
(let [id (:id page)
selected (dm/get-in state [:workspace-local :selected-pages])
;; When the right-clicked page is part of a multi-selection we
;; keep it; otherwise the menu targets just that page.
multi? (and (contains? selected id) (> (count selected) 1))]
(rx/concat
(if multi?
(rx/empty)
(rx/of (dwpg/select-page id)))
(rx/of (show-context-menu
(-> params (assoc :kind :page
:selected id
:selected-pages (if multi? selected #{id}))))))))))
(defn show-track-context-menu
[{:keys [grid-id type index] :as params}]
@ -1651,6 +1662,11 @@
(dm/export dwpg/duplicate-page)
(dm/export dwpg/rename-page)
(dm/export dwpg/delete-page)
(dm/export dwpg/delete-pages)
(dm/export dwpg/select-page)
(dm/export dwpg/toggle-page-selection)
(dm/export dwpg/select-pages-range)
(dm/export dwpg/clear-page-selection)
;; Shapes
(dm/export dwsh/delete-shapes)

View File

@ -135,7 +135,8 @@
ptk/UpdateEvent
(update [_ state]
(let [local (-> (:workspace-local state)
(dissoc :edition :edit-path :selected))
(dissoc :edition :edit-path :selected
:selected-pages :selected-pages-anchor))
exit? (not= :workspace (rt/lookup-name state))
state (-> state
(update :workspace-cache assoc [file-id page-id] local)
@ -395,3 +396,104 @@
(rx/of (dch/commit-changes changes)
(when (= id (:current-page-id state))
(dcm/go-to-workspace {:page-id (first pages)})))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Page selection (sitemap multi-selection)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn clear-page-selection
"Clear the sitemap multi-selection state."
[]
(ptk/reify ::clear-page-selection
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local dissoc :selected-pages :selected-pages-anchor))))
(defn select-page
"Set the sitemap selection to a single page (plain click). It also
sets the anchor used by range (shift+click) selection."
[id]
(ptk/reify ::select-page
ptk/UpdateEvent
(update [_ state]
(update state :workspace-local assoc
:selected-pages (d/ordered-set id)
:selected-pages-anchor id))))
(defn toggle-page-selection
"Add or remove a page from the sitemap multi-selection (ctrl/cmd + click)."
[id]
(ptk/reify ::toggle-page-selection
ptk/UpdateEvent
(update [_ state]
(let [current-id (:current-page-id state)
selected (or (not-empty (dm/get-in state [:workspace-local :selected-pages]))
(d/ordered-set current-id))
selected (if (contains? selected id)
(disj selected id)
(conj selected id))]
(update state :workspace-local assoc
:selected-pages selected
:selected-pages-anchor id)))))
(defn select-pages-range
"Select every page between the current anchor and `id`, both included
(shift + click). The anchor is kept unchanged."
[id]
(ptk/reify ::select-pages-range
ptk/UpdateEvent
(update [_ state]
(let [current-id (:current-page-id state)
pages (-> (dsh/lookup-file-data state) :pages vec)
anchor (or (dm/get-in state [:workspace-local :selected-pages-anchor])
current-id)
a-idx (d/index-of pages anchor)
b-idx (d/index-of pages id)]
(if (and (some? a-idx) (some? b-idx))
(let [start (min a-idx b-idx)
end (inc (max a-idx b-idx))
range (subvec pages start end)]
(update state :workspace-local assoc
:selected-pages (into (d/ordered-set) range)))
state)))))
(defn delete-pages
"Delete a collection of pages in a single change (single undo entry),
always keeping at least one page in the file."
[ids]
(ptk/reify ::delete-pages
ptk/WatchEvent
(watch [it state _]
(let [file-id (:current-file-id state)
fdata (dsh/lookup-file-data state file-id)
pindex (:pages-index fdata)
pages (:pages fdata)
ids (set ids)
;; A file must keep at least one page: if every page is
;; selected, keep the first one in page order.
ids (if (>= (count ids) (count pages))
(set (rest (filter ids pages)))
ids)
;; Pages to delete, in page order.
del-ids (filter ids pages)
remaining (remove ids pages)
changes (reduce
(fn [changes id]
(let [page (-> (get pindex id)
(assoc :index (d/index-of pages id)))]
(-> changes
(delete-page-components page)
(pcb/del-page page))))
(-> (pcb/empty-changes it)
(pcb/with-library-data fdata))
del-ids)]
(if (empty? del-ids)
(rx/empty)
(rx/of (dch/commit-changes changes)
(clear-page-selection)
(when (contains? ids (:current-page-id state))
(dcm/go-to-workspace {:page-id (first remaining)}))))))))

View File

@ -253,6 +253,10 @@
(def editing-page-item
(l/derived :page-item workspace-local))
;; set of pages selected in the sitemap (multi-selection)
(def selected-pages
(l/derived :selected-pages workspace-local))
(def current-hover-ids
(l/derived :hover-ids context-menu))

View File

@ -705,6 +705,8 @@
[{:keys [mdata]}]
(let [page (:page mdata)
deletable? (:deletable? mdata)
selected-pages (:selected-pages mdata)
multi? (> (count selected-pages) 1)
id (:id page)
delete-fn #(st/emit! (dw/delete-page id))
do-delete #(st/emit! (modal/show
@ -712,20 +714,31 @@
:title (tr "modals.delete-page.title")
:message (tr "modals.delete-page.body")
:on-accept delete-fn}))
delete-many-fn #(st/emit! (dw/delete-pages selected-pages))
do-delete-many #(st/emit! (modal/show
{:type :confirm
:title (tr "modals.delete-pages.title")
:message (tr "modals.delete-pages.body")
:on-accept delete-many-fn}))
do-duplicate #(st/emit!
(dw/duplicate-page id)
(ev/event {::ev/name "duplicate-page"}))
do-rename #(st/emit! (dw/start-rename-page-item id))]
[:*
(when deletable?
[:> menu-entry* {:title (tr "workspace.assets.delete")
:on-click do-delete}])
(if multi?
;; When several pages are selected, the only available action is
;; deleting all of them at once.
[:> menu-entry* {:title (tr "workspace.assets.delete-pages")
:on-click do-delete-many}]
[:*
(when deletable?
[:> menu-entry* {:title (tr "workspace.assets.delete")
:on-click do-delete}])
[:> menu-entry* {:title (tr "workspace.assets.rename")
:on-click do-rename}]
[:> menu-entry* {:title (tr "workspace.assets.duplicate")
:on-click do-duplicate}]]))
[:> menu-entry* {:title (tr "workspace.assets.rename")
:on-click do-rename}]
[:> menu-entry* {:title (tr "workspace.assets.duplicate")
:on-click do-duplicate}]])))
(mf/defc viewport-context-menu*
[]

View File

@ -62,9 +62,10 @@
;; --- Page Item
(mf/defc page-item
{::mf/wrap-props false}
[{:keys [page index deletable? selected? editing? hovering? current-page-id]}]
(mf/defc page-item*
{::mf/private true
::mf/wrap-props false}
[{:keys [page index is-deletable is-selected is-editing is-hovering current-page-id]}]
(let [input-ref (mf/use-ref)
id (:id page)
name (:name page "")
@ -76,38 +77,53 @@
on-click
(mf/use-fn
(mf/deps id current-page-id is-separator?)
(fn []
(fn [event]
(when-not is-separator?
;; WASM page transitions:
;; - Capture the current page (A) once
;; - Show a blurred snapshot while the target page (B/C/...) renders
;; - If the user clicks again during the transition, keep showing the original (A) snapshot
(if (and (features/active-feature? @st/state "render-wasm/v1")
(not= id current-page-id))
(-> (if @wasm.api/page-transition?
(p/resolved nil)
;; Blur with Skia, then capture the already-blurred frame.
(do (wasm.api/render-blurred-snapshot!)
(wasm.api/capture-canvas-snapshot)))
(p/finally
(fn []
(wasm.api/apply-canvas-blur)
;; Two RAF so the overlay paints before navigation.
(timers/raf
(fn []
(timers/raf navigate-fn))))))
(navigate-fn)))))
(cond
;; Shift + click: select the range of pages from the anchor
;; to this page (does not navigate).
(kbd/shift? event)
(st/emit! (dw/select-pages-range id))
;; Ctrl/Cmd + click: add/remove this page to/from the
;; multi-selection (does not navigate).
(kbd/mod? event)
(st/emit! (dw/toggle-page-selection id))
;; Plain click: reset the selection to this page and
;; navigate to it.
:else
(do
(st/emit! (dw/select-page id))
;; WASM page transitions:
;; - Capture the current page (A) once
;; - Show a blurred snapshot while the target page (B/C/...) renders
;; - If the user clicks again during the transition, keep showing the original (A) snapshot
(if (and (features/active-feature? @st/state "render-wasm/v1")
(not= id current-page-id))
(-> (if @wasm.api/page-transition?
(p/resolved nil)
;; Blur with Skia, then capture the already-blurred frame.
(do (wasm.api/render-blurred-snapshot!)
(wasm.api/capture-canvas-snapshot)))
(p/finally
(fn []
(wasm.api/apply-canvas-blur)
;; Two RAF so the overlay paints before navigation.
(timers/raf
(fn []
(timers/raf navigate-fn))))))
(navigate-fn)))))))
on-delete
(mf/use-fn
(mf/deps id)
(fn [event]
(dom/stop-propagation event)
(st/emit! (modal/show
{:type :confirm
:title (tr "modals.delete-page.title")
:message (tr "modals.delete-page.body")
:on-accept delete-fn}))))
(st/emit! (modal/show {:type :confirm
:title (tr "modals.delete-page.title")
:message (tr "modals.delete-page.body")
:on-accept delete-fn}))))
on-double-click
(mf/use-fn
@ -153,7 +169,7 @@
:data {:id id
:index index
:name (:name page)}
:draggable? (and (not read-only?) (not editing?)))
:draggable? (and (not read-only?) (not is-editing)))
on-context-menu
(mf/use-fn
@ -166,50 +182,49 @@
(st/emit! (dw/show-page-item-context-menu
{:position position
:page page
:deletable? deletable?}))))))]
:deletable? is-deletable}))))))]
(mf/use-effect
(mf/deps selected?)
(mf/deps is-selected)
(fn []
(when selected?
(when is-selected
(let [node (mf/ref-val dref)]
(dom/scroll-into-view-if-needed! node)))))
(mf/use-layout-effect
(mf/deps editing?)
(mf/deps is-editing)
(fn []
(when editing?
(when is-editing
(let [edit-input (mf/ref-val input-ref)]
(dom/select-text! edit-input))
nil)))
(let [selected? (and selected? (not is-separator?))]
[:li {:class (stl/css-case
:page-element true
:separator is-separator?
:selected selected?
:dnd-over-top (= (:over dprops) :top)
:dnd-over-bot (= (:over dprops) :bot))
(let [selected? (and is-selected (not is-separator?))]
[:li {:class (stl/css-case :page-item true
:separator is-separator?
:selected selected?
:dnd-over-top (= (:over dprops) :top)
:dnd-over-bot (= (:over dprops) :bot))
:ref dref}
[:div {:class (stl/css-case
:element-list-body true
:separator-body is-separator?
:hover (and hovering? (not is-separator?))
:selected selected?)
[:div {:class (stl/css-case :page-item-body true
:separator is-separator?
:hover (and is-hovering (not is-separator?))
:selected selected?)
:data-testid (dm/str "page-" id)
:tab-index "0"
:on-click on-click
:on-double-click on-double-click
:on-context-menu on-context-menu}
(if (and is-separator? (not editing?))
[:div {:class (stl/css :page-separator)
(if (and is-separator? (not is-editing))
[:div {:class (stl/css :page-divider)
:data-testid "page-separator"}]
[:*
(when-not is-separator?
[:div {:class (stl/css :page-icon)}
[:> icon* {:icon-id i/document :size "s"}]])
(if editing?
[:input {:class (stl/css :element-name)
[:div {:class (stl/css :page-item-icon)}
[:> icon* {:icon-id i/document
:size "s"}]])
(if is-editing
[:input {:class (stl/css :page-item-input)
:type "text"
:ref input-ref
:on-blur on-blur
@ -217,32 +232,35 @@
:auto-focus true
:default-value name}]
[:*
[:span {:class (stl/css :page-name) :title name :data-testid "page-name"}
[:span {:class (stl/css :page-item-label)
:title name
:data-testid "page-name"}
name]
[:div {:class (stl/css :page-actions)}
(when (and deletable? (not read-only?))
[:div {:class (stl/css :page-item-actions)}
(when (and is-deletable (not read-only?))
[:> icon-button* {:variant "action"
:aria-label (tr "modals.delete-page.title")
:on-click on-delete
:icon-size "s"
:class (stl/css :page-delete-button)
:icon-class (stl/css :page-delete-button-icon)
:class (stl/css :page-delete-btn)
:icon-class (stl/css :page-delete-icon)
:icon i/delete}])]])])]])))
;; --- Page Item Wrapper
(mf/defc page-item-wrapper
{::mf/wrap-props false}
[{:keys [page-id index deletable? selected? editing? current-page-id]}]
(mf/defc page-item-wrapper*
{::mf/private true
::mf/wrap-props false}
[{:keys [page-id index is-deletable is-selected is-editing current-page-id]}]
(let [page-ref (mf/with-memo [page-id]
(make-page-ref page-id))
page (mf/deref page-ref)]
[:& page-item {:page page
:index index
:current-page-id current-page-id
:deletable? deletable?
:selected? selected?
:editing? editing?}]))
[:> page-item* {:page page
:index index
:current-page-id current-page-id
:is-deletable is-deletable
:is-selected is-selected
:is-editing is-editing}]))
;; --- Pages List
@ -252,18 +270,23 @@
(let [pages (:pages file)
deletable? (> (count pages) 1)
editing-page-id (mf/deref refs/editing-page-item)
current-page-id (mf/use-ctx ctx/current-page-id)]
[:ul {:class (stl/css :page-list)}
selected-pages (mf/deref refs/selected-pages)
current-page-id (mf/use-ctx ctx/current-page-id)
;; When there is no explicit multi-selection, the current page
;; is the selected one.
selected-pages (if (seq selected-pages)
selected-pages
#{current-page-id})]
[:ul
[:> hooks/sortable-container* {}
(for [[index page-id] (d/enumerate pages)]
[:& page-item-wrapper
{:page-id page-id
:index index
:deletable? deletable?
:editing? (= page-id editing-page-id)
:selected? (= page-id current-page-id)
:current-page-id current-page-id
:key page-id}])]]))
[:> page-item-wrapper* {:page-id page-id
:index index
:is-deletable deletable?
:is-editing (= page-id editing-page-id)
:is-selected (contains? selected-pages page-id)
:current-page-id current-page-id
:key page-id}])]]))
;; --- Sitemap Toolbox
@ -276,7 +299,8 @@
on-create (mf/use-fn
(mf/deps file-id project-id)
(fn [event]
(st/emit! (dw/create-page {:file-id file-id :project-id project-id}))
(st/emit! (dw/create-page {:file-id file-id
:project-id project-id}))
(-> event dom/get-current-target dom/blur!)))
read-only? (mf/use-ctx ctx/workspace-read-only?)
@ -289,7 +313,7 @@
:collapsed collapsed
:on-collapsed on-toggle-collapsed
:title (tr "workspace.sidebar.sitemap")
:class (stl/css :title-spacing-sitemap)}
:class (stl/css :sitemap-title)}
(if ^boolean read-only?
(when ^boolean (:can-edit permissions)
@ -297,12 +321,11 @@
:size :small
:content (tr "labels.view-only")}])
[:> icon-button* {:variant "ghost"
:class (stl/css :add-page)
:aria-label (tr "workspace.sidebar.sitemap.add-page")
:on-click on-create
:icon i/add}])]
(when-not ^boolean collapsed
[:div {:class (stl/css :tool-window-content)}
[:> pages-list* {:file file :key (dm/str (:id file))}]])]))
[:div {:class (stl/css :sitemap-content)}
[:> pages-list* {:key (dm/str (:id file))
:file file}]])]))

View File

@ -4,236 +4,155 @@
//
// Copyright (c) KALEIDOS INC Sucursal en España SL
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_utils.scss" as *;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/spacing.scss" as *;
@use "ds/typography.scss" as t;
@use "ds/mixins.scss" as *;
.sitemap {
position: relative;
display: flex;
flex-direction: column;
flex: 1;
width: 100%;
height: var(--height, deprecated.$s-200);
inline-size: 100%;
block-size: var(--height, $sz-200);
}
.title {
margin-left: deprecated.$s-2;
color: var(--title-foreground-color-hover);
.sitemap-title {
padding-inline-start: var(--sp-s);
margin-block: var(--sp-s) var(--sp-xs);
}
.resize-area {
position: absolute;
bottom: calc(-1 * deprecated.$s-8);
left: 0;
width: 100%;
height: deprecated.$s-12;
border-top: deprecated.$s-2 solid var(--resize-area-border-color);
background-color: var(--resize-area-background-color);
cursor: ns-resize;
&:hover {
border-color: var(--resize-area-border-color);
}
}
.tool-window-content {
.sitemap-content {
display: flex;
flex-direction: column;
height: calc(-38px + var(--height, deprecated.$s-200));
width: var(--left-sidebar-width);
inline-size: var(--left-sidebar-width);
overflow: hidden auto;
scrollbar-gutter: stable;
.element-list {
display: grid;
}
}
.pages-list {
width: 100%;
max-height: deprecated.$s-152;
margin-bottom: deprecated.$s-12;
}
.page-item {
@include t.use-typography("body-small");
.page-delete-button {
--delete-button-display: none;
}
.page-delete-button-icon {
display: var(--delete-button-display);
}
.page-element {
@include deprecated.body-small-typography;
min-height: deprecated.$s-32;
width: 100%;
cursor: pointer;
min-block-size: $sz-32;
inline-size: 100%;
&.dnd-over-top {
border-top: deprecated.$s-1 solid var(--layer-row-foreground-color-drag);
border-block-start: $b-1 solid var(--layer-row-foreground-color-drag);
}
&.dnd-over-bot {
border-bottom: deprecated.$s-1 solid var(--layer-row-foreground-color-drag);
border-block-end: $b-1 solid var(--layer-row-foreground-color-drag);
}
.dnd-over > .element-list-body {
border: deprecated.$s-1 solid var(--layer-row-foreground-color-drag);
}
.element-list-body {
.page-item-body {
display: flex;
align-items: center;
height: deprecated.$s-32;
width: 100%;
padding: 0 deprecated.$s-12 0 0;
block-size: $sz-32;
inline-size: 100%;
padding: 0 var(--sp-m) 0 0;
transition: none;
color: var(--layer-row-foreground-color);
user-select: none;
.page-name {
@include deprecated.text-ellipsis;
flex-grow: 1;
padding-left: deprecated.$s-2;
}
.page-icon {
@include deprecated.flex-center;
padding: 0 deprecated.$s-4 0 deprecated.$s-8;
}
.page-actions {
height: deprecated.$s-32;
display: flex;
align-items: center;
}
.element-name {
@include deprecated.text-ellipsis;
color: var(--layer-row-foreground-color-focus);
}
input.element-name {
@include deprecated.text-ellipsis;
@include deprecated.body-small-typography;
@include deprecated.remove-input-style;
flex-grow: 1;
height: deprecated.$s-28;
max-width: calc(var(--parent-size) - (var(--depth) * var(--layer-indentation-size)));
padding-left: deprecated.$s-6;
margin: 0;
border-radius: deprecated.$br-8;
border: deprecated.$s-1 solid var(--input-border-color-focus);
color: var(--layer-row-foreground-color);
&.separator {
block-size: auto;
min-block-size: var(--sp-xxxl);
padding: 0;
}
}
&:active,
&.on-drag {
.element-list-body {
color: var(--layer-row-foreground-color-drag);
background-color: var(--layer-row-background-color-drag);
.page-item-label {
@include text-ellipsis;
.page-icon svg {
stroke: var(--layer-row-foreground-color-drag);
}
}
flex-grow: 1;
padding-inline-start: var(--sp-xxs);
}
&.selected,
&.selected:hover {
.element-list-body {
color: var(--layer-row-foreground-color-selected);
background-color: var(--layer-row-background-color-selected);
box-shadow: deprecated.$s-16 deprecated.$s-0 deprecated.$s-0 deprecated.$s-0
var(--layer-row-background-color-selected);
.page-icon svg {
stroke: var(--layer-row-foreground-color-selected);
}
}
.page-item-icon {
display: flex;
justify-content: center;
align-items: center;
padding: 0 var(--sp-xs) 0 var(--sp-s);
}
&:hover,
&.hover {
.element-list-body {
color: var(--layer-row-foreground-color-hover);
background-color: var(--layer-row-background-color-hover);
box-shadow: deprecated.$s-16 deprecated.$s-0 deprecated.$s-0 deprecated.$s-0
var(--layer-row-background-color-hover);
.page-actions button {
opacity: deprecated.$op-10;
--delete-button-display: initial;
}
.page-icon svg {
stroke: var(--layer-row-foreground-color-hover);
}
}
.page-item-actions {
block-size: $sz-32;
display: flex;
align-items: center;
}
&:focus {
.element-list-body {
color: var(--layer-row-foreground-color-focus);
border: deprecated.$s-1 solid var(--layer-row-border-color-focus);
outline: none;
.page-item-input {
@include text-ellipsis;
@include t.use-typography("body-small");
.page-actions button {
opacity: deprecated.$op-10;
}
}
background: none;
outline: none;
flex-grow: 1;
block-size: $sz-28;
max-inline-size: calc(var(--parent-size) - (var(--depth) * var(--layer-indentation-size)));
padding-inline-start: $sz-6;
margin: 0;
border-radius: $br-8;
border: $b-1 solid var(--input-border-color-focus);
color: var(--layer-row-foreground-color);
}
&:focus-within {
.element-list-body {
outline: none;
.page-actions button {
opacity: deprecated.$op-10;
}
}
&:active .page-item-body {
color: var(--layer-row-foreground-color-drag);
background-color: var(--layer-row-background-color-drag);
}
&.hidden {
.element-list-body {
color: var(--layer-row-foreground-color-hidden);
background-color: var(--layer-row-background-color-hidden);
opacity: deprecated.$op-7;
&.selected .page-item-body,
&.selected:hover .page-item-body {
color: var(--layer-row-foreground-color-selected);
background-color: var(--layer-row-background-color-selected);
box-shadow: $sz-16 0 0 0 var(--layer-row-background-color-selected);
}
.page-icon svg {
stroke: var(--layer-row-foreground-color-hidden);
}
}
&:hover .page-item-body,
&.hover .page-item-body {
color: var(--layer-row-foreground-color-hover);
background-color: var(--layer-row-background-color-hover);
box-shadow: $sz-16 0 0 0 var(--layer-row-background-color-hover);
}
&:hover .page-delete-btn,
&.hover .page-delete-btn {
--delete-button-display: initial;
}
&:focus .page-item-body {
color: var(--layer-row-foreground-color-focus);
border: $b-1 solid var(--layer-row-border-color-focus);
outline: none;
}
&:focus-within .page-item-body {
outline: none;
}
&.separator:hover .page-item-body,
&.separator.hover .page-item-body {
color: var(--layer-row-foreground-color);
background-color: transparent;
box-shadow: none;
}
}
.element-list-body.separator-body {
height: auto;
min-height: var(--sp-xxxl);
padding: 0;
}
.page-separator {
width: 100%;
height: $b-1;
.page-divider {
inline-size: 100%;
block-size: $b-1;
margin: var(--sp-s);
background-color: var(--color-background-quaternary);
}
.page-element.separator:hover .element-list-body,
.page-element.separator.hover .element-list-body {
color: var(--layer-row-foreground-color);
background-color: transparent;
box-shadow: none;
.page-delete-btn {
--delete-button-display: none;
}
.title-spacing-sitemap {
padding-inline-start: deprecated.$s-8;
margin-block: deprecated.$s-8 deprecated.$s-4;
.page-delete-icon {
display: var(--delete-button-display);
}

View File

@ -3926,6 +3926,14 @@ msgstr "Are you sure you want to delete this page?"
msgid "modals.delete-page.title"
msgstr "Delete page"
#: src/app/main/ui/workspace/context_menu.cljs:712
msgid "modals.delete-pages.body"
msgstr "Are you sure you want to delete the selected pages?"
#: src/app/main/ui/workspace/context_menu.cljs:711
msgid "modals.delete-pages.title"
msgstr "Delete pages"
#: src/app/main/ui/dashboard/project_menu.cljs:73
msgid "modals.delete-project-confirm.accept"
msgstr "Delete project"
@ -6441,6 +6449,10 @@ msgstr "Your items are going to be named automatically as \"group name / item na
msgid "workspace.assets.delete"
msgstr "Delete"
#: src/app/main/ui/workspace/context_menu.cljs:733
msgid "workspace.assets.delete-pages"
msgstr "Delete pages"
#: src/app/main/ui/workspace/sidebar/assets/groups.cljs:74
msgid "workspace.assets.delete-group"
msgstr "Delete group"

View File

@ -3810,6 +3810,14 @@ msgstr "¿Seguro que quieres borrar esta página?"
msgid "modals.delete-page.title"
msgstr "Borrar página"
#: src/app/main/ui/workspace/context_menu.cljs:712
msgid "modals.delete-pages.body"
msgstr "¿Seguro que quieres borrar las páginas seleccionadas?"
#: src/app/main/ui/workspace/context_menu.cljs:711
msgid "modals.delete-pages.title"
msgstr "Borrar páginas"
#: src/app/main/ui/dashboard/project_menu.cljs:73
msgid "modals.delete-project-confirm.accept"
msgstr "Eliminar proyecto"
@ -6317,6 +6325,10 @@ msgstr ""
msgid "workspace.assets.delete"
msgstr "Borrar"
#: src/app/main/ui/workspace/context_menu.cljs:733
msgid "workspace.assets.delete-pages"
msgstr "Borrar páginas"
#: src/app/main/ui/workspace/context_menu.cljs:721, src/app/main/ui/workspace/sidebar/assets/colors.cljs:262, src/app/main/ui/workspace/sidebar/assets/typographies.cljs:471, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:537
msgid "workspace.assets.duplicate"
msgstr "Duplicar"