Merge pull request #1108 from logseq/feat/support-remove-image

Feat/support remove image
pull/1139/head
Tienson Qin 2021-01-15 06:52:56 -08:00 committed by GitHub
commit 88df1537fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 247 additions and 71 deletions

View File

@ -17,7 +17,9 @@ dummy.getRangeAt = function() {};
dummy.getElementsByClassName = function() {};
dummy.containsNode = function() {};
dummy.select = function() {};
dummy.closest = function () {};
dummy.setAttribute = function() {};
dummy.getAttribute = function() {};
dummy.font = function() {};
dummy.measureText = function() {};
dummy.fillStyle = function() {};

View File

@ -578,11 +578,6 @@ h1.title {
padding: -1px;
}
.content img {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
span.timestamp {
margin: 0 0.25rem;
}

View File

@ -159,30 +159,66 @@
parts (remove #(string/blank? %) parts)]
(string/join "/" (reverse parts))))))))
(defonce *resizing-image? (atom false))
(rum/defcs resizable-image <
(rum/local nil ::size)
[state config title src metadata full_text]
(let [size (get state ::size)]
(ui/resize-provider
(ui/resize-consumer
(cond->
{:className "resize"
:onSizeChanged #(reset! size %)
:onMouseUp (fn []
(when @size
(when-let [block-id (:block/uuid config)]
(let [size (bean/->clj @size)]
(editor-handler/resize-image! block-id metadata full_text size)))))
:onClick (fn [e]
(util/stop e))}
(:width metadata)
(assoc :style {:width (:width metadata)}))
[:img.rounded-sm.shadow-xl
(merge
{:loading "lazy"
:src src
:title title}
metadata)]))))
{:will-unmount (fn [state]
(reset! *resizing-image? false)
state)}
[state config title src metadata full_text local?]
(rum/with-context [[t] i18n/*tongue-context*]
(let [size (get state ::size)]
(ui/resize-provider
(ui/resize-consumer
(cond->
{:className "resize"
:onSizeChanged (fn [value]
(when (and (not @*resizing-image?)
(some? @size)
(not= value @size))
(reset! *resizing-image? true))
(reset! size value))
:onMouseUp (fn []
(when @size
(when-let [block-id (:block/uuid config)]
(let [size (bean/->clj @size)]
(editor-handler/resize-image! block-id metadata full_text size))))
(when @*resizing-image?
;; TODO: need a better way to prevent the clicking to edit current block
(js/setTimeout #(reset! *resizing-image? false) 200)))
:onClick (fn [e]
(when @*resizing-image? (util/stop e)))}
(:width metadata)
(assoc :style {:width (:width metadata)}))
[:div.asset-container
[:img.rounded-sm.shadow-xl.relative
(merge
{:loading "lazy"
:src src
:title title}
metadata)]
[:span.ctl
[:a.delete
{:title "Delete this image"
:on-click
(fn [e]
(when-let [block-id (:block/uuid config)]
(let [confirm-fn (ui/make-confirm-modal
{:title (t :asset/confirm-delete (.toLocaleLowerCase (t :text/image)))
:sub-title (if local? :asset/physical-delete "")
:sub-checkbox? local?
:on-confirm (fn [e {:keys [close-fn sub-selected]}]
(close-fn)
(editor-handler/delete-asset-of-block!
{:block-id block-id
:local? local?
:repo (state/get-current-repo)
:href src
:title title
:full-text full_text}))})]
(state/set-modal! confirm-fn)
(util/stop e))))}
svg/trash-sm]]])))))
(rum/defcs asset-link < rum/reactive
(rum/local nil ::src)
@ -194,7 +230,7 @@
(p/then (editor-handler/make-asset-url href) #(reset! src %)))
(when @src
(resizable-image config title @src metadata full_text))))
(resizable-image config title @src metadata full_text true))))
;; TODO: safe encoding asciis
;; TODO: image link to another link
@ -205,13 +241,12 @@
nil
(safe-read-string metadata false))
title (second (first label))]
(if (or (util/starts-with? href "/assets")
(util/starts-with? href "../assets"))
(if (config/local-asset? href)
(asset-link config title href label metadata full_text)
(let [href (if (util/starts-with? href "http")
href
(get-file-absolute-path config href))]
(resizable-image config title href metadata full_text)))))
(resizable-image config title href metadata full_text false)))))
(defn repetition-to-string
[[[kind] [duration] n]]
@ -1412,7 +1447,8 @@
(reset! *dragging-block nil)
(editor-handler/unhighlight-block!))
:on-mouse-move (fn [e]
(when (non-dragging? e)
(when (and (non-dragging? e)
(not *resizing-image?))
(state/into-selection-mode!)))
:on-mouse-down (fn [e]
(when (and

View File

@ -25,6 +25,53 @@
width: 9px;
}
}
.asset-container {
display: inline-block;
position: relative;
margin-top: .5rem;
margin-bottom: .5rem;
.ctl {
position: absolute;
top: 0;
right: 0;
padding: 5px;
z-index: 1;
display: none;
> a {
padding: 3px;
border-radius: 4px;
opacity: .4;
user-select: none;
background: var(--ls-primary-background-color);
&.delete {
svg {
color: #FFF;
opacity: .5;
font-weight: normal;
}
}
&:hover {
opacity: 1;
}
&:active {
opacity: 1;
}
}
}
&:hover {
.ctl {
display: flex;
}
}
}
}
.open-block-ref-link {

View File

@ -268,6 +268,7 @@
(util/starts-with? path default-draw-directory))
(defonce local-repo "local")
(defonce local-assets-dir "assets")
(def config-file "config.edn")
(def custom-css-file "custom.css")
(def metadata-file "metadata.edn")
@ -289,6 +290,11 @@
[s]
(string/starts-with? s local-db-prefix))
(defn local-asset?
[s]
(or (string/starts-with? s (str "/" local-assets-dir))
(string/starts-with? s (str "../" local-assets-dir))))
(defn get-local-dir
[s]
(string/replace s local-db-prefix ""))

View File

@ -269,8 +269,12 @@
(:file/content (d/entity (d/db conn) [:file/path path]))))))
(defn get-block-by-uuid
[uuid]
(db-utils/entity [:block/uuid uuid]))
[id]
(db-utils/entity [:block/uuid (if (uuid? id) id (uuid id))]))
(defn query-block-by-uuid
[id]
(db-utils/pull [:block/uuid (if (uuid? id) id (uuid id))]))
(defn get-page-format
[page-name]

View File

@ -266,6 +266,9 @@ title: How to take dummy notes?
:draw/delete "Delete"
:draw/more-options "More options"
:draw/back-to-logseq "Back to logseq"
:text/image "Image"
:asset/confirm-delete "Are you sure you want to delete this {1}?"
:asset/physical-delete "Remove the file too (notice it can't be restored)"
:content/copy "Copy"
:content/cut "Cut"
:content/make-todos "Make {1}s"
@ -619,6 +622,9 @@ title: How to take dummy notes?
:help/create-new-block "创建块"
:help/new-line-in-block "块中新建行"
:help/select-nfs-browser "请选择支持nfs的浏览来使用logseq本地文件夹功能, 如最新的Chrome浏览器."
:text/image "图片"
:asset/confirm-delete "确定要删除{1}吗?"
:asset/physical-delete "同时删除本地文件(目前不可撤销)"
:undo "撤销"
:redo "重做"
:help/zoom-in "聚焦"

View File

@ -1,6 +1,8 @@
(ns frontend.handler.editor
(:require [frontend.state :as state]
[lambdaisland.glogi :as log]
[frontend.db.model :as db-model]
[frontend.db.utils :as db-utils]
[frontend.handler.common :as common-handler]
[frontend.handler.route :as route-handler]
[frontend.handler.git :as git-handler]
@ -1419,10 +1421,16 @@
opts))))
(defn save-block!
[{:keys [format block id repo dummy?] :as state} value]
(when (or (:db/id (db/entity repo [:block/uuid (:block/uuid block)]))
dummy?)
(save-block-aux! block value format {})))
([repo block-or-uuid content]
(let [block (if (or (uuid? block-or-uuid)
(string? block-or-uuid))
(db-model/query-block-by-uuid block-or-uuid) block-or-uuid)
format (:block/format block)]
(save-block! {:block block :repo repo :format format} content)))
([{:keys [format block repo dummy?] :as state} value]
(when (or (:db/id (db/entity repo [:block/uuid (:block/uuid block)]))
dummy?)
(save-block-aux! block value format {}))))
(defn save-current-block-when-idle!
([]
@ -1552,7 +1560,7 @@
(p/then (fs/write-file repo dir filename (.stream file))
#(p/resolved [filename file])))))))
(def *assets-url-cache (atom {}))
(defonce *assets-url-cache (atom {}))
(defn make-asset-url
[path] ;; path start with "/assets" or compatible for "../assets"
@ -1569,6 +1577,18 @@
(swap! *assets-url-cache assoc (keyword handle-path) url)
url))))))
(defn delete-asset-of-block!
[{:keys [repo href title full-text block-id local?] :as opts}]
(let [block (db-model/query-block-by-uuid block-id)
_ (or block (throw (str block-id " not exists")))
format (:block/format block)
text (:block/content block)
content (string/replace text full-text "")]
(save-block! repo block content)
(when local?
;; FIXME: should be relative to current block page path
(fs/unlink (str (util/get-repo-dir repo) (string/replace href #"^../" "/")) nil))))
(defn upload-image
[id files format uploading? drop-or-paste?]
(let [repo (state/get-current-repo)

View File

@ -13,7 +13,8 @@
[goog.object :as gobj]
[goog.dom :as gdom]
[medley.core :as medley]
[frontend.ui.date-picker]))
[frontend.ui.date-picker]
[frontend.context.i18n :as i18n]))
(defonce transition-group (r/adapt-class TransitionGroup))
(defonce css-transition (r/adapt-class CSSTransition))
@ -62,7 +63,7 @@
:or {z-index 999}
:as opts}]]
(let [{:keys [open? toggle-fn]} state
modal-content (modal-content-fn state)]
modal-content (modal-content-fn state)]
[:div.ml-1.relative {:style {:z-index z-index}}
(content-fn state)
(css-transition
@ -94,8 +95,8 @@
child [:div
{:style {:display "flex" :flex-direction "row"}}
[:div {:style {:margin-right "8px"}} title]
;; [:div {:style {:position "absolute" :right "8px"}}
;; icon]
;; [:div {:style {:position "absolute" :right "8px"}}
;; icon]
]]
(rum/with-key
(menu-link new-options child)
@ -104,18 +105,18 @@
(defn button
[text & {:keys [background on-click href]
:as option}]
:as option}]
(let [class "inline-flex.items-center.px-3.py-2.border.border-transparent.text-sm.leading-4.font-medium.rounded-md.text-white.bg-indigo-600.hover:bg-indigo-700.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.active:bg-indigo-700.transition.ease-in-out.duration-150.mt-1"
class (if background (string/replace class "indigo" background) class)]
(if href
[:a.button (merge
{:type "button"
{:type "button"
:class (util/hiccup->class class)}
(dissoc option :background))
text]
[:button
(merge
{:type "button"
{:type "button"
:class (util/hiccup->class class)}
(dissoc option :background))
text])))
@ -130,19 +131,19 @@
[:svg.h-6.w-6.text-green-400
{:stroke "currentColor", :viewBox "0 0 24 24", :fill "none"}
[:path
{:d "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
:stroke-width "2"
{:d "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
:stroke-width "2"
:stroke-linejoin "round"
:stroke-linecap "round"}]]]
:stroke-linecap "round"}]]]
:warning
["text-gray-900"
[:svg.h-6.w-6.text-yellow-500
{:stroke "currentColor", :viewBox "0 0 24 24", :fill "none"}
[:path
{:d "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
:stroke-width "2"
{:d "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
:stroke-width "2"
:stroke-linejoin "round"
:stroke-linecap "round"}]]]
:stroke-linecap "round"}]]]
["text-red-500"
[:svg.h-6.w-6.text-red-500
@ -157,7 +158,7 @@
(= state "exited"))
-1
99)
:top "3.2em"}}
:top "3.2em"}}
[:div.max-w-sm.w-full.shadow-lg.rounded-lg.pointer-events-auto.notification-area
{:class (case state
"entering" "transition ease-out duration-300 transform opacity-0 translate-y-2 sm:translate-x-0"
@ -195,7 +196,7 @@
v (second el)]
(css-transition
{:timeout 100
:key (name k)}
:key (name k)}
(fn [state]
(notification-content state (:content v) (:status v) k)))))
contents)))))
@ -300,7 +301,7 @@
(mixins/event-mixin attach-listeners)
"Render an infinite list."
[state body {:keys [on-load on-top-reached]
:as opts}]
:as opts}]
body)
(rum/defcs auto-complete <
@ -327,7 +328,7 @@
element-top (gobj/get element "offsetTop")
scroll-top (- (gobj/get element "offsetTop") 360)]
(set! (.-scrollTop ac-inner) scroll-top)))))
;; down
;; down
40 (fn [state e]
(let [current-idx (get state ::current-idx)
matched (first (:rum/args state))]
@ -342,7 +343,7 @@
scroll-top (- (gobj/get element "offsetTop") 360)]
(set! (.-scrollTop ac-inner) scroll-top)))))
;; enter
;; enter
13 (fn [state e]
(util/stop e)
(let [[matched {:keys [on-chosen on-enter]}] (:rum/args state)]
@ -368,7 +369,7 @@
{:id (str "ac-" idx)
:class (when (= @current-idx idx)
"chosen")
;; :tab-index -1
;; :tab-index -1
:on-click (fn [e]
(.preventDefault e)
(if (and (gobj/get e "shiftKey") on-shift-chosen)
@ -386,9 +387,9 @@
[:a {:on-click on-click}
[:span.relative.inline-block.flex-shrink-0.h-6.w-11.border-2.border-transparent.rounded-full.cursor-pointer.transition-colors.ease-in-out.duration-200.focus:outline-none.focus:shadow-outline
{:aria-checked "false", :tab-index "0", :role "checkbox"
:class (if on? "bg-indigo-600" "bg-gray-200")}
:class (if on? "bg-indigo-600" "bg-gray-200")}
[:span.inline-block.h-5.w-5.rounded-full.bg-white.shadow.transform.transition.ease-in-out.duration-200
{:class (if on? "translate-x-5" "translate-x-0")
{:class (if on? "translate-x-5" "translate-x-0")
:aria-hidden "true"}]]])
(defn tooltip
@ -425,15 +426,15 @@
[:div.absolute.top-0.right-0.pt-4.pr-4
[:button.text-gray-400.hover:text-gray-500.focus:outline-none.focus:text-gray-500.transition.ease-in-out.duration-150
{:aria-label "Close"
:type "button"
:on-click close-fn}
:type "button"
:on-click close-fn}
[:svg.h-6.w-6
{:stroke "currentColor", :view-box "0 0 24 24", :fill "none"}
[:path
{:d "M6 18L18 6M6 6l12 12"
:stroke-width "2"
{:d "M6 18L18 6M6 6l12 12"
:stroke-width "2"
:stroke-linejoin "round"
:stroke-linecap "round"}]]]]
:stroke-linecap "round"}]]]]
(panel-content close-fn)])
@ -454,6 +455,52 @@
(fn [state]
(modal-panel modal-panel-content state close-fn)))]))
(defn make-confirm-modal
[{:keys [tag title sub-title sub-checkbox? on-cancel on-confirm] :as opts}]
(fn [close-fn]
(rum/with-context [[t] i18n/*tongue-context*]
(let [*sub-checkbox-selected (and sub-checkbox? (atom []))]
[:div.ui__confirm-modal
{:class (str "is-" tag)}
[:div.sm:flex.sm:items-start
[:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-red-100.sm:mx-0.sm:h-10.sm:w-10
[:svg.h-6.w-6.text-red-600
{:stroke "currentColor", :view-box "0 0 24 24", :fill "none"}
[:path
{:d
"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
:stroke-width "2"
:stroke-linejoin "round"
:stroke-linecap "round"}]]]
[:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
[:h2.headline.text-lg.leading-6.font-medium.text-gray-900
(if (keyword? title) (t title) title)]
[:label.sublabel
(when sub-checkbox?
(checkbox
{:default-value false
:on-change (fn [e]
(let [checked (.. e -target -checked)]
(reset! *sub-checkbox-selected [checked])))}))
[:h3.subline.text-gray-400
(if (keyword? sub-title)
(t sub-title)
sub-title)]]]]
[:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
[:span.flex.w-full.rounded-md.shadow-sm.sm:ml-3.sm:w-auto
[:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
{:type "button"
:on-click #(and (fn? on-confirm)
(on-confirm % {:close-fn close-fn
:sub-selected (and *sub-checkbox-selected @*sub-checkbox-selected)}))}
(t :yes)]]
[:span.mt-3.flex.w-full.rounded-md.shadow-sm.sm:mt-0.sm:w-auto
[:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
{:type "button"
:on-click (comp on-cancel close-fn)}
(t :cancel)]]]]))))
(defn loading
[content]
[:div.flex.flex-row.items-center
@ -474,12 +521,12 @@
[:div.flex.flex-col
[:div.content
[:div.flex-1.flex-row.foldable-title {:on-mouse-over #(reset! control? true)
:on-mouse-out #(reset! control? false)}
:on-mouse-out #(reset! control? false)}
[:div.flex.flex-row.items-center
[:a.block-control.opacity-50.hover:opacity-100.mr-2
{:style {:width 14
:height 16
:margin-left -24}
{:style {:width 14
:height 16
:margin-left -24}
:on-click (fn [e]
(util/stop e)
(swap! collapsed? not))}
@ -537,13 +584,13 @@
(rum/defc select
[options on-change]
[:select.mt-1.form-select.block.w-full.px-3.text-base.leading-6.border-gray-300.focus:outline-none.focus:shadow-outline-blue.focus:border-blue-300.sm:text-sm.sm:leading-5.ml-4
{:style {:padding "0 0 0 12px"}
{:style {:padding "0 0 0 12px"}
:on-change (fn [e]
(let [value (util/evalue e)]
(on-change value)))}
(for [{:keys [label value selected]} options]
[:option (cond->
{:key label
{:key label
:value (or value label)}
selected
(assoc :selected selected))

View File

@ -30,6 +30,19 @@
}
}
.ui__confirm-modal {
.sublabel {
display: flex;
padding: 2px 0;
align-items: center;
font-size: 14px;
input[type=checkbox] {
margin-right: 8px;
}
}
}
.dropdown-wrapper {
background-color: var(--ls-primary-background-color, #fff);
min-width: 12rem;