How to use react in clojure plugin?

I am trying to translate the following in clojure to be able to use reagent in a block:


logseq.App.onMacroRendererSlotted(async ({ slot, payload: { arguments: args, uuid } }) => {
if (args?.[0] !== 'agenda') return
const rendered = parent.document.getElementById(slot)?.childElementCount
if (rendered) return

const id = `agenda-task-list-${slot}`
logseq.provideUI({
key: `agenda-${slot}`,
slot,
reset: true,
template: `<div id="${id}"></div>`,
// style: {},
})
logseq.provideStyle(`${LOGSEQ_PROVIDE_COMMON_STYLE}
#block-content-${uuid} .lsp-hook-ui-slot {
width: 100%;
}
#block-content-${uuid} .lsp-hook-ui-slot > div {
width: 100%;
}
#block-content-${uuid} .lsp-hook-ui-slot > div > div {
width: 100%;
}
`)
setTimeout(() => {
ReactDOM.render(
<React.StrictMode>
<TaskListApp containerId={id} />
</React.StrictMode>,
parent.document.getElementById(id)
)
}, 0)
})

(Code from agenda plugin)

I have no idea how to use logseq.App.onMacroRendererSlotted in clojure. Someone pointed this out but I doubt this is how it should be called from plugin api.

So here’s what I’ve got so far. I wonder if I need to use logseq.provideUI and provideStyle like in the example above but I have no idea what it does. I mean it does seems like it create an element that replace the renderer but I don’t understand why it would do that as I don’t see how it can telll where to go with the given arguments.

(defn element
  "Create an string of HTML element using :name (default \"div\") :class :style and :content"
  ( [& {:keys [name class style content other] :or {name "div"}}]
   (str "<" name
        (when class (str " class=\"" class "\" "))
        (when style (str " style=\"" style "\" "))
        (when other (str " " other " "))
        ">"
        (when content (str "\n    " (clojure.string/replace content "\n" "\n    ") "\n"))
        "</" name ">")))


(defn app [id]
  (r/create-element "React.StrictMode"
                     (r/create-element "div"
                                       #js{:containerId id}
                                        "world")))

(js/logseq.App.onMacroRendererSlotted (fn [^js params]
                                        (let [slot (.-slot params)
                                              payload (.-payload params)
                                              args (.-arguments payload)
                                              uuid (.-uuid payload)
                                              ]
                                          (js/Promise.resolve
                                           (do
                                             ( println (aget args 1 ) )
                                             (println slot)
                                             (println (some-> js/document
                                                          ;; (some->
                                                          (.getElementById slot)

                                                          (.childElementCount)
                                                          ))
                                             (let [id (str "agenda-task-list-" slot)]
                                               (println (app id))
                                             (js/logseq.provideUI (clj->js {
                                                                            :key (str "agenda-" slot)
                                                                            :reset true
                                                                            :template (element :other "id=\"" id "\"")
                                                                            }))
                                             (js/logseq.provideStyle "
                                                #block-content-" uuid " .lsp-hook-ui-slot {
                                                    width: 100%;
                                                }
                                                #block-content-" uuid " .lsp-hook-ui-slot > div {
                                                    width: 100%;
                                                }
                                                #block-content-" uuid " .lsp-hook-ui-slot > div > div {
                                                    width: 100%;
                                                }")
                                             (println (rdom/render [app id]
                                                        (.getElementById js/document slot)))))))))

I don’t know why I need to eval the onMacroRendererSlotted part but here’s how I’m able to get something:

  • load plugin in repl and eval the onMacroRendererSlotted function
  • in Logseq, Open console in inspector
  • type {{renderer a,b,c}} in a bloc
  • press enter

Should display an error in the console (preceded by println stuff).

1 Like

Alright, I got something that works!

(defn element
  "Create an string of HTML element using :name (default \"div\") :class :style and :content"
  ( [& {:keys [name class style content other] :or {name "div"}}]
   (str "<" name
        (when class (str " class=\"" class "\" "))
        (when style (str " style=\"" style "\" "))
        (when other (str " " other " "))
        ">"
        (when content (str "\n    " (clojure.string/replace content "\n" "\n    ") "\n"))
        "</" name ">")))

(js/logseq.App.onMacroRendererSlotted (fn [^js params]
                                        (let [slot (.-slot params)
                                              payload (.-payload params)
                                              args (.-arguments payload)
                                              uuid (.-uuid payload)
                                              ]
                                          (js/Promise.resolve
                                           (do
                                             (println (aget args 1 ) )
                                             (println (some-> js/document
                                                          ;; (some->
                                                          (.getElementById slot)
                                                          (.childElementCount)
                                                          ))
                                             (let [id (str "id-" slot)]
                                             (js/logseq.provideUI (clj->js {
                                                                            :key (str "key-" slot)
                                                                            :slot slot
                                                                            :reset true
                                                                            :template (element :other (str "id=\"" id "\""))
                                                                            }))
                                             (js/logseq.provideStyle "
                                                #block-content-" uuid " .lsp-hook-ui-slot {
                                                    width: 100%;
                                                }
                                                #block-content-" uuid " .lsp-hook-ui-slot > div {
                                                    width: 100%;
                                                }
                                                #block-content-" uuid " .lsp-hook-ui-slot > div > div {
                                                    width: 100%;
                                                }")
                                             (js/setTimeout    #(rdom/render [:div [:p "ok"]]
                                                                           (.getElementById (.-document js/parent) id)) 0)))))))

The biggest part of the issue in the previous code was that I wasn’t getting the proper dom elements since I was getting cljs core dom and not the logseq app dom. I’ve removed the react strictmode part since that’s reagent that should be updated (haven’t looked up into this so maybe there’s a fix to prevent the console warning). If I understand, there’s a need for js/timeout because provideUI doesn’t do its work right away. I don’t know if there would be a way to await it but the js/timeout works fine.

So that’s how reagent can be used in a logseq block, I still got a lot to learn from there.

1 Like