mirror of
https://github.com/logseq/logseq.git
synced 2026-06-01 19:01:22 +00:00
feat: add block timestamps
Why adding block timestamps? Block is the atomic unit for logseq instead of a page, with the timestamps we can do a lot of things, for example: 1. show a blocks timeline related to a topic/project/habit. 2. get latest modified blocks from the page or the whole graph. 3. query dsl can have a (sort-by :last_modified_at) clause It'll add both created_at and last_modified_at properties to each block, not all users need this, so, it's an experiment feature, we'll disable it by default. Also, this PR simplify the block properties a lot, it might fix some old issues related to properties.
This commit is contained in:
@@ -1040,10 +1040,11 @@
|
||||
(when (seq properties)
|
||||
[:div.blocks-properties.text-sm.opacity-80.my-1.p-2
|
||||
(for [[k v] properties]
|
||||
^{:key (str (:block/uuid block) "-" k)}
|
||||
[:div.my-1
|
||||
[:b k]
|
||||
[:span.mr-1 ":"]
|
||||
(inline-text (:block/format block) v)])])))
|
||||
(inline-text (:block/format block) (str v))])])))
|
||||
|
||||
(rum/defcs timestamp-cp < rum/reactive
|
||||
(rum/local false ::show?)
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
preferred-workflow (keyword (state/sub [:me :preferred_workflow]))
|
||||
preferred-language (state/sub [:preferred-language])
|
||||
enable-timetracking? (state/enable-timetracking?)
|
||||
enable-block-time? (state/enable-block-time?)
|
||||
show-brackets? (state/show-brackets?)
|
||||
github-token (state/sub [:me :access-token])
|
||||
cors-proxy (state/sub [:me :cors_proxy])
|
||||
@@ -128,101 +129,112 @@
|
||||
[:a {:href (str "/file/" (util/url-encode (str config/app-name "/" config/config-file)))}
|
||||
(t :settings-page/edit-config-edn)])
|
||||
|
||||
(when logged? [:hr])
|
||||
[:hr]
|
||||
|
||||
(when logged?
|
||||
[:div.mt-6.sm:mt-5
|
||||
[:div.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start.sm:pt-5
|
||||
[:label.block.text-sm.font-medium.leading-5.sm:mt-px.sm:pt-2.opacity-70
|
||||
{:for "preferred_format"}
|
||||
(t :settings-page/preferred-file-format)]
|
||||
[:div.mt-1.sm:mt-0.sm:col-span-2
|
||||
[:div.max-w-lg.rounded-md.shadow-sm.sm:max-w-xs
|
||||
[:select.mt-1.form-select.block.w-full.pl-3.pr-10.py-2.text-base.leading-6.border-gray-300.focus:outline-none.focus:shadow-outline-blue.focus:border-blue-300.sm:text-sm.sm:leading-5
|
||||
{:on-change (fn [e]
|
||||
(let [format (-> (util/evalue e)
|
||||
[:div.mt-6.sm:mt-5
|
||||
[:div.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start.sm:pt-5
|
||||
[:label.block.text-sm.font-medium.leading-5.sm:mt-px.sm:pt-2.opacity-70
|
||||
{:for "preferred_format"}
|
||||
(t :settings-page/preferred-file-format)]
|
||||
[:div.mt-1.sm:mt-0.sm:col-span-2
|
||||
[:div.max-w-lg.rounded-md.shadow-sm.sm:max-w-xs
|
||||
[:select.mt-1.form-select.block.w-full.pl-3.pr-10.py-2.text-base.leading-6.border-gray-300.focus:outline-none.focus:shadow-outline-blue.focus:border-blue-300.sm:text-sm.sm:leading-5
|
||||
{:on-change (fn [e]
|
||||
(let [format (-> (util/evalue e)
|
||||
(string/lower-case)
|
||||
keyword)]
|
||||
(user-handler/set-preferred-format! format)))}
|
||||
(for [format [:org :markdown]]
|
||||
[:option (cond->
|
||||
{:key (name format)}
|
||||
(= format preferred-format)
|
||||
(assoc :selected "selected"))
|
||||
(string/capitalize (name format))])]]]]
|
||||
[:div.mt-6.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start.sm:pt-5
|
||||
[:label.block.text-sm.font-medium.leading-5.sm:mt-px.sm:pt-2.opacity-70
|
||||
{:for "preferred_workflow"}
|
||||
(t :settings-page/preferred-workflow)]
|
||||
[:div.mt-1.sm:mt-0.sm:col-span-2
|
||||
[:div.max-w-lg.rounded-md.shadow-sm.sm:max-w-xs
|
||||
[:select.mt-1.form-select.block.w-full.pl-3.pr-10.py-2.text-base.leading-6.border-gray-300.focus:outline-none.focus:shadow-outline-blue.focus:border-blue-300.sm:text-sm.sm:leading-5
|
||||
{:on-change (fn [e]
|
||||
(let [workflow (-> (util/evalue e)
|
||||
(string/lower-case)
|
||||
keyword)]
|
||||
(user-handler/set-preferred-format! format)))}
|
||||
(for [format [:org :markdown]]
|
||||
[:option (cond->
|
||||
{:key (name format)}
|
||||
(= format preferred-format)
|
||||
(assoc :selected "selected"))
|
||||
(string/capitalize (name format))])]]]]
|
||||
[:div.mt-6.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start.sm:pt-5
|
||||
[:label.block.text-sm.font-medium.leading-5.sm:mt-px.sm:pt-2.opacity-70
|
||||
{:for "preferred_workflow"}
|
||||
(t :settings-page/preferred-workflow)]
|
||||
[:div.mt-1.sm:mt-0.sm:col-span-2
|
||||
[:div.max-w-lg.rounded-md.shadow-sm.sm:max-w-xs
|
||||
[:select.mt-1.form-select.block.w-full.pl-3.pr-10.py-2.text-base.leading-6.border-gray-300.focus:outline-none.focus:shadow-outline-blue.focus:border-blue-300.sm:text-sm.sm:leading-5
|
||||
{:on-change (fn [e]
|
||||
(let [workflow (-> (util/evalue e)
|
||||
(string/lower-case)
|
||||
keyword)
|
||||
workflow (if (= workflow :now/later)
|
||||
:now
|
||||
:todo)]
|
||||
(user-handler/set-preferred-workflow! workflow)))}
|
||||
(for [workflow [:now :todo]]
|
||||
[:option (cond->
|
||||
{:key (name workflow)}
|
||||
(= workflow preferred-workflow)
|
||||
(assoc :selected "selected"))
|
||||
(if (= workflow :now)
|
||||
"NOW/LATER"
|
||||
"TODO/DOING")])]]]]
|
||||
keyword)
|
||||
workflow (if (= workflow :now/later)
|
||||
:now
|
||||
:todo)]
|
||||
(user-handler/set-preferred-workflow! workflow)))}
|
||||
(for [workflow [:now :todo]]
|
||||
[:option (cond->
|
||||
{:key (name workflow)}
|
||||
(= workflow preferred-workflow)
|
||||
(assoc :selected "selected"))
|
||||
(if (= workflow :now)
|
||||
"NOW/LATER"
|
||||
"TODO/DOING")])]]]]
|
||||
|
||||
[:div.mt-6.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start.sm:pt-5
|
||||
[:label.block.text-sm.font-medium.leading-5.opacity-70
|
||||
{:for "enable_timetracking"}
|
||||
(t :settings-page/enable-timetracking)]
|
||||
[:div.mt-1.sm:mt-0.sm:col-span-2
|
||||
[:div.max-w-lg.rounded-md.sm:max-w-xs
|
||||
(ui/toggle enable-timetracking?
|
||||
(fn []
|
||||
(let [value (not enable-timetracking?)]
|
||||
(config-handler/set-config! :feature/enable-timetracking? value))))]]]
|
||||
[:div.mt-6.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start.sm:pt-5
|
||||
[:label.block.text-sm.font-medium.leading-5.opacity-70
|
||||
{:for "enable_timetracking"}
|
||||
(t :settings-page/enable-timetracking)]
|
||||
[:div.mt-1.sm:mt-0.sm:col-span-2
|
||||
[:div.max-w-lg.rounded-md.sm:max-w-xs
|
||||
(ui/toggle enable-timetracking?
|
||||
(fn []
|
||||
(let [value (not enable-timetracking?)]
|
||||
(config-handler/set-config! :feature/enable-timetracking? value))))]]]
|
||||
|
||||
[:hr]
|
||||
[:div.mt-6.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start.sm:pt-5
|
||||
[:label.block.text-sm.font-medium.leading-5.opacity-70
|
||||
{:for "enable_block_time"}
|
||||
(t :settings-page/enable-block-time)]
|
||||
[:div.mt-1.sm:mt-0.sm:col-span-2
|
||||
[:div.max-w-lg.rounded-md.sm:max-w-xs
|
||||
(ui/toggle enable-block-time?
|
||||
(fn []
|
||||
(let [value (not enable-block-time?)]
|
||||
(config-handler/set-config! :feature/enable-block-time? value))))]]]
|
||||
|
||||
(ui/admonition
|
||||
:important
|
||||
[:p (t :settings-page/dont-use-other-peoples-proxy-servers)
|
||||
[:a {:href "https://github.com/isomorphic-git/cors-proxy"
|
||||
:target "_blank"}
|
||||
"https://github.com/isomorphic-git/cors-proxy"]])
|
||||
[:hr]
|
||||
|
||||
[:div.mt-6.sm:mt-5.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start.sm:pt-5
|
||||
[:label.block.text-sm.font-medium.leading-5.sm:mt-px.sm:pt-2.opacity-70
|
||||
{:for "cors"}
|
||||
(t :settings-page/custom-cors-proxy-server)]
|
||||
[:div.mt-1.sm:mt-0.sm:col-span-2
|
||||
[:div.max-w-lg.rounded-md.shadow-sm.sm:max-w-xs
|
||||
[:input#pat.form-input.block.w-full.transition.duration-150.ease-in-out.sm:text-sm.sm:leading-5
|
||||
{:default-value cors-proxy
|
||||
:on-blur (fn [event]
|
||||
(when-let [server (util/evalue event)]
|
||||
(user-handler/set-cors! server)
|
||||
(notification/show! "Custom CORS proxy updated successfully!" :success)))
|
||||
:on-key-press (fn [event]
|
||||
(let [k (gobj/get event "key")]
|
||||
(if (= "Enter" k)
|
||||
(when-let [server (util/evalue event)]
|
||||
(user-handler/set-cors! server)
|
||||
(notification/show! "Custom CORS proxy updated successfully!" :success)))))}]]]]
|
||||
(when logged?
|
||||
[:div
|
||||
(ui/admonition
|
||||
:important
|
||||
[:p (t :settings-page/dont-use-other-peoples-proxy-servers)
|
||||
[:a {:href "https://github.com/isomorphic-git/cors-proxy"
|
||||
:target "_blank"}
|
||||
"https://github.com/isomorphic-git/cors-proxy"]])
|
||||
[:div.mt-6.sm:mt-5.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start.sm:pt-5
|
||||
[:label.block.text-sm.font-medium.leading-5.sm:mt-px.sm:pt-2.opacity-70
|
||||
{:for "cors"}
|
||||
(t :settings-page/custom-cors-proxy-server)]
|
||||
[:div.mt-1.sm:mt-0.sm:col-span-2
|
||||
[:div.max-w-lg.rounded-md.shadow-sm.sm:max-w-xs
|
||||
[:input#pat.form-input.block.w-full.transition.duration-150.ease-in-out.sm:text-sm.sm:leading-5
|
||||
{:default-value cors-proxy
|
||||
:on-blur (fn [event]
|
||||
(when-let [server (util/evalue event)]
|
||||
(user-handler/set-cors! server)
|
||||
(notification/show! "Custom CORS proxy updated successfully!" :success)))
|
||||
:on-key-press (fn [event]
|
||||
(let [k (gobj/get event "key")]
|
||||
(if (= "Enter" k)
|
||||
(when-let [server (util/evalue event)]
|
||||
(user-handler/set-cors! server)
|
||||
(notification/show! "Custom CORS proxy updated successfully!" :success)))))}]]]]
|
||||
|
||||
[:hr]
|
||||
[:hr]])
|
||||
|
||||
[:div.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start.sm:pt-5
|
||||
[:label.block.text-sm.font-medium.leading-5.sm:mt-px.sm:pt-2.opacity-70
|
||||
{:for "developer_mode"}
|
||||
(t :settings-page/developer-mode)]
|
||||
[:div.mt-1.sm:mt-0.sm:col-span-2
|
||||
[:div.max-w-lg.rounded-md.shadow-sm.sm:max-w-xs
|
||||
(ui/button (if developer-mode? (t :settings-page/disable-developer-mode) (t :settings-page/enable-developer-mode))
|
||||
:on-click #(state/set-developer-mode! (not developer-mode?)))]]]
|
||||
[:div.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start.sm:pt-5
|
||||
[:label.block.text-sm.font-medium.leading-5.sm:mt-px.sm:pt-2.opacity-70
|
||||
{:for "developer_mode"}
|
||||
(t :settings-page/developer-mode)]
|
||||
[:div.mt-1.sm:mt-0.sm:col-span-2
|
||||
[:div.max-w-lg.rounded-md.shadow-sm.sm:max-w-xs
|
||||
(ui/button (if developer-mode? (t :settings-page/disable-developer-mode) (t :settings-page/enable-developer-mode))
|
||||
:on-click #(state/set-developer-mode! (not developer-mode?)))]]]
|
||||
|
||||
[:br]
|
||||
(t :settings-page/developer-mode-desc)])]])))
|
||||
[:br]
|
||||
(t :settings-page/developer-mode-desc)]]])))
|
||||
|
||||
@@ -94,17 +94,5 @@
|
||||
:block/deadline-ast {}
|
||||
:block/repeated? {}
|
||||
|
||||
;; TODO: To make this really working, every block needs a persisting `CUSTOM-ID`, which I'd like to avoid for now.
|
||||
;; Any suggestions?
|
||||
:block/created-at {}
|
||||
:block/last-modified-at {}
|
||||
|
||||
;; For pages
|
||||
:tag/name {:db/unique :db.unique/identity}
|
||||
;; ;; Definitions, useful for tags and future anki cards
|
||||
;; :definition/block {:db/valueType :db.type/ref}
|
||||
;; ;; Why not make :definition/key unique?
|
||||
;; ;; Multiple definitions with the same key in either one page or multiple pages
|
||||
;; :definition/key {}
|
||||
;; :definition/value {}
|
||||
})
|
||||
:tag/name {:db/unique :db.unique/identity}})
|
||||
|
||||
@@ -288,6 +288,7 @@ title: How to take dummy notes?
|
||||
:settings-page/preferred-file-format "Preferred file format"
|
||||
:settings-page/preferred-workflow "Preferred workflow"
|
||||
:settings-page/enable-timetracking "Enable timetracking"
|
||||
:settings-page/enable-block-time "Enable block timestamps"
|
||||
:settings-page/dont-use-other-peoples-proxy-servers "Don't use other people's proxy servers. It's very dangerous, which could make your token and notes stolen. Logseq will not be responsible for this loss if you use other people's proxy servers. You can deploy it yourself, check "
|
||||
:settings-page/custom-cors-proxy-server "Custom CORS proxy server"
|
||||
:settings-page/developer-mode "Developer mode"
|
||||
@@ -763,6 +764,7 @@ title: How to take dummy notes?
|
||||
:settings-page/preferred-file-format "首选文件格式"
|
||||
:settings-page/preferred-workflow "首选工作流"
|
||||
:settings-page/enable-timetracking "开启 timetracking"
|
||||
:settings-page/enable-block-time "记录 block 创建/修改时间"
|
||||
:settings-page/dont-use-other-peoples-proxy-servers "不要使用其他人的代理服务器。这非常危险,可能会使您的令牌和笔记被盗。 如果您使用其他人的代理服务器,Logseq 将不会对此损失负责。您可以自己部署它,请查阅 "
|
||||
:settings-page/custom-cors-proxy-server "自定义 CORS 代理服务器"
|
||||
:settings-page/developer-mode "开发者模式"
|
||||
|
||||
@@ -134,6 +134,12 @@
|
||||
(= "List" (first block))
|
||||
(:name (first (second block)))))
|
||||
|
||||
(defn- ->schema-properties
|
||||
[properties]
|
||||
(-> properties
|
||||
(update "created_at" util/safe-parse-int)
|
||||
(update "last_modified_at" util/safe-parse-int)))
|
||||
|
||||
(defn extract-properties
|
||||
[[_ properties] start-pos end-pos]
|
||||
(let [properties (->> (into {} properties)
|
||||
@@ -146,7 +152,7 @@
|
||||
(util/safe-parse-int v')
|
||||
v')]
|
||||
[k' v']))))]
|
||||
{:properties properties
|
||||
{:properties (->schema-properties properties)
|
||||
:start-pos start-pos
|
||||
:end-pos end-pos}))
|
||||
|
||||
@@ -379,7 +385,7 @@
|
||||
block
|
||||
{:block/meta meta
|
||||
:block/marker (get block :block/marker "nil")
|
||||
:block/properties (get block :block/properties [])
|
||||
:block/properties (get block :block/properties {})
|
||||
:block/file file
|
||||
:block/format format
|
||||
:block/page page
|
||||
|
||||
@@ -87,23 +87,11 @@
|
||||
|
||||
:else
|
||||
(state/set-db-restoring! false))
|
||||
(if (schema-changed?)
|
||||
(do
|
||||
(notification/show!
|
||||
[:p "Database schema changed, your notes will be exported as zip files, your repos will be re-indexed then."]
|
||||
:warning
|
||||
false)
|
||||
(let [export-repos (for [repo repos]
|
||||
(when-let [url (:url repo)]
|
||||
(println "Export repo: " url)
|
||||
(export-handler/export-repo-as-zip! url)))]
|
||||
(-> (p/all export-repos)
|
||||
(p/then (fn []
|
||||
(store-schema!)
|
||||
(js/setTimeout clear-stores-and-refresh! 5000)))
|
||||
(p/catch (fn [error]
|
||||
(log/error :export/zip {:error error
|
||||
:repos repos}))))))
|
||||
(if false ; FIXME: incompatible changes
|
||||
(notification/show!
|
||||
[:p "Database schema changed, please export your notes as a zip file, and re-index your repos."]
|
||||
:warning
|
||||
false)
|
||||
(store-schema!))
|
||||
|
||||
(nfs/ask-permission-if-local?)
|
||||
|
||||
@@ -380,22 +380,43 @@
|
||||
"ls-block"
|
||||
"edit-block"))))
|
||||
|
||||
(defn- with-time-properties
|
||||
[block properties]
|
||||
(if (state/enable-block-time?)
|
||||
(let [time (util/time-ms)
|
||||
props (into {} (:block/properties block))]
|
||||
(merge properties
|
||||
(if-let [created-at (get props "created_at")]
|
||||
{"created_at" created-at
|
||||
"last_modified_at" time}
|
||||
{"created_at" time
|
||||
"last_modified_at" time})))
|
||||
properties))
|
||||
|
||||
(defn- block-text-with-time
|
||||
[block format value]
|
||||
(let [value (text/remove-level-spaces value (keyword format))
|
||||
properties (with-time-properties block {})]
|
||||
(text/re-construct-block-properties value properties)))
|
||||
|
||||
(defn save-block-if-changed!
|
||||
([block value]
|
||||
(save-block-if-changed! block value nil))
|
||||
([{:block/keys [uuid content meta file page dummy? format repo pre-block? content ref-pages ref-blocks] :as block}
|
||||
value
|
||||
{:keys [indent-left? custom-properties remove-property? rebuild-content?]
|
||||
{:keys [indent-left? custom-properties remove-properties rebuild-content?]
|
||||
:or {rebuild-content? true
|
||||
custom-properties nil
|
||||
remove-property? false}}]
|
||||
remove-properties nil}}]
|
||||
(let [value value
|
||||
repo (or repo (state/get-current-repo))
|
||||
e (db/entity repo [:block/uuid uuid])
|
||||
block (assoc (with-block-meta repo block)
|
||||
:block/properties (:block/properties e))
|
||||
;; (into {} ...) to fix the old data
|
||||
:block/properties (into {} (:block/properties e)))
|
||||
format (or format (state/get-preferred-format))
|
||||
page (db/entity repo (:db/id page))
|
||||
;; page properties
|
||||
[old-properties new-properties] (when pre-block?
|
||||
[(:page/properties (db/entity (:db/id page)))
|
||||
(mldoc/parse-properties value format)])
|
||||
@@ -415,17 +436,14 @@
|
||||
new-properties (if permalink-changed?
|
||||
(assoc new-properties :old_permalink (:permalink old-properties))
|
||||
new-properties)
|
||||
value (cond
|
||||
(or (seq custom-properties)
|
||||
remove-property?)
|
||||
(text/re-construct-block-properties block value custom-properties)
|
||||
|
||||
(and (seq (:block/properties block))
|
||||
(text/properties-hidden? (:block/properties block)))
|
||||
(text/re-construct-block-properties block value (:block/properties block))
|
||||
|
||||
:else
|
||||
value)]
|
||||
text-properties (text/extract-properties value)
|
||||
properties (->> custom-properties
|
||||
(with-time-properties block)
|
||||
(merge text-properties))
|
||||
properties (if (and (seq properties) (seq remove-properties))
|
||||
(medley/remove-keys (fn [k] (contains? (set remove-properties) k)) properties)
|
||||
properties)
|
||||
value (text/re-construct-block-properties value properties)]
|
||||
(cond
|
||||
(not= (string/trim content) (string/trim value)) ; block content changed
|
||||
(let [file (db/entity repo (:db/id file))]
|
||||
@@ -455,10 +473,11 @@
|
||||
(util/format "File %s already exists!" file-path)]
|
||||
:error)
|
||||
;; create the file
|
||||
(let [content (str (util/default-content-with-title format
|
||||
(let [value (block-text-with-time nil format value)
|
||||
content (str (util/default-content-with-title format
|
||||
(or (:page/original-name page)
|
||||
(:page/name page)))
|
||||
(text/remove-level-spaces value (keyword format)))]
|
||||
value)]
|
||||
(p/let [_ (fs/create-if-not-exists repo dir file-path content)
|
||||
_ (git-handler/git-add repo path)]
|
||||
(file-handler/reset-file! repo path content)
|
||||
@@ -607,7 +626,9 @@
|
||||
(let [value (if create-new-block?
|
||||
(str fst-block-text "\n" snd-block-text)
|
||||
value)
|
||||
value (text/re-construct-block-properties block value properties)
|
||||
text-properties (text/extract-properties fst-block-text)
|
||||
properties (with-time-properties block text-properties)
|
||||
value (text/re-construct-block-properties value properties)
|
||||
value (rebuild-block-content value format)
|
||||
[new-content value] (new-file-content block file-content value)
|
||||
parse-result (block/parse-block (assoc block :block/content value) format)
|
||||
@@ -708,11 +729,12 @@
|
||||
(:page/name page)))]
|
||||
(p/let [_ (fs/create-if-not-exists repo dir file-path content)
|
||||
_ (git-handler/git-add repo path)]
|
||||
(file-handler/reset-file! repo path
|
||||
(str content
|
||||
(text/remove-level-spaces value (keyword format))
|
||||
"\n"
|
||||
snd-block-text))
|
||||
(let [value (block-text-with-time nil format value)]
|
||||
(file-handler/reset-file! repo path
|
||||
(str content
|
||||
value
|
||||
"\n"
|
||||
snd-block-text)))
|
||||
(ui-handler/re-render-root!)
|
||||
|
||||
;; Continue to edit the last block
|
||||
@@ -761,14 +783,13 @@
|
||||
(defn- with-timetracking-properties
|
||||
[block value]
|
||||
(let [new-marker (first (re-find format/bare-marker-pattern (or value "")))
|
||||
new-marker (if new-marker (string/lower-case (string/trim new-marker)))
|
||||
properties (into {} (:block/properties block))]
|
||||
new-marker (if new-marker (string/lower-case (string/trim new-marker)))]
|
||||
(if (and
|
||||
new-marker
|
||||
(not= new-marker (string/lower-case (or (:block/marker block) "")))
|
||||
(state/enable-timetracking?))
|
||||
(assoc properties new-marker (util/time-ms))
|
||||
properties)))
|
||||
{new-marker (util/time-ms)}
|
||||
{})))
|
||||
|
||||
(defn insert-new-block!
|
||||
[state]
|
||||
@@ -844,13 +865,10 @@
|
||||
|
||||
(defn- with-marker-time
|
||||
[block marker]
|
||||
(let [properties (:block/properties block)
|
||||
properties (into {} properties)]
|
||||
(if (state/enable-timetracking?)
|
||||
(assoc properties
|
||||
(string/lower-case marker)
|
||||
(util/time-ms))
|
||||
properties)))
|
||||
(if (state/enable-timetracking?)
|
||||
(let [marker (string/lower-case marker)]
|
||||
{marker (util/time-ms)})
|
||||
{}))
|
||||
|
||||
(defn check
|
||||
[{:block/keys [uuid marker content meta file dummy? repeated?] :as block}]
|
||||
@@ -1050,8 +1068,7 @@
|
||||
(let [{:block/keys [content properties]} block]
|
||||
(when (get properties key)
|
||||
(save-block-if-changed! block content
|
||||
{:custom-properties (dissoc properties key)
|
||||
:remove-property? true}))))))
|
||||
{:remove-properties [key]}))))))
|
||||
|
||||
(defn set-block-property!
|
||||
[block-id key value]
|
||||
@@ -1067,13 +1084,9 @@
|
||||
nil
|
||||
|
||||
:else
|
||||
(let [properties (:block/properties block)
|
||||
properties' (if (seq properties)
|
||||
(assoc properties key value)
|
||||
{key value})]
|
||||
(save-block-if-changed! block content
|
||||
{:custom-properties properties'
|
||||
:rebuild-content? false}))))))))
|
||||
(save-block-if-changed! block content
|
||||
{:custom-properties {key value}
|
||||
:rebuild-content? false})))))))
|
||||
|
||||
(defn set-block-timestamp!
|
||||
[block-id key value]
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
[frontend.storage :as storage]
|
||||
[promesa.core :as p]
|
||||
[goog.object :as gobj]
|
||||
[frontend.handler.notification :as notification])
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.handler.config :as config-handler])
|
||||
(:import [goog.format EmailAddress]))
|
||||
|
||||
(defn email? [v]
|
||||
@@ -40,13 +41,15 @@
|
||||
(defn set-preferred-format!
|
||||
[format]
|
||||
(when format
|
||||
(config-handler/set-config! :preferred_format format)
|
||||
(state/set-preferred-format! format)
|
||||
(when (:name (:me @state/state))
|
||||
(util/post (str config/api "set_preferred_format")
|
||||
{:preferred_format (name format)}
|
||||
(fn [_result]
|
||||
(notification/show! "Format set successfully!" :success))
|
||||
(fn [_e])))))
|
||||
(when (state/logged?)
|
||||
(util/post (str config/api "set_preferred_format")
|
||||
{:preferred_format (name format)}
|
||||
(fn [_result]
|
||||
(notification/show! "Format set successfully!" :success))
|
||||
(fn [_e]))))))
|
||||
|
||||
(defn set-preferred-workflow!
|
||||
[workflow]
|
||||
@@ -63,9 +66,9 @@
|
||||
[_e]
|
||||
(when (js/confirm "Your local notes will be completely removed after signing out. Continue?")
|
||||
(->
|
||||
(idb/clear-local-storage-and-idb!)
|
||||
(p/catch (fn [e]
|
||||
(idb/clear-local-storage-and-idb!)
|
||||
(p/catch (fn [e]
|
||||
(println "sign out error: ")
|
||||
(js/console.dir e)))
|
||||
(p/finally (fn []
|
||||
(p/finally (fn []
|
||||
(set! (.-href js/window.location) "/logout"))))))
|
||||
|
||||
@@ -177,6 +177,11 @@
|
||||
(not (false? (:feature/enable-timetracking?
|
||||
(get (sub-config) (get-current-repo))))))
|
||||
|
||||
(defn enable-block-time?
|
||||
[]
|
||||
(true? (:feature/enable-block-time?
|
||||
(get (sub-config) (get-current-repo)))))
|
||||
|
||||
;; Enable by default
|
||||
(defn show-brackets?
|
||||
[]
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
|
||||
(def hidden-properties
|
||||
(set/union
|
||||
#{"id" "custom_id" "heading" "background_color"}
|
||||
#{"id" "custom_id" "heading" "background_color"
|
||||
"created_at" "last_modified_at"}
|
||||
config/markers))
|
||||
|
||||
(defn properties-hidden?
|
||||
@@ -112,23 +113,24 @@
|
||||
(when (and start end)
|
||||
(subs text start (+ end 5)))))
|
||||
|
||||
(defn extract-properties
|
||||
[text]
|
||||
(when-let [properties-text (get-properties-text text)]
|
||||
(->> (string/split-lines properties-text)
|
||||
(map (fn [line]
|
||||
(when (= ":" (first line))
|
||||
(let [[k v] (util/split-first ":" (subs line 1))]
|
||||
(when (and k v)
|
||||
(let [k (string/trim (string/lower-case k))
|
||||
v (string/trim (string/lower-case v))]
|
||||
(when-not (contains? #{"properties" "end"} k)
|
||||
[k v])))))))
|
||||
(into {}))))
|
||||
|
||||
(defn re-construct-block-properties
|
||||
[block content properties]
|
||||
(let [content' (-> (remove-level-spaces content (:block/format block))
|
||||
(string/trim)
|
||||
(string/lower-case))
|
||||
properties-text (get-properties-text content)]
|
||||
(if (or
|
||||
(and
|
||||
properties-text
|
||||
(string/starts-with? content' (string/lower-case properties-text)))
|
||||
(and (contains-properties? content)
|
||||
;; not changed
|
||||
(= (seq (:block/properties (db/entity [:block/uuid (:block/uuid block)])))
|
||||
(seq properties))))
|
||||
content
|
||||
(-> (remove-properties! content)
|
||||
(rejoin-properties properties)))))
|
||||
[content properties]
|
||||
(-> (remove-properties! content)
|
||||
(rejoin-properties properties)))
|
||||
|
||||
(defn insert-property
|
||||
[content key value]
|
||||
|
||||
Reference in New Issue
Block a user