mirror of https://github.com/logseq/logseq
Merge branch 'master' into feat/outliner-core
commit
8f05fb66b7
|
@ -0,0 +1,9 @@
|
|||
{:linters {:unresolved-symbol {:exclude [goog.DEBUG]}}
|
||||
:hooks {:analyze-call {rum.core/defc hooks.rum/defc
|
||||
rum.core/defcs hooks.rum/defcs}}
|
||||
:lint-as {promesa.core/let clojure.core/let
|
||||
garden.def/defstyles clojure.core/def
|
||||
garden.def/defkeyframes clojure.core/def
|
||||
rum.core/defcc rum.core/defc
|
||||
clojure.test.check.clojure-test/defspec clojure.core/def
|
||||
clojure.test.check.properties/for-all clojure.core/for}}
|
|
@ -0,0 +1,68 @@
|
|||
(ns hooks.rum
|
||||
(:require [clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn fn-body? [x]
|
||||
(and (seq? x)
|
||||
(vector? (first x))))
|
||||
|
||||
(defn rewrite-body [mixins body defcs?]
|
||||
(if defcs?
|
||||
(let [[binding-vec & body] (:children body)
|
||||
[state-arg & rest-args] (:children binding-vec)
|
||||
;; the original vector without the state argument
|
||||
fn-args (assoc binding-vec :children rest-args)
|
||||
body (api/list-node
|
||||
(list* (api/token-node 'let*)
|
||||
(api/vector-node [state-arg (api/token-node nil)])
|
||||
state-arg
|
||||
(concat mixins body)))
|
||||
body (api/list-node [fn-args body])]
|
||||
body)
|
||||
(let [[binding-vec & body] (:children body)]
|
||||
(api/list-node (cons binding-vec (concat mixins body))))))
|
||||
|
||||
(defn rewrite
|
||||
([node] (rewrite node false))
|
||||
([node defcs?]
|
||||
(let [args (rest (:children node))
|
||||
component-name (first args)
|
||||
?docstring (when (string? (api/sexpr (second args)))
|
||||
(second args))
|
||||
args (if ?docstring
|
||||
(nnext args)
|
||||
(next args))
|
||||
bodies
|
||||
(loop [args* (seq args)
|
||||
mixins []
|
||||
bodies []]
|
||||
(if args*
|
||||
(let [a (first args*)
|
||||
a-sexpr (api/sexpr a)]
|
||||
(cond (vector? a-sexpr) ;; a-sexpr is a binding vec and the rest is the body of the function
|
||||
[(rewrite-body mixins (api/list-node args*) defcs?)]
|
||||
(fn-body? a-sexpr)
|
||||
(recur (next args*)
|
||||
mixins
|
||||
(conj bodies (rewrite-body mixins a defcs?)))
|
||||
;; assume mixin
|
||||
:else (recur (next args*)
|
||||
(conj mixins a)
|
||||
bodies)))
|
||||
bodies))
|
||||
new-node (with-meta
|
||||
(api/list-node
|
||||
(list* (api/token-node 'defn)
|
||||
component-name
|
||||
(if ?docstring
|
||||
(cons ?docstring bodies)
|
||||
bodies)))
|
||||
(meta node))]
|
||||
new-node)))
|
||||
|
||||
(defn defc [{:keys [:node]}]
|
||||
(let [new-node (rewrite node)]
|
||||
{:node new-node}))
|
||||
|
||||
(defn defcs [{:keys [:node]}]
|
||||
(let [new-node (rewrite node true)]
|
||||
{:node new-node}))
|
|
@ -29,5 +29,5 @@ strings.csv
|
|||
|
||||
.calva
|
||||
resources/electron.js
|
||||
.clj-kondo/
|
||||
.clj-kondo/.cache
|
||||
.lsp/
|
||||
|
|
6
deps.edn
6
deps.edn
|
@ -7,12 +7,12 @@
|
|||
;; persistent-sorted-set {:mvn/version "0.1.2"}
|
||||
;; FIXME: doesn't work on my archlinux laptop (tienson)
|
||||
;; The required namespace "datascript.core" is not available, it was required by "frontend/db.cljs".
|
||||
datascript/datascript {:git/url "https://github.com/tiensonqin/datascript",
|
||||
:sha "efde8d389e6703b6f60ca3538f484a579b0d6de0"}
|
||||
datascript/datascript {:git/url "https://github.com/logseq/datascript",
|
||||
:sha "5c1983cdfbdaa4ba6f8410b54853ea3a78a0cd8c"}
|
||||
;; datascript {:mvn/version "1.0.1"}
|
||||
datascript-transit/datascript-transit
|
||||
{:mvn/version "0.3.0"
|
||||
:exclusions [datascript]}
|
||||
:exclusions [datascript/datascript]}
|
||||
borkdude/rewrite-edn {:git/url "https://github.com/borkdude/rewrite-edn"
|
||||
:sha "edd87dc7f045f28d7afcbfc44bc0f0a2683dde62"}
|
||||
funcool/promesa {:mvn/version "4.0.2"}
|
||||
|
|
|
@ -14,7 +14,10 @@ const resourceFilePath = path.join(resourcesPath, '**')
|
|||
|
||||
const css = {
|
||||
watchCSS () {
|
||||
return exec(`yarn css:watch`, {})
|
||||
return cp.spawn(`yarn css:watch`, {
|
||||
shell: true,
|
||||
stdio: 'inherit'
|
||||
})
|
||||
},
|
||||
|
||||
buildCSS (...params) {
|
||||
|
|
|
@ -71,8 +71,9 @@
|
|||
"fuse.js": "^6.4.6",
|
||||
"gulp-cached": "^1.1.1",
|
||||
"ignore": "^5.1.8",
|
||||
"is-svg": "4.2.2",
|
||||
"jszip": "^3.5.0",
|
||||
"mldoc": "0.5.5",
|
||||
"mldoc": "0.5.8",
|
||||
"mousetrap": "^1.6.5",
|
||||
"path": "^0.12.7",
|
||||
"react": "^17.0.1",
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
"node-fetch": "^2.6.1",
|
||||
"open": "^7.3.1",
|
||||
"chokidar": "^3.5.1",
|
||||
"fs-extra": "^9.1.0"
|
||||
"fs-extra": "^9.1.0",
|
||||
"electron-window-state": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^6.0.0-beta.54",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
["fs-extra" :as fs]
|
||||
["path" :as path]
|
||||
["electron" :refer [BrowserWindow app protocol ipcMain dialog] :as electron]
|
||||
["electron-window-state" :as windowStateKeeper]
|
||||
[clojure.core.async :as async]
|
||||
[electron.state :as state]))
|
||||
|
||||
|
@ -22,8 +23,9 @@
|
|||
(defn create-main-window
|
||||
"Creates main app window"
|
||||
[]
|
||||
(let [win-opts {:width 980
|
||||
:height 700
|
||||
(let [win-state (windowStateKeeper (clj->js {:defaultWidth 980 :defaultHeight 700}))
|
||||
win-opts {:width (.-width win-state)
|
||||
:height (.-height win-state)
|
||||
:frame (not mac?)
|
||||
:autoHideMenuBar (not mac?)
|
||||
:titleBarStyle (if mac? "hidden" nil)
|
||||
|
@ -35,6 +37,7 @@
|
|||
:preload (path/join js/__dirname "js/preload.js")}}
|
||||
url MAIN_WINDOW_ENTRY
|
||||
win (BrowserWindow. (clj->js win-opts))]
|
||||
(.manage win-state win)
|
||||
(.loadURL win url)
|
||||
(when dev? (.. win -webContents (openDevTools)))
|
||||
win))
|
||||
|
|
|
@ -147,6 +147,11 @@
|
|||
["Embed Vimeo Video" [[:editor/input "{{vimeo }}" {:last-pattern slash
|
||||
:backward-pos 2}]]]
|
||||
|
||||
(when (state/markdown?)
|
||||
["Underline" [[:editor/input "<ins></ins>"
|
||||
{:last-pattern slash
|
||||
:backward-pos 6}]]])
|
||||
|
||||
["Html Inline " (->inline "html")]
|
||||
|
||||
;; TODO:
|
||||
|
|
|
@ -326,7 +326,7 @@
|
|||
|
||||
(declare page-reference)
|
||||
|
||||
(defn page-cp
|
||||
(rum/defc page-cp
|
||||
[{:keys [html-export? label children contents-page?] :as config} page]
|
||||
(when-let [page-name (:block/name page)]
|
||||
(let [source-page (model/get-alias-source-page (state/get-current-repo)
|
||||
|
@ -572,7 +572,8 @@
|
|||
["Subscript" l]
|
||||
(->elem :sub (map-inline config l))
|
||||
["Tag" s]
|
||||
(if (and s (util/tag-valid? s))
|
||||
(when s
|
||||
(let [s (text/page-ref-un-brackets! s)]
|
||||
[:a.tag {:data-ref s
|
||||
:href (rfe/href :page {:name s})
|
||||
:on-click (fn [e]
|
||||
|
@ -585,9 +586,8 @@
|
|||
:page
|
||||
{:page page})
|
||||
(.preventDefault e))))}
|
||||
(str "#" s)]
|
||||
[:span.warning.mr-1 {:title "Invalid tag, tags only accept alphanumeric characters, \"-\", \"_\", \"@\" and \"%\"."}
|
||||
(str "#" s)])
|
||||
(str "#" s)]))
|
||||
|
||||
["Emphasis" [[kind] data]]
|
||||
(let [elem (case kind
|
||||
"Bold" :b
|
||||
|
@ -736,6 +736,12 @@
|
|||
(-> (safe-read-string s)
|
||||
(security/remove-javascript-links-in-href)))
|
||||
|
||||
["Inline_Html" s]
|
||||
(when (not html-export?)
|
||||
;; TODO: how to remove span and only export the content of `s`?
|
||||
[:span {:dangerouslySetInnerHTML
|
||||
{:__html s}}])
|
||||
|
||||
["Break_Line"]
|
||||
[:br]
|
||||
["Hard_Break_Line"]
|
||||
|
@ -1257,13 +1263,12 @@
|
|||
[:div [:h1 (:page-name config)]]
|
||||
block-cp))))
|
||||
|
||||
(rum/defc properties-cp
|
||||
[config block]
|
||||
(let [properties (apply dissoc (:block/properties block) text/hidden-properties)]
|
||||
(when (seq properties)
|
||||
[:div.blocks-properties.text-sm.opacity-80.my-1.p-2
|
||||
(for [[k v] properties]
|
||||
^{:key (str (:block/uuid block) "-" k)}
|
||||
(rum/defc span-comma
|
||||
[]
|
||||
[:span ", "])
|
||||
|
||||
(rum/defc property-cp
|
||||
[config block k v]
|
||||
[:div.my-1
|
||||
[:b k]
|
||||
[:span.mr-1 ":"]
|
||||
|
@ -1271,12 +1276,23 @@
|
|||
(let [v (->> (remove string/blank? v)
|
||||
(filter string?))
|
||||
vals (for [v-item v]
|
||||
(page-cp config {:block/name v-item}))]
|
||||
(interpose [:span ", "] vals))
|
||||
(page-cp config {:block/name v-item}))
|
||||
elems (interpose (span-comma) vals)]
|
||||
(for [elem elems]
|
||||
(rum/with-key elem (str (random-uuid)))))
|
||||
(let [page-name (string/lower-case (str v))]
|
||||
(if (db/entity [:block/name page-name])
|
||||
(page-cp config {:block/name page-name})
|
||||
(inline-text (:block/format block) (str v)))))])])))
|
||||
(inline-text (:block/format block) (str v)))))])
|
||||
|
||||
(rum/defc properties-cp
|
||||
[config block]
|
||||
(let [properties (apply dissoc (:block/properties block) text/hidden-properties)]
|
||||
(when (seq properties)
|
||||
[:div.blocks-properties.text-sm.opacity-80.my-1.p-2
|
||||
(for [[k v] properties]
|
||||
(rum/with-key (property-cp config block k v)
|
||||
(str (:block/uuid block) "-" k)))])))
|
||||
|
||||
(rum/defcs timestamp-cp < rum/reactive
|
||||
(rum/local false ::show?)
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
[:div.py-1.rounded-md.bg-base-3.shadow-xs
|
||||
(ui/menu-link
|
||||
{:key "cut"
|
||||
:on-click editor-handler/cut-selection-blocks}
|
||||
:on-click #(editor-handler/cut-selection-blocks true)}
|
||||
"Cut")
|
||||
(ui/menu-link
|
||||
{:key "copy"
|
||||
|
@ -254,8 +254,8 @@
|
|||
|
||||
|
||||
(defn- cut-blocks-and-clear-selections!
|
||||
[_]
|
||||
(editor-handler/cut-selection-blocks)
|
||||
[copy?]
|
||||
(editor-handler/cut-selection-blocks copy?)
|
||||
(editor-handler/clear-selection! nil))
|
||||
|
||||
(rum/defc hidden-selection < rum/reactive
|
||||
|
@ -264,11 +264,11 @@
|
|||
(editor-handler/copy-selection-blocks)
|
||||
(editor-handler/clear-selection! nil)))
|
||||
(mixins/keyboard-mixin (util/->system-modifier "ctrl+x")
|
||||
cut-blocks-and-clear-selections!)
|
||||
(fn [] (cut-blocks-and-clear-selections! true)))
|
||||
(mixins/keyboard-mixin "backspace"
|
||||
cut-blocks-and-clear-selections!)
|
||||
(fn [] (cut-blocks-and-clear-selections! false)))
|
||||
(mixins/keyboard-mixin "delete"
|
||||
cut-blocks-and-clear-selections!)
|
||||
(fn [] (cut-blocks-and-clear-selections! false)))
|
||||
[]
|
||||
[:div#selection.hidden])
|
||||
|
||||
|
|
|
@ -28,3 +28,17 @@
|
|||
[:a#download-as-html.hidden]
|
||||
[:a#download-as-zip.hidden]
|
||||
[:a#export-as-markdown.hidden]])))
|
||||
|
||||
|
||||
(rum/defc export-page
|
||||
[]
|
||||
(when-let [current-repo (state/get-current-repo)]
|
||||
(when-let [page (state/get-current-page)]
|
||||
(rum/with-context [[t] i18n/*tongue-context*]
|
||||
[:div.export.w-96
|
||||
[:h1.title "Export"]
|
||||
[:ul.mr-1
|
||||
[:li.mb-4
|
||||
[:a.font-medium {:on-click #(export/export-page-as-markdown! page)}
|
||||
(t :export-markdown)]]]
|
||||
[:a#export-page-as-markdown.hidden]]))))
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
[frontend.components.editor :as editor]
|
||||
[frontend.components.reference :as reference]
|
||||
[frontend.components.svg :as svg]
|
||||
[frontend.components.export :as export]
|
||||
[frontend.extensions.graph-2d :as graph-2d]
|
||||
[frontend.ui :as ui]
|
||||
[frontend.components.content :as content]
|
||||
|
@ -313,6 +314,10 @@
|
|||
{:title (t :page/delete)
|
||||
:options {:on-click #(state/set-modal! (delete-page-dialog page-name))}})
|
||||
|
||||
(when (state/get-current-page)
|
||||
{:title (t :export)
|
||||
:options {:on-click #(state/set-modal! export/export-page)}})
|
||||
|
||||
(when (util/electron?)
|
||||
{:title (t (if public? :page/make-private :page/make-public))
|
||||
:options {:on-click
|
||||
|
|
|
@ -271,6 +271,7 @@
|
|||
@conn)
|
||||
(into {}))))
|
||||
|
||||
|
||||
(defn get-files-full
|
||||
[repo]
|
||||
(when-let [conn (conn/get-files-conn repo)]
|
||||
|
@ -705,7 +706,8 @@
|
|||
|
||||
(defn get-page-file
|
||||
[page-name]
|
||||
(some-> (db-utils/entity [:block/name page-name])
|
||||
(some-> (or (db-utils/entity [:block/name page-name])
|
||||
(db-utils/entity [:block/original-name page-name]))
|
||||
:block/file))
|
||||
|
||||
(defn get-block-file
|
||||
|
|
|
@ -1201,8 +1201,8 @@
|
|||
(common-handler/copy-to-clipboard-without-id-property! content)))))
|
||||
|
||||
(defn cut-selection-blocks
|
||||
[]
|
||||
(copy-selection-blocks)
|
||||
[copy?]
|
||||
(when copy? (copy-selection-blocks))
|
||||
(when-let [blocks (seq (get-selected-blocks-with-children))]
|
||||
(let [repo (dom/attr (first blocks) "repo")
|
||||
ids (distinct (map #(uuid (dom/attr % "blockid")) blocks))]
|
||||
|
|
|
@ -178,11 +178,17 @@
|
|||
(db/pull-many repo '[*] block-ids)
|
||||
pages-name-and-content
|
||||
(->> page-ids
|
||||
(d/q '[:find ?n (pull ?p [:file/path])
|
||||
(d/q '[:find ?n ?n2 (pull ?p [:file/path])
|
||||
:in $ [?e ...]
|
||||
:where
|
||||
[?e :block/file ?p]
|
||||
[?e :block/name ?n]] (db/get-conn repo))
|
||||
[?e :block/name ?n]
|
||||
[?e :block/original-name ?n2]] (db/get-conn repo))
|
||||
(mapv (fn [[name origin-name file-path]]
|
||||
(if (= name origin-name)
|
||||
[[name file-path]]
|
||||
[[name file-path] [origin-name file-path]])))
|
||||
(apply concat)
|
||||
(mapv (fn [[page-name file-path]] [page-name (:file/path file-path)]))
|
||||
(d/q '[:find ?n ?c
|
||||
:in $ [[?n ?p] ...]
|
||||
|
@ -199,12 +205,8 @@
|
|||
{:embed_blocks embed-blocks
|
||||
:embed_pages pages-name-and-content}))
|
||||
|
||||
(defn export-repo-as-markdown!
|
||||
[repo]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(when-let [files (get-file-contents-with-suffix repo)]
|
||||
(let [heading-to-list? (state/export-heading-to-list?)
|
||||
files
|
||||
(defn- export-files-as-markdown
|
||||
[repo files heading-to-list?]
|
||||
(->> files
|
||||
(mapv (fn [{:keys [path content names format]}]
|
||||
(when (first names)
|
||||
|
@ -212,10 +214,37 @@
|
|||
(f/get-default-config format heading-to-list?)
|
||||
(js/JSON.stringify
|
||||
(clj->js (get-embed-and-refs-blocks-pages repo (first names)))))])))
|
||||
(remove nil?))
|
||||
(remove nil?)))
|
||||
|
||||
(defn export-repo-as-markdown!
|
||||
[repo]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(when-let [files (get-file-contents-with-suffix repo)]
|
||||
(let [heading-to-list? (state/export-heading-to-list?)
|
||||
files
|
||||
(export-files-as-markdown repo files heading-to-list?)
|
||||
zip-file-name (str repo "_markdown_" (quot (util/time-ms) 1000))]
|
||||
(p/let [zipfile (zip/make-zip zip-file-name files)]
|
||||
(when-let [anchor (gdom/getElement "export-as-markdown")]
|
||||
(.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
|
||||
(.setAttribute anchor "download" (.-name zipfile))
|
||||
(.click anchor)))))))
|
||||
|
||||
(defn export-page-as-markdown!
|
||||
[page-name]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(when-let [file (db/get-page-file page-name)]
|
||||
(when-let [path (:file/path file)]
|
||||
(when-let [content (db/get-file path)]
|
||||
(let [names [page-name]
|
||||
format (f/get-format path)
|
||||
files [{:path path :content content :names names :format format}]]
|
||||
(let [files
|
||||
(export-files-as-markdown repo files (state/export-heading-to-list?))]
|
||||
(let [data (js/Blob. [(second (first files))]
|
||||
(clj->js {:type "text/plain;charset=utf-8,"}))]
|
||||
(let [anchor (gdom/getElement "export-page-as-markdown")
|
||||
url (js/window.URL.createObjectURL data)]
|
||||
(.setAttribute anchor "href" url)
|
||||
(.setAttribute anchor "download" path)
|
||||
(.click anchor))))))))))
|
||||
|
|
|
@ -250,6 +250,11 @@
|
|||
|
||||
(get-in @state [:me :preferred_format] "markdown")))))
|
||||
|
||||
(defn markdown?
|
||||
[]
|
||||
(= (keyword (get-preferred-format))
|
||||
:markdown))
|
||||
|
||||
(defn get-pages-directory
|
||||
[]
|
||||
(or
|
||||
|
|
17
yarn.lock
17
yarn.lock
|
@ -2794,7 +2794,7 @@ hsla-regex@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38"
|
||||
integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg=
|
||||
|
||||
html-comment-regex@^1.1.0:
|
||||
html-comment-regex@^1.1.0, html-comment-regex@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7"
|
||||
integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==
|
||||
|
@ -3221,6 +3221,13 @@ is-stream@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
||||
|
||||
is-svg@4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-4.2.2.tgz#a4ea0f3f78dada7085db88f1e85b6f845626cfae"
|
||||
integrity sha512-JlA7Mc7mfWjdxxTkJ094oUK9amGD7gQaj5xA/NCY0vlVvZ1stmj4VX+bRuwOMN93IHRZ2ctpPH/0FO6DqvQ5Rw==
|
||||
dependencies:
|
||||
html-comment-regex "^1.1.2"
|
||||
|
||||
is-svg@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75"
|
||||
|
@ -3846,10 +3853,10 @@ mkdirp@^0.5.4, mkdirp@~0.5.1:
|
|||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
mldoc@0.5.5:
|
||||
version "0.5.5"
|
||||
resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-0.5.5.tgz#cb7eea471adc94e1c7858d4ae772ddabe0a75753"
|
||||
integrity sha512-acseZvvwzLNlvp6/fZzqP5rqS80keWOK1XCreem5eXJNxLtJm+xIqzEJVcsmeyFS1Leya1gPT7dMcCUOhEr26g==
|
||||
mldoc@0.5.6:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-0.5.6.tgz#aa4351791e11b9c8a8df359a9d87619d7ff02e5d"
|
||||
integrity sha512-iRTuTmLUdR8OKiRrrM4dl+51jxAmOJ92+B3rsYuKZERaYcU0B9jiJBT9S2nDZldARNEwjhj8DKITdnJZMxArGQ==
|
||||
dependencies:
|
||||
yargs "^12.0.2"
|
||||
|
||||
|
|
Loading…
Reference in New Issue