feat: support org mode file links (#681)

* feat: support org-mode file links

Add a `:org-mode/insert-file-links` option to configuration.
pull/687/head
Tienson Qin 2020-11-18 07:27:40 -06:00 committed by GitHub
parent 85a1837004
commit 5d3b6322f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 180 additions and 70 deletions

View File

@ -6,7 +6,10 @@
[clojure.string :as string] [clojure.string :as string]
[goog.dom :as gdom] [goog.dom :as gdom]
[goog.object :as gobj] [goog.object :as gobj]
[frontend.format :as format])) [frontend.format :as format]
[frontend.handler.common :as common-handler]))
;; TODO: move to frontend.handler.editor.commands
(defonce *show-commands (atom false)) (defonce *show-commands (atom false))
(defonce *slash-caret-pos (atom nil)) (defonce *slash-caret-pos (atom nil))
@ -16,10 +19,6 @@
(defonce *angle-bracket-caret-pos (atom nil)) (defonce *angle-bracket-caret-pos (atom nil))
(defonce *current-command (atom nil)) (defonce *current-command (atom nil))
(defn ->page-reference
[page]
(util/format "[[%s]]" page))
(def link-steps [[:editor/input (str slash "link")] (def link-steps [[:editor/input (str slash "link")]
[:editor/show-input [{:command :link [:editor/show-input [{:command :link
:id :link :id :link
@ -87,7 +86,7 @@
;; Credits to roamresearch.com ;; Credits to roamresearch.com
(defn commands-map (defn commands-map
[] [get-page-ref-text]
(->> (->>
(concat (concat
(get-preferred-workflow) (get-preferred-workflow)
@ -107,9 +106,9 @@
:placeholder "Draw title"}]]]] :placeholder "Draw title"}]]]]
["WAITING" (->marker "WAITING")] ["WAITING" (->marker "WAITING")]
["CANCELED" (->marker "CANCELED")] ["CANCELED" (->marker "CANCELED")]
["Tomorrow" (->page-reference (date/tomorrow))] ["Tomorrow" #(get-page-ref-text (date/tomorrow))]
["Yesterday" (->page-reference (date/yesterday))] ["Yesterday" #(get-page-ref-text (date/yesterday))]
["Today" (->page-reference (date/today))] ["Today" #(get-page-ref-text (date/today))]
["Current Time" (date/get-current-time)] ["Current Time" (date/get-current-time)]
["Date Picker" [[:editor/show-date-picker]]] ["Date Picker" [[:editor/show-date-picker]]]
["Page Reference" [[:editor/input "[[]]" {:backward-pos 2}] ["Page Reference" [[:editor/input "[[]]" {:backward-pos 2}]
@ -137,7 +136,14 @@
(remove nil?) (remove nil?)
(util/distinct-by-last-wins first))) (util/distinct-by-last-wins first)))
(defonce *matched-commands (atom (commands-map))) (defonce *matched-commands (atom nil))
(defonce *initial-commands (atom nil))
(defn init-commands!
[get-page-ref-text]
(let [commands (commands-map get-page-ref-text)]
(reset! *initial-commands commands)
(reset! *matched-commands commands)))
(defn ->block (defn ->block
([type] ([type]
@ -199,7 +205,7 @@
(when restore-slash-caret-pos? (when restore-slash-caret-pos?
(reset! *slash-caret-pos nil)) (reset! *slash-caret-pos nil))
(reset! *show-commands false) (reset! *show-commands false)
(reset! *matched-commands (commands-map)) (reset! *matched-commands @*initial-commands)
(reset! *angle-bracket-caret-pos nil) (reset! *angle-bracket-caret-pos nil)
(reset! *show-block-commands false) (reset! *show-block-commands false)
(reset! *matched-block-commands (block-commands-map))) (reset! *matched-block-commands (block-commands-map)))
@ -288,7 +294,7 @@
(defn get-matched-commands (defn get-matched-commands
([text] ([text]
(get-matched-commands text (commands-map))) (get-matched-commands text @*initial-commands))
([text commands] ([text commands]
(search/fuzzy-search commands text (search/fuzzy-search commands text
:extract-fn first :extract-fn first

View File

@ -46,6 +46,7 @@
(reset! commands/*current-command chosen) (reset! commands/*current-command chosen)
(let [command-steps (get (into {} matched) chosen) (let [command-steps (get (into {} matched) chosen)
restore-slash? (and restore-slash? (and
(not (fn? command-steps))
(not (contains? (set (map first command-steps)) :editor/input)) (not (contains? (set (map first command-steps)) :editor/input))
(not (contains? #{"Date Picker" "Template" "Deadline" "Scheduled"} chosen)))] (not (contains? #{"Date Picker" "Template" "Deadline" "Scheduled"} chosen)))]
(editor-handler/insert-command! id command-steps (editor-handler/insert-command! id command-steps
@ -75,6 +76,7 @@
(when input (when input
(let [current-pos (:pos (util/get-caret-pos input)) (let [current-pos (:pos (util/get-caret-pos input))
edit-content (state/sub [:editor/content id]) edit-content (state/sub [:editor/content id])
edit-block (state/sub :editor/block)
q (or q (or
@editor-handler/*selected-text @editor-handler/*selected-text
(when (state/sub :editor/show-page-search-hashtag?) (when (state/sub :editor/show-page-search-hashtag?)
@ -86,22 +88,35 @@
chosen-handler (if (state/sub :editor/show-page-search-hashtag?) chosen-handler (if (state/sub :editor/show-page-search-hashtag?)
(fn [chosen _click?] (fn [chosen _click?]
(state/set-editor-show-page-search! false) (state/set-editor-show-page-search! false)
(editor-handler/insert-command! id (let [page-ref-text (page-handler/get-page-ref-text chosen)]
(util/format "#%s" (if (string/includes? chosen " ") (editor-handler/insert-command! id
(str "[[" chosen "]]") (util/format "#%s" page-ref-text)
chosen)) format
format {:last-pattern (str "#" (if @editor-handler/*selected-text "" q))})))
{:last-pattern (str "#" (if @editor-handler/*selected-text "" q))}))
(fn [chosen _click?] (fn [chosen _click?]
(state/set-editor-show-page-search! false) (state/set-editor-show-page-search! false)
(editor-handler/insert-command! id (let [page-ref-text (page-handler/get-page-ref-text chosen)]
(util/format "[[%s]]" chosen) (editor-handler/insert-command! id
format page-ref-text
{:last-pattern (str "[[" (if @editor-handler/*selected-text "" q)) format
:postfix-fn (fn [s] (util/replace-first "]]" s ""))}))) {:last-pattern (str "[[" (if @editor-handler/*selected-text "" q))
:postfix-fn (fn [s] (util/replace-first "]]" s ""))}))))
non-exist-page-handler (fn [_state] non-exist-page-handler (fn [_state]
(state/set-editor-show-page-search! false) (state/set-editor-show-page-search! false)
(util/cursor-move-forward input 2))] (if (state/org-mode-file-link? (state/get-current-repo))
(let [page-ref-text (page-handler/get-page-ref-text q)
value (gobj/get input "value")
old-page-ref (util/format "[[%s]]" q)
new-value (string/replace value
old-page-ref
page-ref-text)]
(state/set-edit-content! id new-value)
(let [new-pos (+ current-pos
(- (count page-ref-text)
(count old-page-ref))
2)]
(util/move-cursor-to input new-pos)))
(util/cursor-move-forward input 2)))]
(ui/auto-complete (ui/auto-complete
matched-pages matched-pages
{:on-chosen chosen-handler {:on-chosen chosen-handler

View File

@ -27,6 +27,7 @@
(let [page (cond (let [page (cond
(and (vector? block) (= "Link" (first block))) (and (vector? block) (= "Link" (first block)))
(let [typ (first (:url (second block)))] (let [typ (first (:url (second block)))]
;; {:url ["File" "file:../pages/hello_world.org"], :label [["Plain" "hello world"]], :title nil}
(or (or
(and (and
(= typ "Search") (= typ "Search")
@ -40,7 +41,11 @@
(and (and
(= typ "Complex") (= typ "Complex")
(= (:protocol (second (:url (second block)))) "file") (= (:protocol (second (:url (second block)))) "file")
(:link (second (:url (second block))))))) (:link (second (:url (second block)))))
(and
(= typ "File")
(second (first (:label (second block)))))))
(and (vector? block) (= "Nested_link" (first block))) (and (vector? block) (= "Nested_link" (first block)))
(let [content (:content (last block))] (let [content (:content (last block))]

View File

@ -8,6 +8,7 @@
[cljs-bean.core :as bean] [cljs-bean.core :as bean]
[frontend.date :as date] [frontend.date :as date]
[frontend.handler.notification :as notification] [frontend.handler.notification :as notification]
[frontend.handler.page :as page-handler]
[frontend.handler.repo :as repo-handler] [frontend.handler.repo :as repo-handler]
[frontend.handler.file :as file-handler] [frontend.handler.file :as file-handler]
[frontend.handler.ui :as ui-handler] [frontend.handler.ui :as ui-handler]
@ -130,6 +131,8 @@
(notification/show! "Sorry, it seems that your browser doesn't support IndexedDB, we recommend to use latest Chrome(Chromium) or Firefox(Non-private mode)." :error false) (notification/show! "Sorry, it seems that your browser doesn't support IndexedDB, we recommend to use latest Chrome(Chromium) or Firefox(Non-private mode)." :error false)
(state/set-indexedb-support! false))) (state/set-indexedb-support! false)))
(page-handler/init-commands!)
(restore-and-setup! me repos logged?) (restore-and-setup! me repos logged?)
(periodically-persist-repo-to-indexeddb!) (periodically-persist-repo-to-indexeddb!)

View File

@ -30,10 +30,8 @@
[frontend.handler.image :as image-handler] [frontend.handler.image :as image-handler]
[frontend.commands :as commands [frontend.commands :as commands
:refer [*show-commands :refer [*show-commands
*matched-commands
*slash-caret-pos *slash-caret-pos
*angle-bracket-caret-pos *angle-bracket-caret-pos
*matched-block-commands
*show-block-commands]] *show-block-commands]]
[frontend.extensions.html-parser :as html-parser] [frontend.extensions.html-parser :as html-parser]
[medley.core :as medley] [medley.core :as medley]
@ -1363,6 +1361,10 @@
:or {restore? true} :or {restore? true}
:as option}] :as option}]
(cond (cond
(fn? command-output)
(let [s (command-output)]
(commands/insert! id s option))
;; replace string ;; replace string
(string? command-output) (string? command-output)
(commands/insert! id command-output option) (commands/insert! id command-output option)
@ -1527,7 +1529,7 @@
(when (> pos 0) (when (> pos 0)
(or (or
(and (= \/ (util/nth-safe edit-content (dec pos))) (and (= \/ (util/nth-safe edit-content (dec pos)))
(commands/commands-map)) @commands/*initial-commands)
(and last-command (and last-command
(commands/get-matched-commands last-command))))) (commands/get-matched-commands last-command)))))
(catch js/Error e (catch js/Error e

View File

@ -15,6 +15,7 @@
[frontend.handler.project :as project-handler] [frontend.handler.project :as project-handler]
[frontend.handler.notification :as notification] [frontend.handler.notification :as notification]
[frontend.handler.ui :as ui-handler] [frontend.handler.ui :as ui-handler]
[frontend.commands :as commands]
[frontend.date :as date] [frontend.date :as date]
[clojure.walk :as walk] [clojure.walk :as walk]
[frontend.git :as git] [frontend.git :as git]
@ -23,45 +24,57 @@
[goog.object :as gobj] [goog.object :as gobj]
[frontend.format.mldoc :as mldoc])) [frontend.format.mldoc :as mldoc]))
(defn- get-directory
[journal?]
(if journal?
config/default-journals-directory
(config/get-pages-directory)))
(defn- get-file-name
[journal? title]
(if journal?
(date/journal-title->default title)
(util/page-name-sanity (string/lower-case title))))
(defn create! (defn create!
[title] ([title]
(let [repo (state/get-current-repo) (create! title {}))
dir (util/get-repo-dir repo) ([title {:keys [redirect?]
journal-page? (date/valid-journal-title? title) :or {redirect? true}}]
directory (if journal-page? (let [repo (state/get-current-repo)
config/default-journals-directory dir (util/get-repo-dir repo)
(config/get-pages-directory))] journal-page? (date/valid-journal-title? title)
(when dir directory (get-directory journal-page?)]
(p/let [_ (-> (fs/mkdir (str dir "/" directory)) (when dir
(p/catch (fn [_e])))] (p/let [_ (-> (fs/mkdir (str dir "/" directory))
(let [format (name (state/get-preferred-format)) (p/catch (fn [_e])))]
page (string/lower-case title) (let [format (name (state/get-preferred-format))
path (str (if journal-page? page (string/lower-case title)
(date/journal-title->default title) path (str (get-file-name journal-page? title)
(util/page-name-sanity page)) "."
"." (if (= format "markdown") "md" format))
(if (= format "markdown") "md" format)) path (str directory "/" path)
path (str directory "/" path) file-path (str "/" path)]
file-path (str "/" path)] (p/let [exists? (fs/file-exists? dir file-path)]
(p/let [exists? (fs/file-exists? dir file-path)] (if exists?
(if exists? (notification/show!
(notification/show! [:p.content
[:p.content (util/format "File %s already exists!" file-path)]
(util/format "File %s already exists!" file-path)] :error)
:error) ;; create the file
;; create the file (let [content (util/default-content-with-title format title)]
(let [content (util/default-content-with-title format title)] (p/let [_ (fs/create-if-not-exists dir file-path content)]
(p/let [_ (fs/create-if-not-exists dir file-path content)] (db/reset-file! repo path content)
(db/reset-file! repo path content) (git-handler/git-add repo path)
(git-handler/git-add repo path) (when redirect?
(route-handler/redirect! {:to :page (route-handler/redirect! {:to :page
:path-params {:name page}}) :path-params {:name page}})
(let [blocks (db/get-page-blocks page) (let [blocks (db/get-page-blocks page)
last-block (last blocks)] last-block (last blocks)]
(when last-block (when last-block
(js/setTimeout (js/setTimeout
#(editor-handler/edit-last-block-for-new-page! last-block 0) #(editor-handler/edit-last-block-for-new-page! last-block 0)
100)))))))))))) 100))))))))))))))
(defn page-add-properties! (defn page-add-properties!
[page-name properties] [page-name properties]
@ -381,3 +394,31 @@
(defn update-public-attribute! (defn update-public-attribute!
[page-name value] [page-name value]
(page-add-properties! page-name {:public value})) (page-add-properties! page-name {:public value}))
(defn get-page-ref-text
[page]
(when-let [edit-block (state/get-edit-block)]
(let [page-name (string/lower-case page)
edit-block-file-path (-> (:db/id (:block/file edit-block))
(db/entity)
:file/path)]
(if (and edit-block-file-path
(state/org-mode-file-link? (state/get-current-repo)))
(if-let [ref-file-path (:file/path (:page/file (db/entity [:page/name page-name])))]
(util/format "[[file:%s][%s]]"
(util/get-relative-path edit-block-file-path ref-file-path)
page)
(let [journal? (date/valid-journal-title? page)
ref-file-path (str (get-directory journal?)
"/"
(get-file-name journal? page)
".org")]
(create! page {:redirect? false})
(util/format "[[file:%s][%s]]"
(util/get-relative-path edit-block-file-path ref-file-path)
page)))
(util/format "[[%s]]" page)))))
(defn init-commands!
[]
(commands/init-commands! get-page-ref-text))

View File

@ -186,6 +186,10 @@
(when-let [repo (get-current-repo)] (when-let [repo (get-current-repo)]
(:pages-directory (get-config repo)))) (:pages-directory (get-config repo))))
(defn org-mode-file-link?
[repo]
(:org-mode/insert-file-link? (get-config repo)))
(defn get-preferred-workflow (defn get-preferred-workflow
[] []
(keyword (keyword

View File

@ -856,10 +856,6 @@
(fn [] (fn []
(js/window.indexedDB.deleteDatabase test-db)))))) (js/window.indexedDB.deleteDatabase test-db))))))
(defn get-file-ext
[file]
(last (string/split file #"\.")))
(defonce mac? goog.userAgent/MAC) (defonce mac? goog.userAgent/MAC)
(defn ->system-modifier (defn ->system-modifier
@ -936,3 +932,41 @@
(string/replace "Ctrl" "Cmd") (string/replace "Ctrl" "Cmd")
(string/replace "Alt" "Opt")) (string/replace "Alt" "Opt"))
keyboard-shortcut)) keyboard-shortcut))
(defn remove-common-preceding
[col1 col2]
(if (and (= (first col1) (first col2))
(seq col1))
(recur (rest col1) (rest col2))
[col1 col2]))
;; fs
(defn get-file-ext
[file]
(last (string/split file #"\.")))
(defn get-relative-path
[current-file-path another-file-path]
(let [directories-f #(butlast (string/split % "/"))
parts-1 (directories-f current-file-path)
parts-2 (directories-f another-file-path)
[parts-1 parts-2] (remove-common-preceding parts-1 parts-2)
another-file-name (last (string/split another-file-path "/"))]
(->> (concat
(if (seq parts-1)
(repeat (count parts-1) "..")
["."])
parts-2
[another-file-name])
(string/join "/"))))
(comment
(= (get-relative-path "journals/2020_11_18.org" "pages/grant_ideas.org")
"../pages/grant_ideas.org")
(= (get-relative-path "journals/2020_11_18.org" "journals/2020_11_19.org")
"./2020_11_19.org")
(= (get-relative-path "a/b/c/d/g.org" "a/b/c/e/f.org")
"../e/f.org")
)