| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- (ns frontend.components.content
- (:require [cljs-bean.core :as bean]
- [cljs-time.coerce :as tc]
- [cljs.pprint :as pp]
- [clojure.string :as string]
- [cognitect.transit :as transit]
- [dommy.core :as d]
- [frontend.commands :as commands]
- [frontend.components.editor :as editor]
- [frontend.components.export :as export]
- [frontend.components.page-menu :as page-menu]
- [frontend.config :as config]
- [frontend.context.i18n :refer [t]]
- [frontend.db :as db]
- [frontend.extensions.srs :as srs]
- [frontend.handler.common :as common-handler]
- [frontend.handler.common.developer :as dev-common-handler]
- [frontend.handler.editor :as editor-handler]
- [frontend.handler.notification :as notification]
- [frontend.handler.page :as page-handler]
- [frontend.handler.property :as property-handler]
- [frontend.handler.property.util :as pu]
- [frontend.mixins :as mixins]
- [frontend.modules.shortcut.core :as shortcut]
- [frontend.persist-db.browser :as db-browser]
- [frontend.state :as state]
- [frontend.ui :as ui]
- [logseq.shui.ui :as shui]
- [frontend.util :as util]
- [frontend.util.url :as url-util]
- [goog.dom :as gdom]
- [goog.object :as gobj]
- [logseq.common.util :as common-util]
- [logseq.common.util.block-ref :as block-ref]
- [promesa.core :as p]
- [rum.core :as rum]))
- (defonce transit-r (transit/reader :json))
- ;; TODO i18n support
- (rum/defc custom-context-menu-content
- []
- (let [repo (state/get-current-repo)]
- [:.menu-links-wrapper
- (ui/menu-background-color #(property-handler/batch-set-block-property! repo (state/get-selection-block-ids) :background-color %)
- #(property-handler/batch-remove-block-property! repo (state/get-selection-block-ids) :background-color))
- (ui/menu-heading #(editor-handler/batch-set-heading! (state/get-selection-block-ids) %)
- #(editor-handler/batch-set-heading! (state/get-selection-block-ids) true)
- #(editor-handler/batch-remove-heading! (state/get-selection-block-ids)))
- [:hr.menu-separator]
- (ui/menu-link
- {:key "cut"
- :on-click #(editor-handler/cut-selection-blocks true)
- :shortcut (ui/keyboard-shortcut-from-config :editor/cut)}
- (t :editor/cut))
- (ui/menu-link
- {:key "delete"
- :on-click #(do (editor-handler/delete-selection %)
- (state/hide-custom-context-menu!))
- :shortcut (ui/keyboard-shortcut-from-config :editor/delete)}
- (t :editor/delete-selection))
- (ui/menu-link
- {:key "copy"
- :on-click editor-handler/copy-selection-blocks
- :shortcut (ui/keyboard-shortcut-from-config :editor/copy)}
- (t :editor/copy))
- (ui/menu-link
- {:key "copy as"
- :on-click (fn [_]
- (let [block-uuids (editor-handler/get-selected-toplevel-block-uuids)]
- (state/set-modal!
- #(export/export-blocks block-uuids {:whiteboard? false}))))}
- (t :content/copy-export-as))
- (ui/menu-link
- {:key "copy block refs"
- :on-click editor-handler/copy-block-refs}
- (t :content/copy-block-ref))
- (ui/menu-link
- {:key "copy block embeds"
- :on-click editor-handler/copy-block-embeds}
- (t :content/copy-block-emebed))
- [:hr.menu-separator]
- (when (state/enable-flashcards?)
- (ui/menu-link
- {:key "Make a Card"
- :on-click #(srs/batch-make-cards!)}
- (t :context-menu/make-a-flashcard)))
- (ui/menu-link
- {:key "Toggle number list"
- :on-click #(state/pub-event! [:editor/toggle-own-number-list (state/get-selection-block-ids)])}
- (t :context-menu/toggle-number-list))
- (ui/menu-link
- {:key "cycle todos"
- :on-click editor-handler/cycle-todos!
- :shortcut (ui/keyboard-shortcut-from-config :editor/cycle-todo)}
- (t :editor/cycle-todo))
- [:hr.menu-separator]
- (ui/menu-link
- {:key "Expand all"
- :on-click editor-handler/expand-all-selection!
- :shortcut (ui/keyboard-shortcut-from-config :editor/expand-block-children)}
- (t :editor/expand-block-children))
- (ui/menu-link
- {:key "Collapse all"
- :on-click editor-handler/collapse-all-selection!
- :shortcut (ui/keyboard-shortcut-from-config :editor/collapse-block-children)}
- (t :editor/collapse-block-children))]))
- (defonce *template-including-parent? (atom nil))
- (rum/defc template-checkbox
- [template-including-parent?]
- [:div.flex.flex-row.w-auto.items-center
- [:p.text-medium.mr-2 (t :context-menu/template-include-parent-block)]
- (ui/toggle template-including-parent?
- #(swap! *template-including-parent? not))])
- (rum/defcs block-template < rum/reactive
- shortcut/disable-all-shortcuts
- (rum/local false ::edit?)
- (rum/local "" ::input)
- {:will-unmount (fn [state]
- (reset! *template-including-parent? nil)
- state)}
- [state block-id]
- (let [edit? (get state ::edit?)
- input (get state ::input)
- template-including-parent? (rum/react *template-including-parent?)
- block-id (if (string? block-id) (uuid block-id) block-id)
- block (db/entity [:block/uuid block-id])
- has-children? (seq (:block/_parent block))
- repo (state/get-current-repo)]
- (when (and (nil? template-including-parent?) has-children?)
- (reset! *template-including-parent? true))
- (if @edit?
- (do
- (state/clear-edit!)
- [:<>
- [:div.px-4.py-2.text-sm {:on-click (fn [e] (util/stop e))}
- [:p (t :context-menu/input-template-name)]
- [:input#new-template.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2
- {:auto-focus true
- :on-change (fn [e]
- (reset! input (util/evalue e)))}]
- (when has-children?
- (template-checkbox template-including-parent?))
- (ui/button (t :submit)
- :on-click (fn []
- (let [title (string/trim @input)]
- (when (not (string/blank? title))
- (p/let [exists? (page-handler/<template-exists? title)]
- (if exists?
- (notification/show!
- [:p (t :context-menu/template-exists-warning)]
- :error)
- (p/do!
- (property-handler/set-block-property! repo block-id :template title)
- (when (false? template-including-parent?)
- (property-handler/set-block-property! repo block-id :template-including-parent false))
- (state/hide-custom-context-menu!))))))))]
- [:hr.menu-separator]])
- (ui/menu-link
- {:key "Make a Template"
- :on-click (fn [e]
- (util/stop e)
- (reset! edit? true))}
- (t :context-menu/make-a-template)))))
- (rum/defc ^:large-vars/cleanup-todo block-context-menu-content <
- shortcut/disable-all-shortcuts
- [_target block-id]
- (let [repo (state/get-current-repo)
- db? (config/db-based-graph? repo)]
- (when-let [block (db/entity [:block/uuid block-id])]
- (let [properties (:block/properties block)
- heading (or (pu/lookup properties :heading)
- false)]
- [:.menu-links-wrapper
- (ui/menu-background-color #(property-handler/set-block-property! repo block-id :background-color %)
- #(property-handler/remove-block-property! repo block-id :background-color))
- (ui/menu-heading heading
- #(editor-handler/set-heading! block-id %)
- #(editor-handler/set-heading! block-id true)
- #(editor-handler/remove-heading! block-id))
- [:hr.menu-separator]
- (ui/menu-link
- {:key "Open in sidebar"
- :on-click (fn [_e]
- (editor-handler/open-block-in-sidebar! block-id))
- :shortcut ["⇧+click"]}
- (t :content/open-in-sidebar))
- [:hr.menu-separator]
- (ui/menu-link
- {:key "Copy block ref"
- :on-click (fn [_e]
- (editor-handler/copy-block-ref! block-id block-ref/->block-ref))}
- (t :content/copy-block-ref))
- (ui/menu-link
- {:key "Copy block embed"
- :on-click (fn [_e]
- (editor-handler/copy-block-ref! block-id #(util/format "{{embed ((%s))}}" %)))}
- (t :content/copy-block-emebed))
- ;; TODO Logseq protocol mobile support
- (when (util/electron?)
- (ui/menu-link
- {:key "Copy block URL"
- :on-click (fn [_e]
- (let [current-repo (state/get-current-repo)
- tap-f (fn [block-id]
- (url-util/get-logseq-graph-uuid-url nil current-repo block-id))]
- (editor-handler/copy-block-ref! block-id tap-f)))}
- (t :content/copy-block-url)))
- (ui/menu-link
- {:key "Copy as"
- :on-click (fn [_]
- (state/set-modal! #(export/export-blocks [block-id] {:whiteboard? false})))}
- (t :content/copy-export-as))
- (ui/menu-link
- {:key "Cut"
- :on-click (fn [_e]
- (editor-handler/cut-block! block-id))
- :shortcut (ui/keyboard-shortcut-from-config :editor/cut)}
- (t :editor/cut))
- (ui/menu-link
- {:key "delete"
- :on-click #(editor-handler/delete-block-aux! block true)
- :shortcut (ui/keyboard-shortcut-from-config :editor/delete)}
- (t :editor/delete-selection))
- [:hr.menu-separator]
- (when-not db?
- (block-template block-id))
- (cond
- (srs/card-block? block)
- (ui/menu-link
- {:key "Preview Card"
- :on-click #(srs/preview (:db/id block))}
- (t :context-menu/preview-flashcard))
- (state/enable-flashcards?)
- (ui/menu-link
- {:key "Make a Card"
- :on-click #(srs/make-block-a-card! block-id)}
- (t :context-menu/make-a-flashcard))
- :else
- nil)
- (ui/menu-link
- {:key "Toggle number list"
- :on-click #(state/pub-event! [:editor/toggle-own-number-list (state/get-selection-block-ids)])}
- (t :context-menu/toggle-number-list))
- [:hr.menu-separator]
- (ui/menu-link
- {:key "Expand all"
- :on-click (fn [_e]
- (editor-handler/expand-all! block-id))
- :shortcut (ui/keyboard-shortcut-from-config :editor/expand-block-children)}
- (t :editor/expand-block-children))
- (ui/menu-link
- {:key "Collapse all"
- :on-click (fn [_e]
- (editor-handler/collapse-all! block-id {}))
- :shortcut (ui/keyboard-shortcut-from-config :editor/collapse-block-children)}
- (t :editor/collapse-block-children))
- (when (state/sub [:plugin/simple-commands])
- (when-let [cmds (state/get-plugins-commands-with-type :block-context-menu-item)]
- (for [[_ {:keys [key label] :as cmd} action pid] cmds]
- (ui/menu-link
- {:key key
- :on-click #(commands/exec-plugin-simple-command!
- pid (assoc cmd :uuid block-id) action)}
- label))))
- (when (state/sub [:ui/developer-mode?])
- (ui/menu-link
- {:key "(Dev) Show block data"
- :on-click (fn []
- (dev-common-handler/show-entity-data [:block/uuid block-id]))}
- (t :dev/show-block-data)))
- (when (state/sub [:ui/developer-mode?])
- (ui/menu-link
- {:key "(Dev) Show block AST"
- :on-click (fn []
- (let [block (db/pull [:block/uuid block-id])]
- (dev-common-handler/show-content-ast (:block/content block) (:block/format block))))}
- (t :dev/show-block-ast)))
- (when (state/sub [:ui/developer-mode?])
- (ui/menu-link
- {:key "(Dev) Show block content history"
- :on-click
- (fn []
- (let [^object worker @db-browser/*worker]
- (p/let [result (.rtc-get-block-content-versions worker block-id)
- blocks-versions (bean/->clj result)]
- (prn :Dev-show-block-content-history)
- (doseq [[block-uuid versions] blocks-versions]
- (prn :block-uuid block-uuid)
- (pp/print-table [:content :created-at]
- (map (fn [version]
- {:created-at (tc/from-long (* (:created-at version) 1000))
- :content (:value version)})
- versions))))))}
- "(Dev) Show block content history"))
- (when (state/sub [:ui/developer-mode?])
- (ui/menu-link
- {:key "(Dev) Show block RTC log"
- :on-click
- (fn []
- (let [^object worker @db-browser/*worker]
- (p/let [result (.rtc-get-block-update-log worker (str block-id))
- logs (transit/read transit-r result)]
- (prn :Dev-show-block-RTC-log block-id)
- (apply js/console.log logs))))}
- "(Dev) Show block RTC log"))]))))
- (rum/defc block-ref-custom-context-menu-content
- [block block-ref-id]
- (when (and block block-ref-id)
- [:.menu-links-wrapper
- (ui/menu-link
- {:key "open-in-sidebar"
- :on-click (fn []
- (state/sidebar-add-block!
- (state/get-current-repo)
- block-ref-id
- :block-ref))
- :shortcut ["⇧+click"]}
- (t :content/open-in-sidebar))
- (ui/menu-link
- {:key "copy"
- :on-click (fn [] (editor-handler/copy-current-ref block-ref-id))}
- (t :content/copy-ref))
- (ui/menu-link
- {:key "delete"
- :on-click (fn [] (editor-handler/delete-current-ref! block block-ref-id))}
- (t :content/delete-ref))
- (ui/menu-link
- {:key "replace-with-text"
- :on-click (fn [] (editor-handler/replace-ref-with-text! block block-ref-id))}
- (t :content/replace-with-text))
- (ui/menu-link
- {:key "replace-with-embed"
- :on-click (fn [] (editor-handler/replace-ref-with-embed! block block-ref-id))}
- (t :content/replace-with-embed))]))
- (rum/defc page-title-custom-context-menu-content
- [page]
- (when-not (string/blank? page)
- (let [page-menu-options (page-menu/page-menu page)]
- [:.menu-links-wrapper
- (for [{:keys [title options]} page-menu-options]
- (rum/with-key
- (ui/menu-link options title)
- title))])))
- ;; TODO: content could be changed
- ;; Also, keyboard bindings should only be activated after
- ;; blocks were already selected.
- (rum/defc hiccup-content < rum/static
- (mixins/event-mixin
- (fn [state]
- ;; fixme: this mixin will register global event listeners on window
- ;; which might cause unexpected issues
- (mixins/listen state js/window "contextmenu"
- (fn [e]
- (let [target (gobj/get e "target")
- block-el (.closest target ".bullet-container[blockid]")
- block-id (some-> block-el (.getAttribute "blockid"))
- {:keys [block block-ref]} (state/sub :block-ref/context)
- {:keys [page]} (state/sub :page-title/context)]
- (cond
- page
- (do
- (shui/popup-show!
- e
- (fn [{:keys [id]}]
- [:div
- {:on-click #(shui/popup-hide! id)}
- (page-title-custom-context-menu-content page)])
- {:content-props {:class "ls-context-menu-content"}})
- (state/set-state! :page-title/context nil))
- block-ref
- (do
- (shui/popup-show!
- e
- (fn [{:keys [id]}]
- [:div
- {:on-click #(shui/popup-hide! id)}
- (block-ref-custom-context-menu-content block block-ref)])
- {:content-props {:class "ls-context-menu-content"}})
- (state/set-state! :block-ref/context nil))
- (and (state/selection?) (not (d/has-class? target "bullet")))
- (shui/popup-show!
- e
- (fn [{:keys [id]}]
- [:div
- {:on-click #(shui/popup-hide! id)}
- (custom-context-menu-content)])
- {:content-props {:class "ls-context-menu-content"}})
- (and block-id (parse-uuid block-id))
- (let [block (.closest target ".ls-block")]
- (when block
- (state/clear-selection!)
- (state/conj-selection-block! block :down))
- (shui/popup-show!
- e
- (fn [{:keys [id]}]
- [:div
- {:on-click #(shui/popup-hide! id)}
- (block-context-menu-content target (uuid block-id))])
- {:content-props {:class "ls-context-menu-content"}}))
- :else
- nil))))))
- [id {:keys [hiccup]}]
- [:div {:id id}
- (if hiccup
- hiccup
- [:div.cursor (t :content/click-to-edit)])])
- (rum/defc non-hiccup-content < rum/reactive
- [id content on-click on-hide config format]
- (let [edit? (state/sub-editing? id)]
- (if edit?
- (editor/box {:on-hide on-hide
- :format format}
- id
- config)
- (let [on-click (fn [e]
- (when-not (util/link? (gobj/get e "target"))
- (util/stop e)
- (editor-handler/reset-cursor-range! (gdom/getElement (str id)))
- (state/set-edit-content! id content)
- (when on-click
- (on-click e))))]
- [:pre.cursor.content.pre-white-space
- {:id id
- :on-click on-click}
- (if (string/blank? content)
- [:div.cursor (t :content/click-to-edit)]
- content)]))))
- (defn- set-draw-iframe-style!
- []
- (let [width (gobj/get js/window "innerWidth")]
- (when (>= width 1024)
- (let [draws (d/by-class "draw-iframe")
- width (- width 200)]
- (doseq [draw draws]
- (d/set-style! draw :width (str width "px"))
- (let [height (max 700 (/ width 2))]
- (d/set-style! draw :height (str height "px")))
- (d/set-style! draw :margin-left (str (- (/ (- width 570) 2)) "px")))))))
- (rum/defcs content < rum/reactive
- {:did-mount (fn [state]
- (set-draw-iframe-style!)
- state)
- :did-update (fn [state]
- (set-draw-iframe-style!)
- state)}
- [state id {:keys [format
- config
- hiccup
- content
- on-click
- on-hide]
- :as option}]
- (if hiccup
- [:div
- (hiccup-content id option)]
- ;; TODO: remove this
- (let [format (common-util/normalize-format format)]
- (non-hiccup-content id content on-click on-hide config format))))
|