0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-06 20:11:29 -05:00

Merge pull request #3388 from penpot/niwinz-bugfixes-2023-w26-2

 Add backward compatibility layer for v1.20 and other fixes
This commit is contained in:
Alejandro 2023-07-10 12:48:43 +02:00 committed by GitHub
commit 9c0e594294
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 230 additions and 160 deletions

View file

@ -53,6 +53,10 @@
- Fix problem with importation process [Taiga #5597](https://tree.taiga.io/project/penpot/issue/5597)
- Fix problem with HSV color picker [#3317](https://github.com/penpot/penpot/issues/3317)
- Fix problem with slashes in layers names for exporter [#3276](https://github.com/penpot/penpot/issues/3276)
- Fix incorrect modified data on moving files on dashboard [Taiga #5530](https://tree.taiga.io/project/penpot/issue/5530)
- Fix focus handling on comments edition [Taiga #5560](https://tree.taiga.io/project/penpot/issue/5560)
- Fix incorrect fullname use on registring user after OIDC authentication [Taiga #5517](https://tree.taiga.io/project/penpot/issue/5517)
- Fix incorrect modified-at on project after import file [Taiga #5268](https://tree.taiga.io/project/penpot/issue/5268)
### :arrow_up: Deps updates

View file

@ -118,8 +118,7 @@
(t/write! tw data)))
(catch java.io.IOException _)
(catch Throwable cause
(l/warn :hint "unexpected error on encoding response"
:cause cause))
(l/error :hint "unexpected error on encoding response" :cause cause))
(finally
(.close ^OutputStream output-stream))))))
@ -132,8 +131,8 @@
(catch java.io.IOException _)
(catch Throwable cause
(l/warn :hint "unexpected error on encoding response"
:cause cause))
(l/error :hint "unexpected error on encoding response"
:cause cause))
(finally
(.close ^OutputStream output-stream))))))

View file

@ -33,7 +33,7 @@
[::sm/word-string {:max 500}])
(def schema:token
[::sm/word-string {:max 1000}])
[::sm/word-string {:max 6000}])
;; ---- COMMAND: login with password
@ -323,9 +323,9 @@
:extra-data ptoken})))
(defn register-profile
[{:keys [::db/conn] :as cfg} {:keys [token] :as params}]
[{:keys [::db/conn] :as cfg} {:keys [token fullname] :as params}]
(let [claims (tokens/verify (::main/props cfg) {:token token :iss :prepared-register})
params (merge params claims)
params (assoc claims :fullname fullname)
is-active (or (:is-active params)
(not (contains? cf/flags :email-verification)))

View file

@ -929,5 +929,10 @@
::input (:path file)
::project-id project-id
::ignore-index-errors? true))]
(db/update! conn :project
{:modified-at (dt/now)}
{:id project-id})
(rph/with-meta ids
{::audit/props {:file nil :file-ids ids}}))))

View file

@ -189,6 +189,8 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn check-features-compatibility!
"Function responsible to check if provided features are supported by
the current backend"
[features]
(let [not-supported (set/difference features supported-features)]
(when (seq not-supported)
@ -248,47 +250,59 @@
(into #{} (comp (filter pmap/pointer-map?)
(map pmap/get-id)))))
;; FIXME: file locking
(defn- process-components-v2-feature
"A special case handling of the components/v2 feature."
[conn {:keys [id features data] :as file}]
(binding [pmap/*tracked* (atom {})]
(let [data (ctf/migrate-to-components-v2 data)
features (conj features "components/v2")
features' (db/create-array conn "text" features)]
(db/update! conn :file
{:data (blob/encode data)
:features features'}
{:id id})
(persist-pointers! conn id)
(-> file
(assoc :features features)
(assoc :data data)))))
(defn handle-file-features!
[conn {:keys [features] :as file} client-features]
;; Check features compatibility between the currently supported features on
;; the current backend instance and the file retrieved from the database
(check-features-compatibility! features)
(cond-> file
(and (contains? features "components/v2")
(not (contains? client-features "components/v2")))
(as-> file (ex/raise :type :restriction
:code :feature-mismatch
:feature "components/v2"
:hint "file has 'components/v2' feature enabled but frontend didn't specifies it"
:file-id (:id file)))
;; This operation is needed because the components migration generates a new
;; page with random id which is returned to the client; without persisting
;; the migration this can cause that two simultaneous clients can have a
;; different view of the file data and end persisting two pages with main
;; components and breaking the whole file."
(and (contains? client-features "components/v2")
(not (contains? features "components/v2")))
(as-> file (process-components-v2-feature conn file))
;; This operation is needed for backward comapatibility with frontends that
;; does not support pointer-map resolution mechanism; this just resolves the
;; pointers on backend and return a complete file.
(and (contains? features "storage/pointer-map")
(not (contains? client-features "storage/pointer-map")))
(process-pointers deref)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; QUERY COMMANDS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn handle-file-features!
[conn {:keys [id features data] :as file} client-features]
(when (and (contains? features "components/v2")
(not (contains? client-features "components/v2")))
(ex/raise :type :restriction
:code :feature-mismatch
:feature "components/v2"
:hint "file has 'components/v2' feature enabled but frontend didn't specifies it"))
;; NOTE: this operation is needed because the components migration
;; generates a new page with random id which is returned to the
;; client; without persisting the migration this can cause that two
;; simultaneous clients can have a different view of the file data
;; and end persisting two pages with main components and breaking
;; the whole file
(let [file (if (and (contains? client-features "components/v2")
(not (contains? features "components/v2")))
(binding [pmap/*tracked* (atom {})]
(let [data (ctf/migrate-to-components-v2 data)
features (conj features "components/v2")
features' (db/create-array conn "text" features)]
(db/update! conn :file
{:data (blob/encode data)
:features features'}
{:id id})
(persist-pointers! conn id)
(-> file
(assoc :features features)
(assoc :data data))))
file)]
(cond-> file
(and (contains? features "storage/pointer-map")
(not (contains? client-features "storage/pointer-map")))
(process-pointers deref))))
;; --- COMMAND QUERY: get-file (by id)
(sm/def! ::features
@ -331,7 +345,7 @@
([conn id client-features]
(get-file conn id client-features nil))
([conn id client-features project-id]
;; here we check if client requested features are supported
;; here we check if client requested features are supported
(check-features-compatibility! client-features)
(binding [pmap/*load-fn* (partial load-pointer conn id)]
(let [params (merge {:id id}

View file

@ -323,3 +323,13 @@
:rfn (fn [^Reader rdr]
(let [^List x (read-object! rdr)]
(Matrix. (.get x 0) (.get x 1) (.get x 2) (.get x 3) (.get x 4) (.get x 5))))})
;; Backward compatibility for 1.19 with v1.20;
(add-handlers!
{:name "penpot/geom/rect"
:rfn read-map-like}
{:name "penpot/shape"
:rfn read-map-like})

View file

@ -96,22 +96,25 @@
"Get the parent shape linked to a component for this shape, if any"
([objects shape] (get-component-shape objects shape nil))
([objects shape {:keys [allow-main?] :or {allow-main? false} :as options}]
(cond
(nil? shape)
nil
(cond
(nil? shape)
nil
(and (not (ctk/in-component-copy? shape)) (not allow-main?))
nil
(= uuid/zero (:id shape))
nil
(ctk/instance-root? shape)
shape
(and (not (ctk/in-component-copy? shape)) (not allow-main?))
nil
:else
(get-component-shape objects (get objects (:parent-id shape)) options))))
(ctk/instance-root? shape)
shape
:else
(get-component-shape objects (get objects (:parent-id shape)) options))))
(defn in-component-main?
"Check if the shape is inside a component non-main instance.
Note that we must iterate on the parents because non-root shapes in
a main component have not any discriminating attribute."
[objects shape]

View file

@ -134,14 +134,13 @@
(rx/throw {:type :comment-error})))))))))
(defn update-comment-thread-status
[{:keys [id] :as thread}]
(dm/assert! (comment-thread? thread))
[thread-id]
(ptk/reify ::update-comment-thread-status
ptk/WatchEvent
(watch [_ state _]
(let [done #(d/update-in-when % [:comment-threads id] assoc :count-unread-comments 0)
(let [done #(d/update-in-when % [:comment-threads thread-id] assoc :count-unread-comments 0)
share-id (-> state :viewer-local :share-id)]
(->> (rp/cmd! :update-comment-thread-status {:id id :share-id share-id})
(->> (rp/cmd! :update-comment-thread-status {:id thread-id :share-id share-id})
(rx/map (constantly done))
(rx/catch #(rx/throw {:type :comment-error})))))))

View file

@ -10,6 +10,7 @@
[app.common.data.macros :as dm]
[app.common.pages :as cp]
[app.common.schema :as sm]
[app.common.time :as dt]
[app.common.uri :as u]
[app.common.uuid :as uuid]
[app.config :as cf]
@ -872,10 +873,14 @@
ptk/UpdateEvent
(update [_ state]
(let [origin-project (get-in state [:dashboard-files (first ids) :project-id])]
(let [origin-project (get-in state [:dashboard-files (first ids) :project-id])
update-project (fn [project]
(-> project
(update :count #(+ % (count ids)))
(assoc :modified-at (dt/now))))]
(-> state
(d/update-in-when [:dashboard-projects origin-project] update :count #(- % (count ids)))
(d/update-in-when [:dashboard-projects project-id] update :count #(+ % (count ids))))))
(d/update-in-when [:dashboard-projects origin-project] update-project)
(d/update-in-when [:dashboard-projects project-id] update-project))))
ptk/WatchEvent
(watch [_ _ _]

View file

@ -165,12 +165,22 @@
[{:keys [code] :as error}]
(cond
(= :feature-mismatch code)
(let [message (tr "errors.feature-mismatch" (:feature error))]
(st/emit! (modal/show {:type :alert :message message})))
(let [message (tr "errors.feature-mismatch" (:feature error))
team-id (:current-team-id @st/state)
project-id (:current-project-id @st/state)
on-accept #(if (and project-id team-id)
(st/emit! (rt/nav :dashboard-files {:team-id team-id :project-id project-id}))
(set! (.-href glob/location) ""))]
(st/emit! (modal/show {:type :alert :message message :on-accept on-accept})))
(= :features-not-supported code)
(let [message (tr "errors.feature-not-supported" (:feature error))]
(st/emit! (modal/show {:type :alert :message message})))
(let [message (tr "errors.feature-not-supported" (:feature error))
team-id (:current-team-id @st/state)
project-id (:current-project-id @st/state)
on-accept #(if (and project-id team-id)
(st/emit! (rt/nav :dashboard-files {:team-id team-id :project-id project-id}))
(set! (.-href glob/location) ""))]
(st/emit! (modal/show {:type :alert :message message :on-accept on-accept})))
(= :max-quote-reached code)
(let [message (tr "errors.max-quote-reached" (:target error))]

View file

@ -10,11 +10,10 @@
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr t]]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as k]
[goog.events :as events]
[rumext.v2 :as mf])
(:import goog.events.EventType))
[rumext.v2 :as mf]))
(mf/defc alert-dialog
{::mf/register modal/components
@ -26,29 +25,27 @@
hint
accept-label
accept-style] :as props}]
(let [locale (mf/deref i18n/locale)
on-accept (or on-accept identity)
message (or message (t locale "ds.alert-title"))
(let [on-accept (or on-accept identity)
message (or message (tr "ds.alert-title"))
accept-label (or accept-label (tr "ds.alert-ok"))
accept-style (or accept-style :danger)
title (or title (t locale "ds.alert-title"))
title (or title (tr "ds.alert-title"))
accept-fn
(mf/use-callback
(mf/use-fn
(fn [event]
(dom/prevent-default event)
(st/emit! (modal/hide))
(on-accept props)))]
(mf/with-effect
(mf/with-effect []
(letfn [(on-keydown [event]
(when (k/enter? event)
(dom/prevent-default event)
(dom/stop-propagation event)
(st/emit! (modal/hide))
(on-accept props)))]
(->> (events/listen js/document EventType.KEYDOWN on-keydown)
(->> (events/listen js/document "keydown" on-keydown)
(partial events/unlistenByKey))))
[:div.modal-overlay

View file

@ -6,6 +6,7 @@
(ns app.main.ui.comments
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.config :as cfg]
@ -19,38 +20,55 @@
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[app.util.object :as obj]
[app.util.time :as dt]
[cuerdas.core :as str]
[okulary.core :as l]
[rumext.v2 :as mf]))
(mf/defc resizing-textarea
{::mf/wrap-props false}
[props]
(let [value (obj/get props "value" "")
on-focus (obj/get props "on-focus")
on-blur (obj/get props "on-blur")
placeholder (obj/get props "placeholder")
on-change (obj/get props "on-change")
on-esc (obj/get props "on-esc")
autofocus? (obj/get props "autofocus")
{::mf/wrap-props false
::mf/forward-ref true}
[props ref]
(let [value (d/nilv (unchecked-get props "value") "")
on-focus (unchecked-get props "on-focus")
on-blur (unchecked-get props "on-blur")
placeholder (unchecked-get props "placeholder")
on-change (unchecked-get props "on-change")
on-esc (unchecked-get props "on-esc")
autofocus? (unchecked-get props "autofocus")
select-on-focus? (unchecked-get props "select-on-focus")
ref (mf/use-ref)
local-ref (mf/use-ref)
ref (or ref local-ref)
on-key-down
(mf/use-callback
(mf/use-fn
(fn [event]
(when (and (kbd/esc? event)
(fn? on-esc))
(on-esc event))))
on-change*
(mf/use-callback
(mf/use-fn
(mf/deps on-change)
(fn [event]
(let [content (dom/get-target-val event)]
(on-change content))))]
(on-change content))))
on-focus*
(mf/use-fn
(mf/deps select-on-focus? on-focus)
(fn [event]
(when (fn? on-focus)
(on-focus event))
(when ^boolean select-on-focus?
(let [target (dom/get-target event)]
(dom/select-text! target)
;; In webkit browsers the mouseup event will be called after the on-focus causing and unselect
(.addEventListener target "mouseup" dom/prevent-default #js {:once true})))))
]
(mf/use-layout-effect
@ -64,7 +82,7 @@
{:ref ref
:auto-focus autofocus?
:on-key-down on-key-down
:on-focus on-focus
:on-focus on-focus*
:on-blur on-blur
:value value
:placeholder placeholder
@ -76,24 +94,24 @@
content (mf/use-state "")
on-focus
(mf/use-callback
(mf/use-fn
#(reset! show-buttons? true))
on-blur
(mf/use-callback
(mf/use-fn
#(reset! show-buttons? false))
on-change
(mf/use-callback
(mf/use-fn
#(reset! content %))
on-cancel
(mf/use-callback
(mf/use-fn
#(do (reset! content "")
(reset! show-buttons? false)))
on-submit
(mf/use-callback
(mf/use-fn
(mf/deps thread @content)
(fn []
(st/emit! (dcm/add-comment thread @content))
@ -128,7 +146,7 @@
pos-y (* (:y position) zoom)
on-esc
(mf/use-callback
(mf/use-fn
(mf/deps draft)
(fn [event]
(dom/stop-propagation event)
@ -137,13 +155,13 @@
(st/emit! :interrupt))))
on-change
(mf/use-callback
(mf/use-fn
(mf/deps draft)
(fn [content]
(st/emit! (dcm/update-draft-thread {:content content}))))
on-submit
(mf/use-callback
(mf/use-fn
(mf/deps draft)
(partial on-submit draft))]
@ -179,16 +197,20 @@
(let [content (mf/use-state content)
on-change
(mf/use-callback
(mf/use-fn
#(reset! content %))
on-submit*
(mf/use-callback
(mf/use-fn
(mf/deps @content)
(fn [] (on-submit @content)))]
(fn [] (on-submit @content)))
]
[:div.reply-form.edit-form
[:& resizing-textarea {:value @content
:autofocus true
:select-on-focus true
:on-change on-change}]
[:div.buttons
[:input.btn-primary {:type "button" :value "Post" :on-click on-submit*}]
@ -202,24 +224,24 @@
edition? (mf/use-state false)
on-show-options
(mf/use-callback #(reset! options true))
(mf/use-fn #(reset! options true))
on-hide-options
(mf/use-callback #(reset! options false))
(mf/use-fn #(reset! options false))
on-edit-clicked
(mf/use-callback
(mf/use-fn
(fn []
(reset! options false)
(reset! edition? true)))
on-delete-comment
(mf/use-callback
(mf/use-fn
(mf/deps comment)
#(st/emit! (dcm/delete-comment comment)))
delete-thread
(mf/use-callback
(mf/use-fn
(mf/deps thread)
#(st/emit! (dcm/close-thread)
(if (= origin :viewer)
@ -228,7 +250,7 @@
on-delete-thread
(mf/use-callback
(mf/use-fn
(mf/deps thread)
#(st/emit! (modal/show
{:type :confirm
@ -238,17 +260,17 @@
:on-accept delete-thread})))
on-submit
(mf/use-callback
(mf/use-fn
(mf/deps comment thread)
(fn [content]
(reset! edition? false)
(st/emit! (dcm/update-comment (assoc comment :content content)))))
on-cancel
(mf/use-callback #(reset! edition? false))
(mf/use-fn #(reset! edition? false))
toggle-resolved
(mf/use-callback
(mf/use-fn
(mf/deps thread)
(fn [event]
(dom/stop-propagation event)
@ -268,6 +290,7 @@
(if (:is-resolved thread)
[:span i/checkbox-checked]
[:span i/checkbox-unchecked])])
(when (= (:id profile) (:id owner))
[:div.options
[:div.options-icon {:on-click on-show-options} i/actions]])]
@ -287,40 +310,45 @@
[:li {:on-click on-delete-thread} (tr "labels.delete-comment-thread")]
[:li {:on-click on-delete-comment} (tr "labels.delete-comment")])]]]))
(defn comments-ref
[{:keys [id] :as thread}]
(l/derived (l/in [:comments id]) st/state))
(defn make-comments-ref
[thread-id]
(l/derived (l/in [:comments thread-id]) st/state))
(mf/defc thread-comments
{::mf/wrap [mf/memo]}
[{:keys [thread zoom users origin position-modifier]}]
(let [ref (mf/use-ref)
pos (cond-> (:position thread)
(some? position-modifier)
(gpt/transform position-modifier))
(let [ref (mf/use-ref)
pos-x (+ (* (:x pos) zoom) 14)
pos-y (- (* (:y pos) zoom) 14)
comments-ref (mf/use-memo (mf/deps thread) #(comments-ref thread))
thread-id (:id thread)
thread-pos (:position thread)
pos (cond-> thread-pos
(some? position-modifier)
(gpt/transform position-modifier))
pos-x (+ (* (:x pos) zoom) 14)
pos-y (- (* (:y pos) zoom) 14)
comments-ref (mf/with-memo [thread-id]
(make-comments-ref thread-id))
comments-map (mf/deref comments-ref)
comments (->> (vals comments-map)
(sort-by :created-at))
comments (mf/with-memo [comments-map]
(->> (vals comments-map)
(sort-by :created-at)))
comment (first comments)]
(mf/use-layout-effect
(mf/deps thread)
#(st/emit! (dcm/retrieve-comments (:id thread))))
(mf/with-effect [thread-id]
(st/emit! (dcm/retrieve-comments thread-id)))
(mf/use-effect
(mf/deps thread)
#(st/emit! (dcm/update-comment-thread-status thread)))
(mf/with-effect [thread-id]
(st/emit! (dcm/update-comment-thread-status thread-id)))
(mf/use-layout-effect
(mf/deps thread comments-map)
(fn []
(when-let [node (mf/ref-val ref)]
(dom/scroll-into-view-if-needed! node))))
(mf/with-layout-effect [thread-pos comments-map]
(when-let [node (mf/ref-val ref)]
(dom/scroll-into-view-if-needed! node)))
(when (some? comment)
[:div.thread-content
@ -345,22 +373,22 @@
(defn use-buble
[zoom {:keys [position frame-id]}]
(let [dragging-ref (mf/use-ref false)
start-ref (mf/use-ref nil)
start-ref (mf/use-ref nil)
state (mf/use-state {:hover false
:new-position-x nil
:new-position-y nil
:new-frame-id frame-id})
state (mf/use-state {:hover false
:new-position-x nil
:new-position-y nil
:new-frame-id frame-id})
on-pointer-down
(mf/use-callback
(mf/use-fn
(fn [event]
(dom/capture-pointer event)
(mf/set-ref-val! dragging-ref true)
(mf/set-ref-val! start-ref (dom/get-client-position event))))
on-pointer-up
(mf/use-callback
(mf/use-fn
(mf/deps (select-keys @state [:new-position-x :new-position-y :new-frame-id]))
(fn [_ thread]
(when (and
@ -369,7 +397,7 @@
(st/emit! (dwcm/update-comment-thread-position thread [(:new-position-x @state) (:new-position-y @state)])))))
on-lost-pointer-capture
(mf/use-callback
(mf/use-fn
(fn [event]
(dom/release-pointer event)
(mf/set-ref-val! dragging-ref false)
@ -378,7 +406,7 @@
(swap! state assoc :new-position-y nil)))
on-pointer-move
(mf/use-callback
(mf/use-fn
(mf/deps position zoom)
(fn [event]
(when-let [_ (mf/ref-val dragging-ref)]
@ -416,7 +444,7 @@
pos-y (* (or (:new-position-y @state) (:y pos)) zoom)
on-pointer-down*
(mf/use-callback
(mf/use-fn
(mf/deps origin was-open? open? drag? on-pointer-down)
(fn [event]
(when (not= origin :viewer)
@ -427,7 +455,7 @@
(on-pointer-down event))))
on-pointer-up*
(mf/use-callback
(mf/use-fn
(mf/deps origin thread was-open? drag? on-pointer-up)
(fn [event]
(when (not= origin :viewer)
@ -439,7 +467,7 @@
(st/emit! (dcm/open-thread thread))))))
on-pointer-move*
(mf/use-callback
(mf/use-fn
(mf/deps origin drag? on-pointer-move)
(fn [event]
(when (not= origin :viewer)
@ -448,7 +476,7 @@
(on-pointer-move event))))
on-click*
(mf/use-callback
(mf/use-fn
(mf/deps origin thread on-click)
(fn [event]
(dom/stop-propagation event)
@ -472,7 +500,7 @@
[{:keys [item users on-click]}]
(let [owner (get users (:owner-id item))
on-click*
(mf/use-callback
(mf/use-fn
(mf/deps item)
(fn [event]
(dom/stop-propagation event)

View file

@ -199,11 +199,6 @@
(when (and (some? current) (not (.contains current target)))
(dom/blur! current)))))))
on-mouse-up
(mf/use-callback
(fn [event]
(dom/prevent-default event)))
handle-focus
(mf/use-callback
(fn [event]
@ -212,9 +207,9 @@
(on-focus event))
(when select-on-focus?
(-> event (dom/get-target) (.select))
(dom/select-text! event)
;; In webkit browsers the mouseup event will be called after the on-focus causing and unselect
(.addEventListener target "mouseup" on-mouse-up #js {"once" true})))))
(.addEventListener target "mouseup" dom/prevent-default #js {:once true})))))
props (-> props
(obj/without ["value" "onChange" "nillable" "onFocus"])

View file

@ -255,6 +255,7 @@
(fn []
(st/emit! (dd/fetch-files {:project-id project-id})
(dd/fetch-recent-files (:id team))
(dd/fetch-projects)
(dd/clear-selected-files))))]
(mf/with-effect

View file

@ -29,12 +29,12 @@
[]
(let [{cmode :mode cshow :show} (mf/deref refs/comments-local)
update-mode
(mf/use-callback
(mf/use-fn
(fn [mode]
(st/emit! (dcm/update-filters {:mode mode}))))
update-show
(mf/use-callback
(mf/use-fn
(fn [mode]
(st/emit! (dcm/update-filters {:show mode}))))]
@ -76,7 +76,7 @@
page-id (or page-id (mf/use-ctx ctx/current-page-id))
on-thread-click
(mf/use-callback
(mf/use-fn
(mf/deps page-id)
(fn [thread]
(when (not= page-id (:page-id thread))

View file

@ -234,7 +234,7 @@
(defn select-text!
[^js node]
(when (and (some? node) (or (= "INPUT" (.-tagName node)) (= "TEXTAREA" (.-tagName node))))
(when (some? node)
(.select ^js node)))
(defn ^boolean equals?