Initial import of LogDarioSeq baseline
Establishes version control for the personal Logseq DB fork before starting the server-client architecture migration and domain features described in docs/agent-guide and the approved implementation plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
16
deps/common/.carve/config.edn
vendored
Normal file
16
deps/common/.carve/config.edn
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{:paths ["src"]
|
||||
:api-namespaces [logseq.common.path
|
||||
logseq.common.uuid
|
||||
logseq.common.util.namespace
|
||||
logseq.common.util.page-ref
|
||||
logseq.common.util.block-ref
|
||||
logseq.common.util
|
||||
logseq.common.util.date-time
|
||||
logseq.common.date
|
||||
logseq.common.util.macro
|
||||
logseq.common.cognito-config
|
||||
logseq.common.config
|
||||
logseq.common.defkeywords
|
||||
logseq.common.graph-dir
|
||||
logseq.common.graph-registry]
|
||||
:report {:format :ignore}}
|
||||
27
deps/common/.carve/ignore
vendored
Normal file
27
deps/common/.carve/ignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
;; API fn
|
||||
logseq.common.graph/get-files
|
||||
;; API fn
|
||||
logseq.common.graph/read-directories
|
||||
;; API fn
|
||||
logseq.common.graph/get-default-graphs-dir
|
||||
;; API fn
|
||||
logseq.common.graph/expand-home
|
||||
;; API fn
|
||||
logseq.common.graph/get-db-graphs-dir
|
||||
;; API fn
|
||||
logseq.common.graph/get-db-based-graphs
|
||||
;; API fn
|
||||
logseq.common.authorization/verify-jwt
|
||||
|
||||
;; Profile utils
|
||||
logseq.common.profile/profile-fn!
|
||||
logseq.common.profile/*key->call-count
|
||||
logseq.common.profile/*key->time-sum
|
||||
|
||||
;; API fn
|
||||
logseq.common.plural/is-plural?
|
||||
logseq.common.plural/is-singular?
|
||||
logseq.common.plural/pluralize
|
||||
|
||||
;; API fn
|
||||
logseq.common.version/format-version
|
||||
11
deps/common/.clj-kondo/config.edn
vendored
Normal file
11
deps/common/.clj-kondo/config.edn
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{:linters
|
||||
{:aliased-namespace-symbol {:level :warning}
|
||||
:namespace-name-mismatch {:level :warning}
|
||||
:used-underscored-binding {:level :warning}
|
||||
:shadowed-var {:level :warning}
|
||||
|
||||
:consistent-alias
|
||||
{:aliases {clojure.string string}}}
|
||||
:lint-as {promesa.core/let clojure.core/let}
|
||||
:skip-comments true
|
||||
:output {:progress true}}
|
||||
1
deps/common/.gitignore
vendored
Normal file
1
deps/common/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.clj-kondo/.cache
|
||||
52
deps/common/README.md
vendored
Normal file
52
deps/common/README.md
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
## Description
|
||||
|
||||
This library provides common util namespaces and resources to share between the
|
||||
frontend and other non-frontend contexts. This library is not supposed to depend
|
||||
on other logseq libraries. This library is compatible with ClojureScript and
|
||||
with node/[nbb-logseq](https://github.com/logseq/nbb-logseq) to respectively
|
||||
provide frontend and Electron/commandline functionality.
|
||||
|
||||
|
||||
## API
|
||||
|
||||
This library is under the parent namespace `logseq.common`.
|
||||
|
||||
## Dev
|
||||
|
||||
This follows the practices that [the Logseq frontend
|
||||
follows](/docs/dev-practices.md). Most of the same linters are used, with
|
||||
configurations that are specific to this library. See [this library's CI
|
||||
file](/.github/workflows/deps-common.yml) for linting examples.
|
||||
|
||||
### Setup
|
||||
|
||||
To run linters and tests, you'll want to install pnpm dependencies once:
|
||||
```
|
||||
pnpm install
|
||||
```
|
||||
### Testing
|
||||
|
||||
To run nbb-logseq tests:
|
||||
|
||||
```
|
||||
# Run all tests
|
||||
$ pnpm test
|
||||
# List available options
|
||||
$ pnpm test -H
|
||||
# Run tests with :focus metadata flag
|
||||
$ pnpm test -i focus
|
||||
```
|
||||
|
||||
To run ClojureScript tests:
|
||||
```
|
||||
clojure -M:test
|
||||
```
|
||||
|
||||
To auto-run ClojureScript tests while writing tests:
|
||||
|
||||
```
|
||||
clojure -M:test -w src
|
||||
```
|
||||
### Managing dependencies
|
||||
|
||||
See [standard nbb/cljs library advice in graph-parser](/deps/graph-parser/README.md#managing-dependencies).
|
||||
27
deps/common/bb.edn
vendored
Normal file
27
deps/common/bb.edn
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{:min-bb-version "1.0.168"
|
||||
:deps
|
||||
{logseq/bb-tasks
|
||||
#_{:local/root "../../../bb-tasks"}
|
||||
{:git/url "https://github.com/logseq/bb-tasks"
|
||||
:git/sha "7cc218286c03f093996539b8195de56a0ec5df6a"}}
|
||||
|
||||
:pods
|
||||
{clj-kondo/clj-kondo {:version "2026.04.15"}}
|
||||
|
||||
:tasks
|
||||
{test:load-all-namespaces-with-nbb
|
||||
logseq.bb-tasks.nbb.test/load-all-namespaces
|
||||
|
||||
lint:large-vars
|
||||
logseq.bb-tasks.lint.large-vars/-main
|
||||
|
||||
lint:carve
|
||||
logseq.bb-tasks.lint.carve/-main
|
||||
|
||||
lint:ns-docstrings
|
||||
logseq.bb-tasks.lint.ns-docstrings/-main}
|
||||
|
||||
:tasks/config
|
||||
{:large-vars
|
||||
{:metadata-exceptions #{:large-vars/cleanup-todo}
|
||||
:max-lines-count 45}}}
|
||||
12
deps/common/deps.edn
vendored
Normal file
12
deps/common/deps.edn
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{:paths ["src" "resources"]
|
||||
:deps {com.andrewmcveigh/cljs-time {:git/url "https://github.com/logseq/cljs-time" ;; fork
|
||||
:sha "5704fbf48d3478eedcf24d458c8964b3c2fd59a9"}
|
||||
funcool/promesa {:mvn/version "11.0.678"}}
|
||||
:aliases
|
||||
{:test {:extra-paths ["test"]
|
||||
:extra-deps {olical/cljs-test-runner {:mvn/version "3.8.1"}
|
||||
org.clojure/clojurescript {:mvn/version "1.12.134"}}
|
||||
:main-opts ["-m" "cljs-test-runner.main"]}
|
||||
:clj-kondo
|
||||
{:replace-deps {clj-kondo/clj-kondo {:mvn/version "2026.04.15"}}
|
||||
:main-opts ["-m" "clj-kondo.main"]}}}
|
||||
4
deps/common/nbb.edn
vendored
Normal file
4
deps/common/nbb.edn
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{:paths ["src" "resources"]
|
||||
:deps
|
||||
{io.github.nextjournal/nbb-test-runner
|
||||
{:git/sha "b379325cfa5a3306180649da5de3bf5166414e71"}}}
|
||||
12
deps/common/package.json
vendored
Normal file
12
deps/common/package.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@logseq/common",
|
||||
"version": "1.0.0",
|
||||
"packageManager": "pnpm@10.33.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@logseq/nbb-logseq": "git+https://github.com/logseq/nbb-logseq.git#feat-db-v34"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "pnpm exec nbb-logseq -cp test -m nextjournal.test-runner"
|
||||
}
|
||||
}
|
||||
32
deps/common/pnpm-lock.yaml
generated
vendored
Normal file
32
deps/common/pnpm-lock.yaml
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
'@logseq/nbb-logseq':
|
||||
specifier: git+https://github.com/logseq/nbb-logseq.git#feat-db-v34
|
||||
version: https://codeload.github.com/logseq/nbb-logseq/tar.gz/4d9f1382ce2cec6f39bf12df49ba425f1bd5f3fb
|
||||
|
||||
packages:
|
||||
|
||||
'@logseq/nbb-logseq@https://codeload.github.com/logseq/nbb-logseq/tar.gz/4d9f1382ce2cec6f39bf12df49ba425f1bd5f3fb':
|
||||
resolution: {tarball: https://codeload.github.com/logseq/nbb-logseq/tar.gz/4d9f1382ce2cec6f39bf12df49ba425f1bd5f3fb}
|
||||
version: 1.2.173-feat-db-v34
|
||||
engines: {node: '>=20.10.0'}
|
||||
hasBin: true
|
||||
|
||||
import-meta-resolve@4.1.0:
|
||||
resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@logseq/nbb-logseq@https://codeload.github.com/logseq/nbb-logseq/tar.gz/4d9f1382ce2cec6f39bf12df49ba425f1bd5f3fb':
|
||||
dependencies:
|
||||
import-meta-resolve: 4.1.0
|
||||
|
||||
import-meta-resolve@4.1.0: {}
|
||||
237
deps/common/resources/templates/config.edn
vendored
Normal file
237
deps/common/resources/templates/config.edn
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
{:meta/version 1
|
||||
|
||||
;; Hide empty block properties
|
||||
;; Default value: false
|
||||
;; :ui/hide-empty-properties? false
|
||||
|
||||
;; Enable tooltip preview on hover.
|
||||
;; Default value: true
|
||||
:ui/enable-tooltip? true
|
||||
|
||||
;; Display brackets [[]] around page references.
|
||||
;; Default value: true
|
||||
;; :ui/show-brackets? true
|
||||
|
||||
;; Display all lines of a block when referencing ((block)).
|
||||
;; Default value: false
|
||||
:ui/show-full-blocks? false
|
||||
|
||||
;; Automatically expand block references when zooming in.
|
||||
;; Default value: true
|
||||
:ui/auto-expand-block-refs? true
|
||||
|
||||
;; Disable accent marks when searching.
|
||||
;; After changing this setting, rebuild the search index by pressing (^C ^S).
|
||||
;; Default value: true
|
||||
:feature/enable-search-remove-accents? true
|
||||
|
||||
;; Enable flashcards.
|
||||
;; Default value: true
|
||||
;; :feature/enable-flashcards? true
|
||||
|
||||
;; Disable the journal's built-in 'Scheduled tasks and deadlines' query.
|
||||
;; Default value: false
|
||||
;; :feature/disable-scheduled-and-deadline-query? false
|
||||
|
||||
;; Specify the number of days displayed in the future for
|
||||
;; the 'scheduled tasks and deadlines' query.
|
||||
;; Example usage:
|
||||
;; Display all scheduled and deadline blocks for the next 14 days:
|
||||
;; :scheduled/future-days 14
|
||||
;; Default value: 7
|
||||
;; :scheduled/future-days 7
|
||||
|
||||
;; Specify the first day of the week.
|
||||
;; Available options:
|
||||
;; - integer from 0 to 6 (Monday to Sunday)
|
||||
;; Default value: 6 (Sunday)
|
||||
:start-of-week 6
|
||||
|
||||
;; Specify a custom CSS import.
|
||||
;; This option takes precedence over the local `logseq/custom.css` file.
|
||||
;; Example usage:
|
||||
;; :custom-css-url "@import url('https://cdn.jsdelivr.net/gh/dracula/logseq@master/custom.css');"
|
||||
|
||||
;; Specify a custom JS import.
|
||||
;; This option takes precedence over the local `logseq/custom.js` file.
|
||||
;; Example usage:
|
||||
;; :custom-js-url "https://cdn.logseq.com/custom.js"
|
||||
|
||||
;; Set bullet indentation when exporting
|
||||
;; Available options:
|
||||
;; - `:eight-spaces` as eight spaces
|
||||
;; - `:four-spaces` as four spaces
|
||||
;; - `:two-spaces` as two spaces
|
||||
;; - `:tab` as a tab character (default)
|
||||
;; :export/bullet-indentation :tab
|
||||
|
||||
;; Publish all pages within the Graph
|
||||
;; Regardless of whether individual pages have been marked as public.
|
||||
;; Default value: false
|
||||
;; :publishing/all-pages-public? false
|
||||
|
||||
;; Define the default home page and sidebar status.
|
||||
;; If unspecified, the journal page will be loaded on startup and the right sidebar will stay hidden.
|
||||
;; The `:page` value represents the name of the page displayed at startup.
|
||||
;; Available options for `:sidebar` are:
|
||||
;; - "Contents" to display the Contents page in the right sidebar.
|
||||
;; - A specific page name to display in the right sidebar.
|
||||
;; - An array of multiple pages, e.g., ["Contents" "Page A" "Page B"].
|
||||
;; If `:sidebar` remains unset, the right sidebar will stay hidden.
|
||||
;; Examples:
|
||||
;; 1. Set "Changelog" as the home page and display "Contents" in the right sidebar:
|
||||
;; :default-home {:page "Changelog", :sidebar "Contents"}
|
||||
;; 2. Set "Jun 3rd, 2021" as the home page without the right sidebar:
|
||||
;; :default-home {:page "Jun 3rd, 2021"}
|
||||
;; 3. Set "home" as the home page and display multiple pages in the right sidebar:
|
||||
;; :default-home {:page "home", :sidebar ["Page A" "Page B"]}
|
||||
|
||||
;; Configure custom shortcuts.
|
||||
;; Syntax:
|
||||
;; 1. + indicates simultaneous key presses, e.g., `Ctrl+Shift+a`.
|
||||
;; 2. A space between keys represents key chords, e.g., `t s` means
|
||||
;; pressing `t` followed by `s`.
|
||||
;; 3. mod refers to `Ctrl` for Windows/Linux and `Command` for Mac.
|
||||
;; 4. Use false to disable a specific shortcut.
|
||||
;; 5. You can define multiple bindings for a single action, e.g., ["ctrl+j" "down"].
|
||||
;; The full list of configurable shortcuts is available at:
|
||||
;; https://github.com/logseq/logseq/blob/master/src/main/frontend/modules/shortcut/config.cljs
|
||||
;; Example:
|
||||
;; :shortcuts
|
||||
;; {:editor/new-block "enter"
|
||||
;; :editor/new-line "shift+enter"
|
||||
;; :editor/insert-link "mod+shift+k"
|
||||
;; :editor/highlight false
|
||||
;; :ui/toggle-settings "t s"
|
||||
;; :editor/up ["ctrl+k" "up"]
|
||||
;; :editor/down ["ctrl+j" "down"]
|
||||
;; :editor/left ["ctrl+h" "left"]
|
||||
;; :editor/right ["ctrl+l" "right"]}
|
||||
:shortcuts {}
|
||||
|
||||
;; Configure the behavior of pressing Enter in document mode.
|
||||
;; if set to true, pressing Enter will create a new block.
|
||||
;; Default value: false
|
||||
:shortcut/doc-mode-enter-for-new-block? false
|
||||
|
||||
;; Block content larger than `block/title-max-length` will not be searchable
|
||||
;; or editable for performance.
|
||||
;; Default value: 10000
|
||||
:block/title-max-length 10000
|
||||
|
||||
;; Display command documentation on hover.
|
||||
;; Default value: true
|
||||
:ui/show-command-doc? true
|
||||
|
||||
;; Display empty bullet points.
|
||||
;; Default value: false
|
||||
:ui/show-empty-bullets? false
|
||||
|
||||
;; Pre-defined :view function to use with advanced queries.
|
||||
:query/views
|
||||
{:pprint
|
||||
(fn [r] [:pre.code (pprint r)])}
|
||||
|
||||
;; Advanced queries `:result-transform` function.
|
||||
;; Transform the query result before displaying it.
|
||||
;; Example usage for DB graphs:
|
||||
;; :query/result-transforms
|
||||
;; {:sort-by-priority
|
||||
;; (fn [result] (sort-by (fn [h] (get h :logseq.property/priority "Z")) result))}
|
||||
|
||||
;; Queries will be displayed at the bottom of today's journal page.
|
||||
;; Example usage:
|
||||
;; :default-queries
|
||||
;; {:journals []}
|
||||
|
||||
;; Add custom commands to the command palette
|
||||
;; Example usage:
|
||||
;; :commands
|
||||
;; [
|
||||
;; ["js" "Javascript"]
|
||||
;; ["md" "Markdown"]
|
||||
;; ]
|
||||
:commands []
|
||||
|
||||
;; Enable collapsing blocks with titles but no children.
|
||||
;; By default, only blocks with children can be collapsed.
|
||||
;; Setting `:outliner/block-title-collapse-enabled?` to true allows collapsing
|
||||
;; blocks with titles (multiple lines) and content. For example:
|
||||
;; - block title
|
||||
;; block content
|
||||
;; Default value: false
|
||||
:outliner/block-title-collapse-enabled? false
|
||||
|
||||
;; Macros replace texts and will make you more productive.
|
||||
;; Example usage:
|
||||
;; Change the :macros value below to:
|
||||
;; {"poem" "Rose is $1, violet's $2. Life's ordered: Org assists you."}
|
||||
;; input "{{poem red,blue}}"
|
||||
;; becomes
|
||||
;; Rose is red, violet's blue. Life's ordered: Org assists you.
|
||||
:macros {}
|
||||
|
||||
;; Configure the default expansion level for linked references.
|
||||
;; For example, consider the following block hierarchy:
|
||||
;; - a [[page]] (level 1)
|
||||
;; - b (level 2)
|
||||
;; - c (level 3)
|
||||
;; - d (level 4)
|
||||
;;
|
||||
;; With the default value of level 2, block b will be collapsed.
|
||||
;; If the level's value is set to 3, block c will be collapsed.
|
||||
;; Default value: 2
|
||||
:ref/default-open-blocks-level 2
|
||||
|
||||
;; Mobile photo upload configuration.
|
||||
;; :mobile/photo
|
||||
;; {:allow-editing? true
|
||||
;; :quality 80}
|
||||
|
||||
;; Mobile features options
|
||||
;; Gestures
|
||||
;; Example usage:
|
||||
;; :mobile
|
||||
;; {:gestures/disabled-in-block-with-tags ["kanban"]}
|
||||
|
||||
;; Extra CodeMirror options
|
||||
;; See https://codemirror.net/5/doc/manual.html#config for possible options
|
||||
;; Example usage:
|
||||
;; :editor/extra-codemirror-options
|
||||
;; {:lineWrapping false ; Default value: false
|
||||
;; :lineNumbers true ; Default value: true
|
||||
;; :readOnly false} ; Default value: false
|
||||
|
||||
;; Enable logical outdenting
|
||||
;; Default value: false
|
||||
;; :editor/logical-outdenting? false
|
||||
|
||||
;; Prefer pasting the file when text and a file are in the clipboard.
|
||||
;; Default value: false
|
||||
;; :editor/preferred-pasting-file? false
|
||||
|
||||
;; Quick capture templates for receiving content from other apps.
|
||||
;; Each template contains three elements {time}, {text} and {url}, which can be auto-expanded
|
||||
;; by receiving content from other apps. Note: the {} cannot be omitted.
|
||||
;; - {time}: capture time
|
||||
;; - {date}: capture date using current date format, use `[[{date}]]` to get a page reference
|
||||
;; - {text}: text that users selected before sharing.
|
||||
;; - {url}: URL or assets path for media files stored in Logseq.
|
||||
;; You can also reorder them or use only one or two of them in the template.
|
||||
;; You can also insert or format any text in the template, as shown in the following examples.
|
||||
;; :quick-capture-templates
|
||||
;; {:text "[[quick capture]] **{time}**: {text} from {url}"
|
||||
;; :media "[[quick capture]] **{time}**: {url}"}
|
||||
|
||||
;; Quick capture options.
|
||||
;; - insert-today? Insert the capture at the end of today's journal page (boolean).
|
||||
;; - redirect-page? Redirect to the quick capture page after capturing (boolean).
|
||||
;; - default-page The default page to capture to if insert-today? is false (string).
|
||||
;; - prettify-url? Whether urls to YouTube or X should be wrapped with {{video ...}} or {{twitter ...}}
|
||||
;; :quick-capture-options
|
||||
;; {:insert-today? false ;; Default value: true
|
||||
;; :redirect-page? false ;; Default value: false
|
||||
;; :default-page "quick capture" ;; Default page: "quick capture"
|
||||
;; :prettify-url? true} ;; Default value: true
|
||||
|
||||
}
|
||||
137
deps/common/src/logseq/common/authorization.cljs
vendored
Normal file
137
deps/common/src/logseq/common/authorization.cljs
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
(ns logseq.common.authorization
|
||||
"jwt token authorization"
|
||||
(:require [clojure.string :as string]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(def text-decoder (js/TextDecoder.))
|
||||
(def text-encoder (js/TextEncoder.))
|
||||
|
||||
(def ^:private jwks-ttl-ms (* 6 60 60 1000))
|
||||
(def ^:private token-ttl-ms (* 60 60 1000))
|
||||
(def ^:private token-capacity 200)
|
||||
|
||||
(defonce ^:private *jwks-cache (atom {:url nil :keys nil :fetched-at 0}))
|
||||
(defonce ^:private *token-cache (atom {}))
|
||||
|
||||
(defn- get-now-ms []
|
||||
(.now js/Date))
|
||||
|
||||
(defn- cached-token
|
||||
[token now-s now-ms]
|
||||
(when-let [{:keys [payload exp cached-at]} (get @*token-cache token)]
|
||||
(when (and (number? exp)
|
||||
(> exp now-s)
|
||||
(< (- now-ms cached-at) token-ttl-ms))
|
||||
payload)))
|
||||
|
||||
(defn- cache-token!
|
||||
[token payload now-ms]
|
||||
(let [exp (aget payload "exp")]
|
||||
(when (number? exp)
|
||||
(swap! *token-cache assoc token {:payload payload :exp exp :cached-at now-ms}))
|
||||
(when (> (count @*token-cache) token-capacity)
|
||||
(swap! *token-cache
|
||||
(fn [cache]
|
||||
(into {}
|
||||
(remove (fn [[_ {:keys [exp cached-at]}]]
|
||||
(or (not (number? exp))
|
||||
(<= exp (js/Math.floor (/ now-ms 1000)))
|
||||
(>= (- now-ms cached-at) token-ttl-ms))))
|
||||
cache))))))
|
||||
|
||||
(defn- get-jwks-keys
|
||||
[url & {:keys [force?]}]
|
||||
(let [now (get-now-ms)
|
||||
{:keys [url cached-url jwks-keys fetched-at]} {:cached-url (:url @*jwks-cache)
|
||||
:url url
|
||||
:jwks-keys (:keys @*jwks-cache)
|
||||
:fetched-at (:fetched-at @*jwks-cache)}
|
||||
fresh? (and (not force?)
|
||||
(= cached-url url)
|
||||
jwks-keys
|
||||
(< (- now fetched-at) jwks-ttl-ms))]
|
||||
(if fresh?
|
||||
(p/resolved jwks-keys)
|
||||
(p/let [jwks-resp (js/fetch url)
|
||||
_ (when-not (.-ok jwks-resp) (throw (ex-info "jwks" {})))
|
||||
jwks (.json jwks-resp)
|
||||
jwks-keys (or (aget jwks "keys") #js [])]
|
||||
(reset! *jwks-cache {:url url :keys jwks-keys :fetched-at now})
|
||||
jwks-keys))))
|
||||
|
||||
(defn- base64url->uint8array [input]
|
||||
(let [pad (if (pos? (mod (count input) 4))
|
||||
(apply str (repeat (- 4 (mod (count input) 4)) "="))
|
||||
"")
|
||||
base64 (-> (str input pad)
|
||||
(string/replace "-" "+")
|
||||
(string/replace "_" "/"))
|
||||
raw (js/atob base64)
|
||||
data (js/Uint8Array. (.-length raw))]
|
||||
(dotimes [i (.-length raw)]
|
||||
(aset data i (.charCodeAt raw i)))
|
||||
data))
|
||||
|
||||
(defn- decode-jwt-part [part]
|
||||
(let [data (base64url->uint8array part)]
|
||||
(js/JSON.parse (.decode text-decoder data))))
|
||||
|
||||
(defn client-id-allowed? [env client-id]
|
||||
(let [primary-client-id (aget env "COGNITO_CLIENT_ID")
|
||||
additional-client-ids (->> (string/split (or (aget env "COGNITO_CLIENT_IDS") "") #",")
|
||||
(map string/trim)
|
||||
(remove string/blank?))
|
||||
allowed-client-ids (cond-> (set additional-client-ids)
|
||||
(and (string? primary-client-id) (not (string/blank? primary-client-id)))
|
||||
(conj primary-client-id))]
|
||||
(and (string? client-id)
|
||||
(not (string/blank? client-id))
|
||||
(contains? allowed-client-ids client-id))))
|
||||
|
||||
(defn- import-rsa-key [jwk]
|
||||
(.importKey js/crypto.subtle
|
||||
"jwk"
|
||||
jwk
|
||||
#js {:name "RSASSA-PKCS1-v1_5" :hash "SHA-256"}
|
||||
false
|
||||
#js ["verify"]))
|
||||
|
||||
(defn verify-jwt [token env]
|
||||
(let [parts (string/split token #"\.")]
|
||||
(when (not= 3 (count parts)) (throw (ex-info "invalid" {})))
|
||||
(let [header-part (nth parts 0)
|
||||
payload-part (nth parts 1)
|
||||
signature-part (nth parts 2)
|
||||
now-ms (get-now-ms)
|
||||
now-s (js/Math.floor (/ now-ms 1000))]
|
||||
(if-let [cached (cached-token token now-s now-ms)]
|
||||
(p/resolved cached)
|
||||
(p/let [header (decode-jwt-part header-part)
|
||||
payload (decode-jwt-part payload-part)
|
||||
issuer (aget env "COGNITO_ISSUER")
|
||||
client-id-claim (or (aget payload "aud")
|
||||
(aget payload "client_id"))
|
||||
_ (when (not= (aget payload "iss") issuer) (throw (ex-info "iss not found" {})))
|
||||
_ (when-not (client-id-allowed? env client-id-claim) (throw (ex-info "aud not found" {})))
|
||||
_ (when (and (aget payload "exp") (< (aget payload "exp") now-s))
|
||||
(throw (ex-info "exp" {})))
|
||||
jwks-url (aget env "COGNITO_JWKS_URL")
|
||||
jwks-keys (get-jwks-keys jwks-url)
|
||||
matching-key (.find jwks-keys (fn [k] (= (aget k "kid") (aget header "kid"))))
|
||||
matching-key (if matching-key
|
||||
matching-key
|
||||
(p/let [jwks-keys (get-jwks-keys jwks-url :force? true)
|
||||
matching-key (.find jwks-keys (fn [k] (= (aget k "kid") (aget header "kid"))))]
|
||||
matching-key))
|
||||
_ (when-not matching-key (throw (ex-info "kid" {})))
|
||||
crypto-key (import-rsa-key matching-key)
|
||||
data (.encode text-encoder (str header-part "." payload-part))
|
||||
signature (base64url->uint8array signature-part)
|
||||
ok (.verify js/crypto.subtle
|
||||
"RSASSA-PKCS1-v1_5"
|
||||
crypto-key
|
||||
signature
|
||||
data)]
|
||||
(when ok
|
||||
(cache-token! token payload now-ms)
|
||||
payload))))))
|
||||
13
deps/common/src/logseq/common/cognito_config.cljs
vendored
Normal file
13
deps/common/src/logseq/common/cognito_config.cljs
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
(ns logseq.common.cognito-config
|
||||
"Shared Cognito configuration for frontend and CLI-safe consumers.")
|
||||
|
||||
(def COGNITO-CLIENT-ID
|
||||
"69cs1lgme7p8kbgld8n5kseii6")
|
||||
|
||||
(def CLI-COGNITO-CLIENT-ID
|
||||
"69cs1lgme7p8kbgld8n5kseii6")
|
||||
|
||||
(def OAUTH-DOMAIN
|
||||
"logseq-prod.auth.us-east-1.amazoncognito.com")
|
||||
|
||||
(def OAUTH-SCOPE "email openid phone")
|
||||
139
deps/common/src/logseq/common/config.cljs
vendored
Normal file
139
deps/common/src/logseq/common/config.cljs
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
(ns logseq.common.config
|
||||
"Common config constants and fns that are shared between deps and app"
|
||||
(:require [clojure.string :as string]))
|
||||
|
||||
(goog-define PUBLISHING false)
|
||||
|
||||
(defn hidden?
|
||||
[path patterns]
|
||||
(let [path (if (and (string? path)
|
||||
(= \/ (first path)))
|
||||
(subs path 1)
|
||||
path)]
|
||||
(some (fn [pattern]
|
||||
(let [pattern (if (and (string? pattern)
|
||||
(not= \/ (first pattern)))
|
||||
(str "/" pattern)
|
||||
pattern)]
|
||||
(string/starts-with? (str "/" path) pattern))) patterns)))
|
||||
|
||||
(defn remove-hidden-files
|
||||
"Removes files that match a pattern specified by :hidden config"
|
||||
[files config get-path-fn]
|
||||
(if-let [patterns (seq (:hidden config))]
|
||||
(remove (fn [file]
|
||||
(let [path (get-path-fn file)]
|
||||
(hidden? path patterns))) files)
|
||||
files))
|
||||
|
||||
(def app-name "logseq")
|
||||
|
||||
(defonce asset-protocol "assets://")
|
||||
|
||||
(defonce db-version-prefix "logseq_db_")
|
||||
(defonce file-version-prefix "logseq_local_")
|
||||
|
||||
(defn strip-leading-db-version-prefix
|
||||
"Strip exactly one leading db prefix for user-facing display values."
|
||||
[s]
|
||||
(if (and (string? s)
|
||||
(string/starts-with? s db-version-prefix))
|
||||
(subs s (count db-version-prefix))
|
||||
s))
|
||||
|
||||
(defn canonicalize-db-version-repo
|
||||
"Normalize any repo/graph name to exactly one leading db prefix."
|
||||
[s]
|
||||
(when (seq s)
|
||||
(let [s (str s)
|
||||
stripped (loop [name' s]
|
||||
(if (string/starts-with? name' db-version-prefix)
|
||||
(recur (subs name' (count db-version-prefix)))
|
||||
name'))]
|
||||
(str db-version-prefix stripped))))
|
||||
|
||||
(defonce default-graphs-dir "~/logseq/graphs")
|
||||
(defonce local-assets-dir "assets")
|
||||
(defonce unlinked-graphs-dir "Unlinked graphs")
|
||||
|
||||
(defonce favorites-page-name "$$$favorites")
|
||||
(defonce views-page-name "$$$views")
|
||||
(defonce library-page-name "Library")
|
||||
(defonce quick-add-page-name "Quick add")
|
||||
(defonce recycle-page-name "Recycle")
|
||||
|
||||
(defn local-relative-asset?
|
||||
[s]
|
||||
(and (string? s)
|
||||
(not (string/includes? s "://"))
|
||||
(re-find (re-pattern (str "^[./]*" local-assets-dir)) s)))
|
||||
|
||||
(defn local-protocol-asset?
|
||||
[s]
|
||||
(when (string? s)
|
||||
(string/starts-with? s asset-protocol)))
|
||||
|
||||
(defn protocol-path?
|
||||
[s]
|
||||
(try
|
||||
(let [url (js/URL. s)]
|
||||
(some? (.-protocol url)))
|
||||
(catch :default _
|
||||
false)))
|
||||
|
||||
(defn remove-asset-protocol
|
||||
[s]
|
||||
(if (local-protocol-asset? s)
|
||||
(-> s
|
||||
(string/replace-first asset-protocol "file://"))
|
||||
s))
|
||||
|
||||
(defn text-formats
|
||||
[]
|
||||
#{:json :org :md :yml :dat :asciidoc :rst :txt :markdown :adoc :html :js :ts :edn :clj :ml :rb :ex :erl :java :php :c :css
|
||||
:tldr :sh})
|
||||
|
||||
(defn img-formats
|
||||
[]
|
||||
#{:gif :svg :jpeg :ico :png :jpg :bmp :webp :avif :cr2})
|
||||
|
||||
(defonce block-pattern "-")
|
||||
|
||||
(def unused-in-db-graphs-deprecation
|
||||
"is not used in DB graphs")
|
||||
|
||||
(def file-only-config
|
||||
"File only config keys that are deprecated in DB graphs along with
|
||||
descriptions for their deprecation."
|
||||
(merge
|
||||
(zipmap
|
||||
[:file/name-format
|
||||
:file-sync/ignore-files
|
||||
:hidden
|
||||
:ignored-page-references-keywords
|
||||
:journal/file-name-format
|
||||
:journal/page-title-format
|
||||
:journals-directory
|
||||
:logbook/settings
|
||||
:org-mode/insert-file-link?
|
||||
:pages-directory
|
||||
:preferred-workflow
|
||||
:property/separated-by-commas
|
||||
:property-pages/excludelist
|
||||
:srs/learning-fraction
|
||||
:srs/initial-interval
|
||||
:whiteboards-directory
|
||||
:feature/enable-whiteboards?]
|
||||
(repeat unused-in-db-graphs-deprecation))
|
||||
{:preferred-format
|
||||
"is not used in DB graphs as there is only markdown mode."
|
||||
:property-pages/enabled?
|
||||
"is not used in DB graphs as all properties have pages"
|
||||
:block-hidden-properties
|
||||
"is not used in DB graphs as hiding a property is done in its configuration"
|
||||
:feature/enable-block-timestamps?
|
||||
"is not used in DB graphs as it is always enabled"
|
||||
:favorites
|
||||
"is not stored in config for DB graphs"
|
||||
:default-templates
|
||||
"is replaced by #Template and the `Apply template to tags` property"}))
|
||||
81
deps/common/src/logseq/common/date.cljs
vendored
Normal file
81
deps/common/src/logseq/common/date.cljs
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
(ns logseq.common.date
|
||||
"Date related fns shared by worker and frontend namespaces. Eventually some
|
||||
of this should go to logseq.common.util.date-time"
|
||||
(:require [cljs-time.format :as tf]
|
||||
[clojure.string :as string]
|
||||
[logseq.common.util :as common-util]))
|
||||
|
||||
(defonce built-in-journal-title-formatters
|
||||
(list
|
||||
"do MMM yyyy"
|
||||
"do MMMM yyyy"
|
||||
"MMM do, yyyy"
|
||||
"MMMM do, yyyy"
|
||||
"E, dd-MM-yyyy"
|
||||
"E, dd.MM.yyyy"
|
||||
"E, MM/dd/yyyy"
|
||||
"E, yyyy/MM/dd"
|
||||
"EEE, dd-MM-yyyy"
|
||||
"EEE, dd.MM.yyyy"
|
||||
"EEE, MM/dd/yyyy"
|
||||
"EEE, yyyy/MM/dd"
|
||||
"EEEE, dd-MM-yyyy"
|
||||
"EEEE, dd.MM.yyyy"
|
||||
"EEEE, MM/dd/yyyy"
|
||||
"EEEE, yyyy/MM/dd"
|
||||
"dd-MM-yyyy"
|
||||
;; This tyle will mess up other date formats like "2022-08" "2022Q4" "2022/10"
|
||||
;; "dd.MM.yyyy"
|
||||
"MM/dd/yyyy"
|
||||
"MM-dd-yyyy"
|
||||
"MM_dd_yyyy"
|
||||
"yyyy/MM/dd"
|
||||
"yyyy-MM-dd"
|
||||
"yyyy-MM-dd EEE"
|
||||
"yyyy-MM-dd EEEE"
|
||||
"yyyy_MM_dd"
|
||||
"yyyyMMdd"
|
||||
"yyyy年MM月dd日"))
|
||||
|
||||
(defonce slash-journal-title-formatters
|
||||
(filter #(string/includes? % "/") built-in-journal-title-formatters))
|
||||
|
||||
(defn journal-title-formatters
|
||||
[date-formatter]
|
||||
(->
|
||||
(cons
|
||||
date-formatter
|
||||
built-in-journal-title-formatters)
|
||||
(distinct)))
|
||||
|
||||
(defn normalize-date
|
||||
"Given raw date string, return a normalized date string at best effort.
|
||||
Warning: this is a function with heavy cost (likely 50ms). Use with caution"
|
||||
[s date-formatter]
|
||||
(some
|
||||
(fn [formatter]
|
||||
(try
|
||||
(tf/parse (tf/formatter formatter) s)
|
||||
(catch :default _e
|
||||
false)))
|
||||
(journal-title-formatters date-formatter)))
|
||||
|
||||
(defn normalize-journal-title
|
||||
"Normalize journal title at best effort. Return nil if title is not a valid date.
|
||||
Return goog.date.Date.
|
||||
|
||||
Return format: 20220812T000000"
|
||||
[title date-formatter]
|
||||
(and title
|
||||
(normalize-date (common-util/capitalize-all title) date-formatter)))
|
||||
|
||||
(defn ^:api valid-journal-title?
|
||||
"This is a loose rule, requires double check by journal-title->custom-format.
|
||||
|
||||
BUG: This also accepts strings like 3/4/5 as journal titles"
|
||||
[title date-formatter]
|
||||
(boolean (normalize-journal-title title date-formatter)))
|
||||
|
||||
(defn ^:api valid-journal-title-with-slash?
|
||||
[title]
|
||||
(some #(valid-journal-title? title %) slash-journal-title-formatters))
|
||||
31
deps/common/src/logseq/common/defkeywords.cljc
vendored
Normal file
31
deps/common/src/logseq/common/defkeywords.cljc
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
(ns logseq.common.defkeywords
|
||||
"Macro 'defkeywords' to def keyword with config"
|
||||
#?(:cljs (:require-macros [logseq.common.defkeywords])))
|
||||
|
||||
(def ^:private *defined-kws (volatile! {}))
|
||||
|
||||
#_:clj-kondo/ignore
|
||||
(defmacro defkeyword
|
||||
"Define keyword with docstring.
|
||||
How 'find keyword definition' works?
|
||||
clojure-lsp treat keywords defined by `cljs.spec.alpha/def` as keyword-definition.
|
||||
Adding a :lint-as `defkeyword` -> `cljs.spec.alpha/def` in clj-kondo config make it works."
|
||||
[& _args])
|
||||
|
||||
(defmacro defkeywords
|
||||
"impl at hooks.defkeywords in .clj-kondo
|
||||
(defkeywords ::a <config-map> ::b <config-map>)"
|
||||
[& keyvals]
|
||||
(let [kws (take-nth 2 keyvals)
|
||||
current-meta (meta &form)]
|
||||
(doseq [kw kws]
|
||||
(when-let [info (get @*defined-kws kw)]
|
||||
(when (not= (:file current-meta) (:file info))
|
||||
(vswap! *defined-kws assoc kw current-meta)
|
||||
(throw (ex-info "keyword already defined somewhere else" {:kw kw :info info}))))
|
||||
(vswap! *defined-kws assoc kw current-meta))
|
||||
;; (let [kw->config (partition 2 keyvals)]
|
||||
;; (doseq [[kw config] kw->config]
|
||||
;; (vswap! *defined-kw->config assoc kw config)))
|
||||
)
|
||||
`(vector ~@keyvals))
|
||||
140
deps/common/src/logseq/common/graph.cljs
vendored
Normal file
140
deps/common/src/logseq/common/graph.cljs
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
(ns ^:node-only logseq.common.graph
|
||||
"This ns provides common fns for a graph directory and only runs in a node environment"
|
||||
(:require ["fs" :as fs]
|
||||
["os" :as os]
|
||||
["path" :as node-path]
|
||||
[clojure.string :as string]
|
||||
[logseq.common.config :as common-config]
|
||||
[logseq.common.graph-dir :as graph-dir]
|
||||
[logseq.common.path :as path]))
|
||||
|
||||
(def ^:private win32?
|
||||
"Copy of electron.utils/win32? . Too basic to couple the two libraries"
|
||||
(= (.-platform js/process) "win32"))
|
||||
|
||||
(defn- fix-win-path!
|
||||
"Copy of electron.utils/fix-win-path!. Too basic to couple the two libraries"
|
||||
[path]
|
||||
(when (not-empty path)
|
||||
(if win32?
|
||||
(string/replace path "\\" "/")
|
||||
path)))
|
||||
|
||||
(defn readdir
|
||||
"Reads given directory recursively and returns all filenames. Also applies
|
||||
some graph-specific filtering e.g. symbolic links and files starting with '.'
|
||||
are removed"
|
||||
[root-dir]
|
||||
(->> (tree-seq
|
||||
(fn [[is-dir _fpath]]
|
||||
is-dir)
|
||||
(fn [[_is-dir dir]]
|
||||
(let [files (fs/readdirSync dir #js {:withFileTypes true})]
|
||||
(->> files
|
||||
(remove #(.isSymbolicLink ^js %))
|
||||
(remove #(string/starts-with? (.-name ^js %) "."))
|
||||
(map #(do
|
||||
[(.isDirectory %)
|
||||
(node-path/join dir (.-name %))])))))
|
||||
[true root-dir])
|
||||
(filter (complement first))
|
||||
(map second)
|
||||
(map fix-win-path!)
|
||||
(vec)))
|
||||
|
||||
(defn read-directories
|
||||
"Given a dir, returns all the sub-directories"
|
||||
[root-dir]
|
||||
(let [files (fs/readdirSync root-dir #js {:withFileTypes true})]
|
||||
(->> files
|
||||
(remove #(.isSymbolicLink ^js %))
|
||||
(remove #(string/starts-with? (.-name ^js %) "."))
|
||||
(filter #(.isDirectory %))
|
||||
(map #(.-name %)))))
|
||||
|
||||
(defn- path-at-or-under?
|
||||
[path dir]
|
||||
(or (= path dir)
|
||||
(string/starts-with? path (str dir "/"))))
|
||||
|
||||
(defn ignored-path?
|
||||
"Given a graph directory and path, returns truthy value on whether the path is
|
||||
ignored. Useful for contexts like reading a graph's directory and file watcher
|
||||
notifications
|
||||
|
||||
Rules:
|
||||
|
||||
- Paths starting with '.' are ignored
|
||||
- Paths ending with '.DS_Store' are ignored
|
||||
- Dynamic caches used by Logseq are ignored: graph-txid.edn and pages-metadata.edn
|
||||
- Contents in '**/node_modules/' are ignored
|
||||
- Contents in '/logseq/.recycle/' are ignored
|
||||
- Contents in '/logseq/bak/' are ignored
|
||||
- Contents in with '/logseq/version-files/' are ignored
|
||||
- Contents in '/mirror/markdown/' are ignored
|
||||
"
|
||||
[dir path]
|
||||
(let [dir (path/path-normalize dir)
|
||||
path (path/path-normalize path)
|
||||
rpath (path/trim-dir-prefix dir path)]
|
||||
(when (string? path)
|
||||
(or
|
||||
(string/starts-with? rpath ".")
|
||||
(some #(path-at-or-under? rpath %)
|
||||
["logseq/.recycle" "logseq/bak" "logseq/version-files" "mirror/markdown"])
|
||||
(contains? #{"logseq/graphs-txid.edn" "logseq/pages-metadata.edn"} rpath)
|
||||
(some #(string/includes? rpath (str "/" % "/"))
|
||||
["node_modules"])
|
||||
(some #(string/ends-with? rpath %)
|
||||
[".DS_Store"])
|
||||
;; hidden directory or file
|
||||
(or (re-find #"/\.[^.]+" rpath)
|
||||
(re-find #"^\.[^.]+" rpath))))))
|
||||
|
||||
(def ^:private allowed-formats
|
||||
#{:org :markdown :md :edn :json :js :css})
|
||||
|
||||
(defn- get-ext
|
||||
[p]
|
||||
(-> (node-path/extname p)
|
||||
(subs 1)
|
||||
keyword))
|
||||
|
||||
(defn get-files
|
||||
"Given a graph's root dir, returns a list of all files that it recognizes.
|
||||
Graph dir must be an absolute path in order for ignoring to work correctly"
|
||||
[graph-dir]
|
||||
(->> (readdir graph-dir)
|
||||
(remove (partial ignored-path? graph-dir))
|
||||
(filter #(contains? allowed-formats (get-ext %)))))
|
||||
|
||||
(defn get-default-graphs-dir
|
||||
"Get default dir for storing graphs by first looking in env var."
|
||||
[]
|
||||
(or js/process.env.LOGSEQ_GRAPHS_DIR common-config/default-graphs-dir))
|
||||
|
||||
(defn expand-home
|
||||
"Expands path if it starts with '~'"
|
||||
[path]
|
||||
(if (and (seq path) (string/starts-with? path "~"))
|
||||
(node-path/join (os/homedir) (subs path 1))
|
||||
path))
|
||||
|
||||
(defn get-db-graphs-dir
|
||||
"Returns the directory where DB graphs are stored."
|
||||
[]
|
||||
(expand-home (get-default-graphs-dir)))
|
||||
|
||||
(defn get-db-based-graphs
|
||||
"Returns canonical DB graph repo names from the default DB graph directory."
|
||||
[]
|
||||
(let [dir (get-db-graphs-dir)]
|
||||
(fs/mkdirSync dir #js {:recursive true})
|
||||
(->> (read-directories dir)
|
||||
(remove (fn [s] (= s common-config/unlinked-graphs-dir)))
|
||||
(map graph-dir/decode-graph-dir-name)
|
||||
(keep (fn [s]
|
||||
(when (and (string? s)
|
||||
(not (string/starts-with? s common-config/file-version-prefix)))
|
||||
(common-config/canonicalize-db-version-repo s))))
|
||||
distinct)))
|
||||
75
deps/common/src/logseq/common/graph_dir.cljs
vendored
Normal file
75
deps/common/src/logseq/common/graph_dir.cljs
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
(ns logseq.common.graph-dir
|
||||
"Platform-agnostic graph directory naming helpers."
|
||||
(:require [clojure.string :as string]
|
||||
[logseq.common.config :as common-config]))
|
||||
|
||||
(defn encode-graph-dir-name
|
||||
[graph-name]
|
||||
(let [encoded (js/encodeURIComponent (or graph-name ""))]
|
||||
(-> encoded
|
||||
(string/replace "%20" " ")
|
||||
(string/replace "~" "%7E")
|
||||
(string/replace "%" "~"))))
|
||||
|
||||
(defn decode-graph-dir-name
|
||||
[dir-name]
|
||||
(when-not (and (string? dir-name)
|
||||
(or (string/includes? dir-name "++")
|
||||
(string/includes? dir-name "+3A+")))
|
||||
(when (some? dir-name)
|
||||
(try
|
||||
(js/decodeURIComponent (string/replace dir-name "~" "%"))
|
||||
(catch :default _
|
||||
nil)))))
|
||||
|
||||
(def ^:private legacy-dir-pattern #"(?:\+\+|\+3A\+|%)")
|
||||
|
||||
(defn decode-legacy-graph-dir-name
|
||||
[dir-name]
|
||||
(when (and (string? dir-name)
|
||||
(re-find legacy-dir-pattern dir-name))
|
||||
(let [compat-name (-> dir-name
|
||||
(string/replace "+3A+" ":")
|
||||
(string/replace "++" "/"))]
|
||||
(try
|
||||
(let [decoded (js/decodeURIComponent compat-name)]
|
||||
(when (seq decoded)
|
||||
decoded))
|
||||
(catch :default _
|
||||
nil)))))
|
||||
|
||||
(defn repo->graph-dir-key
|
||||
[repo]
|
||||
(when (seq repo)
|
||||
(if (string/starts-with? repo common-config/db-version-prefix)
|
||||
(subs repo (count common-config/db-version-prefix))
|
||||
repo)))
|
||||
|
||||
(defn repo-identity
|
||||
"Return the canonical value used for repo identity comparison.
|
||||
|
||||
Repo identity comparison is based on the graph directory key, so `demo` and
|
||||
`logseq_db_demo` identify the same graph. Use this helper, or `same-repo?`,
|
||||
whenever code needs to decide whether two repo names represent the same graph."
|
||||
[repo]
|
||||
(repo->graph-dir-key repo))
|
||||
|
||||
(defn same-repo?
|
||||
"Return true when two repo names identify the same graph."
|
||||
[a b]
|
||||
(let [a' (repo-identity a)
|
||||
b' (repo-identity b)]
|
||||
(and (some? a')
|
||||
(some? b')
|
||||
(= a' b'))))
|
||||
|
||||
(defn graph-dir-key->encoded-dir-name
|
||||
[graph-dir-key]
|
||||
(when (some? graph-dir-key)
|
||||
(encode-graph-dir-name graph-dir-key)))
|
||||
|
||||
(defn repo->encoded-graph-dir-name
|
||||
[repo]
|
||||
(some-> repo
|
||||
repo->graph-dir-key
|
||||
graph-dir-key->encoded-dir-name))
|
||||
72
deps/common/src/logseq/common/graph_registry.cljc
vendored
Normal file
72
deps/common/src/logseq/common/graph_registry.cljc
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
(ns logseq.common.graph-registry
|
||||
"Graph registry normalization and lookup helpers."
|
||||
(:require [clojure.string :as string]))
|
||||
|
||||
(def ^:private db-version-prefix
|
||||
"logseq_db_")
|
||||
|
||||
(defn- present-string?
|
||||
[s]
|
||||
(and (string? s) (not (string/blank? s))))
|
||||
|
||||
(defn normalize-entry
|
||||
[entry]
|
||||
(let [local-graph-id (:local-graph-id entry)
|
||||
graph-id (or (when (present-string? (:graph-id entry)) (:graph-id entry))
|
||||
(when (present-string? local-graph-id) local-graph-id))]
|
||||
(when-not graph-id
|
||||
(throw (ex-info "Missing graph identity"
|
||||
{:entry entry})))
|
||||
(-> entry
|
||||
(assoc :graph-id graph-id)
|
||||
(dissoc :rtc-graph-id))))
|
||||
|
||||
(defn ^:api upsert-entry
|
||||
[registry entry]
|
||||
(let [entry' (normalize-entry entry)]
|
||||
(->> (or registry [])
|
||||
(remove #(or (= (:graph-id entry') (:graph-id %))
|
||||
(and (present-string? (:repo entry'))
|
||||
(= (:repo entry') (:repo %)))
|
||||
(and (present-string? (:local-graph-id entry'))
|
||||
(= (:local-graph-id entry') (:local-graph-id %)))))
|
||||
(cons entry')
|
||||
vec)))
|
||||
|
||||
(defn- normalize-comparable
|
||||
[s]
|
||||
(some-> s str string/lower-case))
|
||||
|
||||
(defn- canonical-repo
|
||||
[s]
|
||||
(when (seq s)
|
||||
(let [s (str s)
|
||||
stripped (loop [name' s]
|
||||
(if (string/starts-with? name' db-version-prefix)
|
||||
(recur (subs name' (count db-version-prefix)))
|
||||
name'))]
|
||||
(str db-version-prefix stripped))))
|
||||
|
||||
(defn- identifier-match?
|
||||
[entry graph-identifier]
|
||||
(let [identifier (normalize-comparable graph-identifier)
|
||||
repo (normalize-comparable (:repo entry))
|
||||
graph-name (normalize-comparable (:graph-name entry))
|
||||
graph-id (normalize-comparable (:graph-id entry))
|
||||
canonical-repo-name (normalize-comparable (canonical-repo graph-identifier))]
|
||||
(or (= identifier repo)
|
||||
(= identifier graph-name)
|
||||
(= identifier graph-id)
|
||||
(= canonical-repo-name repo))))
|
||||
|
||||
(defn ^:api resolve-target
|
||||
[registry {:keys [graph-id graph-identifier]}]
|
||||
(cond
|
||||
(present-string? graph-id)
|
||||
(some #(when (= graph-id (:graph-id %)) %) registry)
|
||||
|
||||
(present-string? graph-identifier)
|
||||
(some #(when (identifier-match? % graph-identifier) %) registry)
|
||||
|
||||
:else
|
||||
nil))
|
||||
6
deps/common/src/logseq/common/log.cljs
vendored
Normal file
6
deps/common/src/logseq/common/log.cljs
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
(ns logseq.common.log
|
||||
"Minimal, logging ns that shims lambdaisland.glogi fns for nbb. Could port
|
||||
glogi to nbb later if this shim gets too big")
|
||||
|
||||
(defn error [& msgs]
|
||||
(apply js/console.error (map clj->js msgs)))
|
||||
296
deps/common/src/logseq/common/path.cljs
vendored
Normal file
296
deps/common/src/logseq/common/path.cljs
vendored
Normal file
@@ -0,0 +1,296 @@
|
||||
(ns logseq.common.path
|
||||
"Path manipulation functions, use '/' sep on all platforms.
|
||||
Also handles URL paths."
|
||||
(:require [clojure.string :as string]))
|
||||
|
||||
(defn- safe-decode-uri-component
|
||||
[uri]
|
||||
(try
|
||||
(.normalize (js/decodeURIComponent uri) "NFC")
|
||||
(catch :default _
|
||||
(js/console.error "decode-uri-component-failed" uri)
|
||||
uri)))
|
||||
|
||||
(defn is-file-url?
|
||||
[s]
|
||||
(and (string? s)
|
||||
(or
|
||||
(string/starts-with? s "memory://") ;; special memory fs
|
||||
(string/starts-with? s "assets://") ;; Electron asset, urlencoded
|
||||
(string/starts-with? s "file://") ;; Electron files
|
||||
)))
|
||||
|
||||
(defn filename
|
||||
"File name of a path or URL.
|
||||
Returns nil when it's a directory that ends with '/'."
|
||||
[path]
|
||||
(let [fname (if (string/ends-with? path "/")
|
||||
nil
|
||||
(last (string/split path #"/")))]
|
||||
(if (and (seq fname) (is-file-url? path))
|
||||
(safe-decode-uri-component fname)
|
||||
fname)))
|
||||
|
||||
(defn split-ext
|
||||
"Split file name into stem and extension, for both path and URL"
|
||||
[path]
|
||||
(let [fname (filename path)
|
||||
pos (string/last-index-of fname ".")]
|
||||
(if-not (or (nil? pos) (zero? pos))
|
||||
[(subs fname 0 pos)
|
||||
(string/lower-case (subs fname (+ pos 1)))]
|
||||
[fname ""])))
|
||||
|
||||
(defn file-stem
|
||||
"File name without extension"
|
||||
[path]
|
||||
(first (split-ext path)))
|
||||
|
||||
(defn file-ext
|
||||
"File extension, lowercased"
|
||||
[path]
|
||||
(second (split-ext path)))
|
||||
|
||||
(defn- path-join-internal
|
||||
"Joins the given path segments into a single path, handling relative paths,
|
||||
'..' and '.' normalization."
|
||||
[& segments]
|
||||
(let [segments (remove string/blank? segments) ;; handle (path-join nil path)
|
||||
segments (map #(string/replace % #"[/\\]+" "/") segments)
|
||||
;; a fix for clojure.string/split
|
||||
split-fn (fn [s]
|
||||
(if (= s "/")
|
||||
[""]
|
||||
(string/split s #"/")))
|
||||
join-fn (fn [segs]
|
||||
(case segs
|
||||
[] "."
|
||||
[""] "/"
|
||||
#_{:clj-kondo/ignore [:path-invalid-construct/string-join]}
|
||||
(string/join "/" segs)))]
|
||||
(->> (filter seq segments)
|
||||
(mapcat split-fn)
|
||||
(reduce (fn [acc segment]
|
||||
(cond
|
||||
(= "" segment)
|
||||
[segment]
|
||||
|
||||
(= ".." segment)
|
||||
(case (last acc)
|
||||
".." (conj acc segment)
|
||||
"" acc
|
||||
nil [".."]
|
||||
(pop acc))
|
||||
|
||||
(= "." segment)
|
||||
acc
|
||||
|
||||
:else
|
||||
(conj acc segment)))
|
||||
[])
|
||||
(join-fn))))
|
||||
|
||||
(defn- uri-path-join-internal
|
||||
"Joins the given URI path segments into a single path, handling relative paths,
|
||||
'..' and '.' normalization."
|
||||
[& segments]
|
||||
(let [segments (remove nil? segments) ;; handle (path-join nil path)
|
||||
segments (map #(string/replace % #"[/\\]+" "/") segments)
|
||||
;; a fix for clojure.string/split
|
||||
split-fn (fn [s]
|
||||
(if (= s "/")
|
||||
[""]
|
||||
(string/split s #"/")))
|
||||
join-fn (fn [segs]
|
||||
(case segs
|
||||
[] "."
|
||||
[""] "/"
|
||||
#_{:clj-kondo/ignore [:path-invalid-construct/string-join]}
|
||||
(string/join "/" segs)))]
|
||||
(->> (filter seq segments)
|
||||
(mapcat split-fn)
|
||||
(map #(js/encodeURIComponent %))
|
||||
(reduce (fn [acc segment]
|
||||
(cond
|
||||
(= "" segment)
|
||||
[segment]
|
||||
|
||||
(= ".." segment)
|
||||
(case (last acc)
|
||||
".." (conj acc segment)
|
||||
"" acc
|
||||
nil [".."]
|
||||
(pop acc))
|
||||
|
||||
(= "." segment)
|
||||
acc
|
||||
|
||||
:else
|
||||
(conj acc segment)))
|
||||
[])
|
||||
(join-fn))))
|
||||
|
||||
(defn- preserve-file-url-win-drive
|
||||
[scheme encoded-path]
|
||||
(cond-> encoded-path
|
||||
(= scheme "file:")
|
||||
(string/replace #"^/([a-zA-Z])%3[Aa](?=/|$)" "/$1:")))
|
||||
|
||||
(defn url-join
|
||||
"Segments are not URL-encoded"
|
||||
[base-url & segments]
|
||||
(let [custom-scheme (re-find #"^[a-zA-Z0-9_+\-\.]+://" base-url)
|
||||
custom-scheme? (and custom-scheme (not= custom-scheme "file://"))
|
||||
base-url (cond-> base-url
|
||||
custom-scheme? (string/replace-first custom-scheme "file://"))
|
||||
^js url (try
|
||||
(js/URL. (safe-decode-uri-component base-url))
|
||||
(catch :default e
|
||||
(js/console.error "Failed to construct URL in url-join:" base-url e)
|
||||
(js/URL. "file:///")))
|
||||
scheme (if custom-scheme?
|
||||
(string/replace custom-scheme "//" "")
|
||||
(.-protocol url))
|
||||
path (.-pathname url)
|
||||
domain (or (not-empty (.-host url))
|
||||
(if (or custom-scheme? (string/starts-with? path "/")) "" "/"))
|
||||
encoded-new-path (->> (apply uri-path-join-internal path segments)
|
||||
(preserve-file-url-win-drive scheme))]
|
||||
(str scheme "//" domain encoded-new-path)))
|
||||
|
||||
(defn path-join
|
||||
"Join path segments, or URL base and path segments"
|
||||
[base & segments]
|
||||
(if (is-file-url? base)
|
||||
(apply url-join base segments)
|
||||
(let [rejoined-path (apply path-join-internal base segments)]
|
||||
(if (and (not-empty base)
|
||||
(string/starts-with? base "//")) ;; Win path fix
|
||||
(str "/" rejoined-path)
|
||||
rejoined-path))))
|
||||
|
||||
(defn prepend-protocol
|
||||
"Prepend protocol to path. Handle UNC path. aka. path-to-url
|
||||
|
||||
protocol is one of file: http: https: assets:"
|
||||
[protocol path]
|
||||
(cond
|
||||
(string/starts-with? path protocol)
|
||||
(do
|
||||
(js/console.error "BUG: should not prepend protocol to path with protocol" protocol path)
|
||||
path)
|
||||
|
||||
(string/starts-with? path "//") ;; Windows UNC path
|
||||
(str protocol path)
|
||||
|
||||
:else
|
||||
(path-join (str protocol "//") path)))
|
||||
|
||||
(defn- path-normalize-internal
|
||||
"Normalize path using path-join, break into segment and re-join"
|
||||
[path]
|
||||
(path-join path))
|
||||
|
||||
(defn url-normalize
|
||||
[origin-url]
|
||||
(let [^js url (try
|
||||
(js/URL. (safe-decode-uri-component origin-url))
|
||||
(catch :default e
|
||||
(js/console.error "Failed to construct URL in url-normalize:" origin-url e)
|
||||
nil))
|
||||
scheme (when url (.-protocol url))
|
||||
domain (when url (or (not-empty (.-host url)) "/"))
|
||||
path (when url (.-pathname url))
|
||||
encoded-new-path (when url
|
||||
(->> (uri-path-join-internal path)
|
||||
(preserve-file-url-win-drive scheme)))]
|
||||
(if url
|
||||
(str scheme "//" domain encoded-new-path)
|
||||
origin-url)))
|
||||
|
||||
(defn path-normalize
|
||||
"Normalize path or URL"
|
||||
[path]
|
||||
(-> (if (is-file-url? path)
|
||||
(url-normalize path)
|
||||
(path-normalize-internal path))
|
||||
(.normalize "NFC")))
|
||||
|
||||
(defn url-to-path
|
||||
"Extract path part of a URL, decoded.
|
||||
|
||||
The reverse operation is (path-join protocol:// path)"
|
||||
[original-url]
|
||||
(if (is-file-url? original-url)
|
||||
;; NOTE: URL type is not consistent across all protocols
|
||||
;; Check file:// and assets://, pathname behavior is different
|
||||
(if-let [^js url (try
|
||||
(js/URL. (string/replace (safe-decode-uri-component original-url) "assets://" "file://"))
|
||||
(catch :default e
|
||||
(js/console.error "Failed to construct URL in url-to-path:" original-url e)
|
||||
nil))]
|
||||
(let [path (.-pathname url)
|
||||
host (.-host url)
|
||||
path (if (string/starts-with? path "///")
|
||||
(subs path 2)
|
||||
path)
|
||||
path (if (re-find #"(?i)^/[a-zA-Z]:" path) ;; Win path fix
|
||||
(subs path 1)
|
||||
path)]
|
||||
(if (string/blank? host)
|
||||
path
|
||||
(str "//" host path)))
|
||||
original-url)
|
||||
original-url))
|
||||
|
||||
(defn file-url-or-path->path
|
||||
"Converts a file URL to a local filesystem path."
|
||||
[s]
|
||||
(if (is-file-url? s)
|
||||
(url-to-path s)
|
||||
s))
|
||||
|
||||
(defn trim-dir-prefix
|
||||
"Trim dir prefix from path"
|
||||
[base-path sub-path]
|
||||
(let [base-path (path-normalize base-path)
|
||||
sub-path (path-normalize sub-path)
|
||||
is-url? (is-file-url? base-path)]
|
||||
(if (string/starts-with? sub-path base-path)
|
||||
(if is-url?
|
||||
(safe-decode-uri-component (string/replace (subs sub-path (count base-path)) #"^/+", ""))
|
||||
(string/replace (subs sub-path (count base-path)) #"^/+", ""))
|
||||
(do
|
||||
(js/console.error "unhandled trim-base" base-path sub-path)
|
||||
nil))))
|
||||
|
||||
(defn parent
|
||||
"Parent, containing directory"
|
||||
[path]
|
||||
(if (string/includes? path "/")
|
||||
;; ugly but works
|
||||
(path-normalize (str path "/.."))
|
||||
nil))
|
||||
|
||||
;; compat
|
||||
(defn basename
|
||||
[path]
|
||||
(let [path (string/replace path #"/+$" "")]
|
||||
(filename path)))
|
||||
|
||||
(defn absolute?
|
||||
"Whether path `p` is absolute."
|
||||
[p]
|
||||
(let [p (path-normalize p)]
|
||||
(boolean (or (is-file-url? p)
|
||||
(string/starts-with? p "/")
|
||||
;; is windows dir
|
||||
(re-find #"^[a-zA-Z]:[/\\]" p)))))
|
||||
|
||||
(defn protocol-url?
|
||||
"Whether path `p` is a protocol URL.
|
||||
|
||||
This is a loose check, it only checks if there is a valid protocol prefix."
|
||||
[p]
|
||||
(boolean (and (re-find #"^[a-zA-Z0-9_+\-\.]{2,}:" p) ;; HACK: avoid matching windows drive
|
||||
(not (string/includes? p " ")))))
|
||||
333
deps/common/src/logseq/common/plural.cljs
vendored
Normal file
333
deps/common/src/logseq/common/plural.cljs
vendored
Normal file
@@ -0,0 +1,333 @@
|
||||
(ns logseq.common.plural
|
||||
"ClojureScript port of pluralize.js core (rules + API).
|
||||
|
||||
Usage:
|
||||
(pluralize \"duck\" 2 true) ;; => \"2 ducks\"
|
||||
(plural \"person\") ;; => \"people\"
|
||||
(singular \"people\") ;; => \"person\"
|
||||
(is-plural? \"ducks\") ;; => true
|
||||
(is-singular? \"duck\") ;; => true
|
||||
|
||||
You can add rules at runtime:
|
||||
(add-plural-rule! #\"(ox)$\" \"$1en\")
|
||||
(add-uncountable-rule! \"metadata\")"
|
||||
(:require [clojure.string :as string]))
|
||||
|
||||
;; -----------------------------------------------------------------------------
|
||||
;; Rule storage (mirrors original semantics)
|
||||
;; pluralize and singularize must run rules sequentially.
|
||||
;; -----------------------------------------------------------------------------
|
||||
|
||||
(defonce ^:private plural-rules (atom [])) ;; vector of [js/RegExp replacement]
|
||||
(defonce ^:private singular-rules (atom [])) ;; vector of [js/RegExp replacement]
|
||||
(defonce ^:private uncountables (atom {})) ;; token -> true
|
||||
(defonce ^:private irregular-plurals (atom {})) ;; plural -> singular
|
||||
(defonce ^:private irregular-singles (atom {})) ;; singular -> plural
|
||||
|
||||
;; -----------------------------------------------------------------------------
|
||||
;; Helpers
|
||||
;; -----------------------------------------------------------------------------
|
||||
|
||||
(defn- sanitize-rule
|
||||
"If rule is a string, compile to case-insensitive regexp that matches the whole string.
|
||||
Else keep it (assumed to be js/RegExp)."
|
||||
[rule]
|
||||
(if (string? rule)
|
||||
(js/RegExp. (str "^" rule "$") "i")
|
||||
rule))
|
||||
|
||||
(defn- restore-case
|
||||
"Replicate casing of `word` onto `token`."
|
||||
[word token]
|
||||
(cond
|
||||
(= word token)
|
||||
token
|
||||
|
||||
(= word (string/lower-case word))
|
||||
(string/lower-case token)
|
||||
|
||||
(= word (string/upper-case word))
|
||||
(string/upper-case token)
|
||||
|
||||
(and (seq word)
|
||||
(= (subs word 0 1) (string/upper-case (subs word 0 1))))
|
||||
(str (string/upper-case (subs token 0 1))
|
||||
(string/lower-case (subs token 1)))
|
||||
|
||||
:else
|
||||
(string/lower-case token)))
|
||||
|
||||
(defn- interpolate
|
||||
"Replace $1..$12 etc in `s` using JS replace args (match, g1, g2 ...)."
|
||||
[s js-args]
|
||||
(.replace s (js/RegExp. "\\$(\\d{1,2})" "g")
|
||||
(fn [_ idx]
|
||||
(let [i (js/parseInt idx 10)
|
||||
v (aget js-args i)]
|
||||
(or v "")))))
|
||||
|
||||
(defn- replace-with-rule
|
||||
"Apply a [re repl] rule to word with casing restoration (matches JS behavior)."
|
||||
[word [re repl]]
|
||||
(.replace word re
|
||||
(fn [& args]
|
||||
;; args: [match g1 g2 ... offset string]
|
||||
(let [match (nth args 0)
|
||||
;; In JS replace callback, second-to-last is offset
|
||||
offset (nth args (- (count args) 2))
|
||||
;; interpolate expects JS-ish indexed args;
|
||||
;; easiest is to turn args into a JS array.
|
||||
js-args (to-array args)
|
||||
result (interpolate repl js-args)]
|
||||
(if (= match "")
|
||||
;; match empty => restore based on char before match
|
||||
(restore-case (subs word (dec offset) offset) result)
|
||||
(restore-case match result))))))
|
||||
|
||||
(defn- sanitize-word
|
||||
"Return sanitized `word` based on `token` and `rules`."
|
||||
[token word rules]
|
||||
(cond
|
||||
(or (zero? (count token))
|
||||
(contains? @uncountables token))
|
||||
word
|
||||
|
||||
:else
|
||||
(let [rs rules
|
||||
;; JS iterates from end to start
|
||||
n (count rs)]
|
||||
(loop [i (dec n)]
|
||||
(if (neg? i)
|
||||
word
|
||||
(let [[re _ :as rule] (nth rs i)]
|
||||
(if (.test re word)
|
||||
(replace-with-rule word rule)
|
||||
(recur (dec i)))))))))
|
||||
|
||||
(defn- replace-word-fn
|
||||
"Build a word transformer (plural or singular)."
|
||||
[replace-map-atom keep-map-atom rules-atom]
|
||||
(fn [word]
|
||||
(let [token (string/lower-case word)
|
||||
keep-map @keep-map-atom
|
||||
replace-map @replace-map-atom
|
||||
rules @rules-atom]
|
||||
(cond
|
||||
(contains? keep-map token)
|
||||
(restore-case word token)
|
||||
|
||||
(contains? replace-map token)
|
||||
(restore-case word (get replace-map token))
|
||||
|
||||
:else
|
||||
(sanitize-word token word rules)))))
|
||||
|
||||
(defn- check-word-fn
|
||||
"Build a predicate for whether word is plural/singular (mirrors JS `checkWord`)."
|
||||
[replace-map-atom keep-map-atom rules-atom]
|
||||
(fn [word]
|
||||
(let [token (string/lower-case word)
|
||||
keep-map @keep-map-atom
|
||||
replace-map @replace-map-atom
|
||||
rules @rules-atom]
|
||||
(cond
|
||||
(contains? keep-map token) true
|
||||
(contains? replace-map token) false
|
||||
:else (= (sanitize-word token token rules) token)))))
|
||||
|
||||
;; -----------------------------------------------------------------------------
|
||||
;; Public API (matches original surface)
|
||||
;; -----------------------------------------------------------------------------
|
||||
|
||||
(def plural (replace-word-fn irregular-singles irregular-plurals plural-rules))
|
||||
(def singular (replace-word-fn irregular-plurals irregular-singles singular-rules))
|
||||
|
||||
(def is-plural? (check-word-fn irregular-singles irregular-plurals plural-rules))
|
||||
(def is-singular? (check-word-fn irregular-plurals irregular-singles singular-rules))
|
||||
|
||||
(defn pluralize
|
||||
"Pluralize or singularize based on count. If inclusive, prefix with count."
|
||||
([word item-count] (pluralize word item-count false))
|
||||
([word item-count inclusive]
|
||||
(let [pluralized (if (= item-count 1) (singular word) (plural word))]
|
||||
(str (when inclusive (str item-count " "))
|
||||
pluralized))))
|
||||
|
||||
(defn add-plural-rule!
|
||||
[rule replacement]
|
||||
(swap! plural-rules conj [(sanitize-rule rule) replacement]))
|
||||
|
||||
(defn add-singular-rule!
|
||||
[rule replacement]
|
||||
(swap! singular-rules conj [(sanitize-rule rule) replacement]))
|
||||
|
||||
(defn add-uncountable-rule!
|
||||
"If word is string => mark as uncountable.
|
||||
If regexp => add plural+singular passthrough rules ($0)."
|
||||
[word]
|
||||
(if (string? word)
|
||||
(swap! uncountables assoc (string/lower-case word) true)
|
||||
(do
|
||||
(add-plural-rule! word "$0")
|
||||
(add-singular-rule! word "$0"))))
|
||||
|
||||
(defn add-irregular-rule!
|
||||
[single plural-word]
|
||||
(let [p (string/lower-case plural-word)
|
||||
s (string/lower-case single)]
|
||||
(swap! irregular-singles assoc s p)
|
||||
(swap! irregular-plurals assoc p s)))
|
||||
|
||||
;; -----------------------------------------------------------------------------
|
||||
;; Data initialization (same as original JS)
|
||||
;; -----------------------------------------------------------------------------
|
||||
|
||||
(defn- ^:large-vars/cleanup-todo init-irregulars! []
|
||||
(doseq [[s p]
|
||||
;; Pronouns + irregulars
|
||||
[["I" "we"]
|
||||
["me" "us"]
|
||||
["he" "they"]
|
||||
["she" "they"]
|
||||
["them" "them"]
|
||||
["myself" "ourselves"]
|
||||
["yourself" "yourselves"]
|
||||
["itself" "themselves"]
|
||||
["herself" "themselves"]
|
||||
["himself" "themselves"]
|
||||
["themself" "themselves"]
|
||||
["is" "are"]
|
||||
["was" "were"]
|
||||
["has" "have"]
|
||||
["this" "these"]
|
||||
["that" "those"]
|
||||
["my" "our"]
|
||||
["its" "their"]
|
||||
["his" "their"]
|
||||
["her" "their"]
|
||||
;; Words ending with consonant + o
|
||||
["echo" "echoes"]
|
||||
["dingo" "dingoes"]
|
||||
["volcano" "volcanoes"]
|
||||
["tornado" "tornadoes"]
|
||||
["torpedo" "torpedoes"]
|
||||
;; Ends with us
|
||||
["genus" "genera"]
|
||||
["viscus" "viscera"]
|
||||
;; Ends with ma
|
||||
["stigma" "stigmata"]
|
||||
["stoma" "stomata"]
|
||||
["dogma" "dogmata"]
|
||||
["lemma" "lemmata"]
|
||||
["schema" "schemata"]
|
||||
["anathema" "anathemata"]
|
||||
;; Other irregular
|
||||
["ox" "oxen"]
|
||||
["axe" "axes"]
|
||||
["die" "dice"]
|
||||
["yes" "yeses"]
|
||||
["foot" "feet"]
|
||||
["eave" "eaves"]
|
||||
["goose" "geese"]
|
||||
["tooth" "teeth"]
|
||||
["quiz" "quizzes"]
|
||||
["human" "humans"]
|
||||
["proof" "proofs"]
|
||||
["carve" "carves"]
|
||||
["valve" "valves"]
|
||||
["looey" "looies"]
|
||||
["thief" "thieves"]
|
||||
["groove" "grooves"]
|
||||
["pickaxe" "pickaxes"]
|
||||
["passerby" "passersby"]
|
||||
["canvas" "canvases"]]]
|
||||
(add-irregular-rule! s p)))
|
||||
|
||||
(defn- init-plural-rules! []
|
||||
(doseq [[rule repl]
|
||||
[[(js/RegExp. "s?$" "i") "s"]
|
||||
[(js/RegExp. "[^\\u0000-\\u007F]$" "i") "$0"]
|
||||
[(js/RegExp. "([^aeiou]ese)$" "i") "$1"]
|
||||
[(js/RegExp. "(ax|test)is$" "i") "$1es"]
|
||||
[(js/RegExp. "(alias|[^aou]us|t[lm]as|gas|ris)$" "i") "$1es"]
|
||||
[(js/RegExp. "(e[mn]u)s?$" "i") "$1s"]
|
||||
[(js/RegExp. "([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$" "i") "$1"]
|
||||
[(js/RegExp. "(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$" "i") "$1i"]
|
||||
[(js/RegExp. "(alumn|alg|vertebr)(?:a|ae)$" "i") "$1ae"]
|
||||
[(js/RegExp. "(seraph|cherub)(?:im)?$" "i") "$1im"]
|
||||
[(js/RegExp. "(her|at|gr)o$" "i") "$1oes"]
|
||||
[(js/RegExp. "(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$" "i") "$1a"]
|
||||
[(js/RegExp. "(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$" "i") "$1a"]
|
||||
[(js/RegExp. "sis$" "i") "ses"]
|
||||
[(js/RegExp. "(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$" "i") "$1$2ves"]
|
||||
[(js/RegExp. "([^aeiouy]|qu)y$" "i") "$1ies"]
|
||||
[(js/RegExp. "([^ch][ieo][ln])ey$" "i") "$1ies"]
|
||||
[(js/RegExp. "(x|ch|ss|sh|zz)$" "i") "$1es"]
|
||||
[(js/RegExp. "(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$" "i") "$1ices"]
|
||||
[(js/RegExp. "\\b((?:tit)?m|l)(?:ice|ouse)$" "i") "$1ice"]
|
||||
[(js/RegExp. "(pe)(?:rson|ople)$" "i") "$1ople"]
|
||||
[(js/RegExp. "(child)(?:ren)?$" "i") "$1ren"]
|
||||
[(js/RegExp. "eaux$" "i") "$0"]
|
||||
[(js/RegExp. "m[ae]n$" "i") "men"]
|
||||
["thou" "you"]]]
|
||||
(add-plural-rule! rule repl)))
|
||||
|
||||
(defn- init-singular-rules! []
|
||||
(doseq [[rule repl]
|
||||
[[(js/RegExp. "s$" "i") ""]
|
||||
[(js/RegExp. "(ss)$" "i") "$1"]
|
||||
[(js/RegExp. "(wi|kni|(?:after|half|high|low|mid|non|night|[^\\w]|^)li)ves$" "i") "$1fe"]
|
||||
[(js/RegExp. "(ar|(?:wo|[ae])l|[eo][ao])ves$" "i") "$1f"]
|
||||
[(js/RegExp. "ies$" "i") "y"]
|
||||
[(js/RegExp. "(dg|ss|ois|lk|ok|wn|mb|th|ch|ec|oal|is|ck|ix|sser|ts|wb)ies$" "i") "$1ie"]
|
||||
[(js/RegExp. "\\b(l|(?:neck|cross|hog|aun)?t|coll|faer|food|gen|goon|group|hipp|junk|vegg|(?:pork)?p|charl|calor|cut)ies$" "i") "$1ie"]
|
||||
[(js/RegExp. "\\b(mon|smil)ies$" "i") "$1ey"]
|
||||
[(js/RegExp. "\\b((?:tit)?m|l)ice$" "i") "$1ouse"]
|
||||
[(js/RegExp. "(seraph|cherub)im$" "i") "$1"]
|
||||
[(js/RegExp. "(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$" "i") "$1"]
|
||||
[(js/RegExp. "(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$" "i") "$1sis"]
|
||||
[(js/RegExp. "(movie|twelve|abuse|e[mn]u)s$" "i") "$1"]
|
||||
[(js/RegExp. "(test)(?:is|es)$" "i") "$1is"]
|
||||
[(js/RegExp. "(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$" "i") "$1us"]
|
||||
[(js/RegExp. "(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$" "i") "$1um"]
|
||||
[(js/RegExp. "(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$" "i") "$1on"]
|
||||
[(js/RegExp. "(alumn|alg|vertebr)ae$" "i") "$1a"]
|
||||
[(js/RegExp. "(cod|mur|sil|vert|ind)ices$" "i") "$1ex"]
|
||||
[(js/RegExp. "(matr|append)ices$" "i") "$1ix"]
|
||||
[(js/RegExp. "(pe)(rson|ople)$" "i") "$1rson"]
|
||||
[(js/RegExp. "(child)ren$" "i") "$1"]
|
||||
[(js/RegExp. "(eau)x?$" "i") "$1"]
|
||||
[(js/RegExp. "men$" "i") "man"]]]
|
||||
(add-singular-rule! rule repl)))
|
||||
|
||||
(defn- init-uncountables! []
|
||||
(doseq [w
|
||||
["adulthood" "advice" "agenda" "aid" "aircraft" "alcohol" "ammo"
|
||||
"analytics" "anime" "athletics" "audio" "bison" "blood" "bream"
|
||||
"buffalo" "butter" "carp" "cash" "chassis" "chess" "clothing" "cod"
|
||||
"commerce" "cooperation" "corps" "debris" "diabetes" "digestion" "elk"
|
||||
"energy" "equipment" "excretion" "expertise" "firmware" "flounder"
|
||||
"fun" "gallows" "garbage" "graffiti" "hardware" "headquarters" "health"
|
||||
"herpes" "highjinks" "homework" "housework" "information" "jeans"
|
||||
"justice" "kudos" "labour" "literature" "machinery" "mackerel" "mail"
|
||||
"media" "mews" "moose" "music" "mud" "manga" "news" "only" "personnel"
|
||||
"pike" "plankton" "pliers" "police" "pollution" "premises" "rain"
|
||||
"research" "rice" "salmon" "scissors" "series" "sewage" "shambles"
|
||||
"shrimp" "software" "staff" "swine" "tennis" "traffic"
|
||||
"transportation" "trout" "tuna" "wealth" "welfare" "whiting"
|
||||
"wildebeest" "wildlife" "you"]]
|
||||
(add-uncountable-rule! w))
|
||||
(doseq [re [(js/RegExp. "pok[eé]mon$" "i")
|
||||
(js/RegExp. "[^aeiou]ese$" "i")
|
||||
(js/RegExp. "deer$" "i")
|
||||
(js/RegExp. "fish$" "i")
|
||||
(js/RegExp. "measles$" "i")
|
||||
(js/RegExp. "o[iu]s$" "i")
|
||||
(js/RegExp. "pox$" "i")
|
||||
(js/RegExp. "sheep$" "i")]]
|
||||
(add-uncountable-rule! re)))
|
||||
|
||||
(init-irregulars!)
|
||||
(init-plural-rules!)
|
||||
(init-singular-rules!)
|
||||
(init-uncountables!)
|
||||
18
deps/common/src/logseq/common/profile.clj
vendored
Normal file
18
deps/common/src/logseq/common/profile.clj
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
(ns logseq.common.profile
|
||||
"Utils for profiling")
|
||||
|
||||
(defmacro profile-fn!
|
||||
[f & {:keys [print-on-call? gen-k-fn]
|
||||
:or {print-on-call? true}}]
|
||||
`(let [origin-f# ~f
|
||||
gen-k-fn# (or ~gen-k-fn (constantly (keyword ~f)))]
|
||||
(set! ~f (fn [& args#]
|
||||
(let [start# (cljs.core/system-time)
|
||||
r# (apply origin-f# args#)
|
||||
end# (cljs.core/system-time)
|
||||
k# (gen-k-fn# r#)]
|
||||
(vswap! *key->call-count update k# inc)
|
||||
(vswap! *key->time-sum update k# #(+ % (- end# start#)))
|
||||
(when ~print-on-call?
|
||||
(println "call-count:" (get @*key->call-count k#) "time-sum(ms):" (get @*key->time-sum k#)))
|
||||
r#)))))
|
||||
11
deps/common/src/logseq/common/profile.cljs
vendored
Normal file
11
deps/common/src/logseq/common/profile.cljs
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
(ns logseq.common.profile
|
||||
"Utils for profiling"
|
||||
(:require-macros [logseq.common.profile]))
|
||||
|
||||
(def *key->call-count
|
||||
"key -> count"
|
||||
(volatile! {}))
|
||||
|
||||
(def *key->time-sum
|
||||
"docstring"
|
||||
(volatile! {}))
|
||||
376
deps/common/src/logseq/common/util.cljs
vendored
Normal file
376
deps/common/src/logseq/common/util.cljs
vendored
Normal file
@@ -0,0 +1,376 @@
|
||||
(ns logseq.common.util
|
||||
"Util fns shared between the app. Util fns only rely on
|
||||
clojure standard libraries."
|
||||
(:require [cljs-time.coerce :as tc]
|
||||
[cljs-time.core :as t]
|
||||
[cljs.reader :as reader]
|
||||
[clojure.edn :as edn]
|
||||
[clojure.string :as string]
|
||||
[goog.string :as gstring]
|
||||
[logseq.common.log :as log]))
|
||||
|
||||
(defn safe-decode-uri-component
|
||||
[uri]
|
||||
(try
|
||||
(js/decodeURIComponent uri)
|
||||
(catch :default _
|
||||
(log/error :decode-uri-component-failed uri)
|
||||
uri)))
|
||||
|
||||
(defn path-normalize
|
||||
"Normalize file path (for reading paths from FS, not required by writing)
|
||||
Keep capitalization sensitivity"
|
||||
[s]
|
||||
(.normalize s "NFC"))
|
||||
|
||||
(defn remove-nils-non-nested
|
||||
"remove pairs of key-value that has nil value from a map (nested not supported)."
|
||||
[nm]
|
||||
(into {} (remove (comp nil? second)) nm))
|
||||
|
||||
(defn fast-remove-nils
|
||||
"remove pairs of key-value that has nil value from a coll of maps."
|
||||
[nm]
|
||||
(keep (fn [m] (if (map? m) (remove-nils-non-nested m) m)) nm))
|
||||
|
||||
(defn split-first [pattern s]
|
||||
(when-let [first-index (string/index-of s pattern)]
|
||||
[(subs s 0 first-index)
|
||||
(subs s (+ first-index (count pattern)) (count s))]))
|
||||
|
||||
(defn split-last [pattern s]
|
||||
(when-let [last-index (string/last-index-of s pattern)]
|
||||
[(subs s 0 last-index)
|
||||
(subs s (+ last-index (count pattern)) (count s))]))
|
||||
|
||||
(defn tag-valid?
|
||||
[tag-name]
|
||||
(when (string? tag-name)
|
||||
(not (re-find #"[#\t\r\n]+" tag-name))))
|
||||
|
||||
(defn safe-subs
|
||||
"Like `subs`, but clamps out-of-range indices and returns \"\" for a non-string
|
||||
`s` instead of throwing. `subs` is a bare `.substring` passthrough, so a nil
|
||||
`s` otherwise raises \"Cannot read properties of null\"."
|
||||
([s start]
|
||||
(let [c (count s)]
|
||||
(safe-subs s start c)))
|
||||
([s start end]
|
||||
(if (string? s)
|
||||
(let [c (count s)]
|
||||
(subs s (min c start) (min c end)))
|
||||
"")))
|
||||
|
||||
(defn wrapped-by
|
||||
[v start end]
|
||||
(and (string? v) (>= (count v) 2)
|
||||
(= start (first v)) (= end (last v))))
|
||||
|
||||
(defn wrapped-by-quotes?
|
||||
[v]
|
||||
(wrapped-by v "\"" "\""))
|
||||
|
||||
(defn wrapped-by-parens?
|
||||
[v]
|
||||
(wrapped-by v "(" ")"))
|
||||
|
||||
(defn url?
|
||||
"Test if it is a `protocol://`-style URL.
|
||||
|
||||
NOTE: Can not handle mailto: links, use this with caution."
|
||||
[s]
|
||||
(and (string? s)
|
||||
(try
|
||||
(not (contains? #{nil "null"} (.-origin (js/URL. s))))
|
||||
(catch :default _e
|
||||
false))))
|
||||
|
||||
(defn json->clj
|
||||
[json-string]
|
||||
(-> json-string
|
||||
(js/JSON.parse)
|
||||
(js->clj :keywordize-keys true)))
|
||||
|
||||
(defn zero-pad
|
||||
[n]
|
||||
(if (< n 10)
|
||||
(str "0" n)
|
||||
(str n)))
|
||||
|
||||
(defn remove-boundary-slashes
|
||||
[s]
|
||||
(when (string? s)
|
||||
(let [s (if (= \/ (first s))
|
||||
(subs s 1)
|
||||
s)]
|
||||
(if (= \/ (last s))
|
||||
(subs s 0 (dec (count s)))
|
||||
s))))
|
||||
|
||||
(defn split-namespace-pages
|
||||
[title]
|
||||
(let [parts (string/split title "/")]
|
||||
(->>
|
||||
(loop [others (rest parts)
|
||||
result [(first parts)]]
|
||||
(if (seq others)
|
||||
(let [prev (last result)]
|
||||
(recur (rest others)
|
||||
(conj result (str prev "/" (first others)))))
|
||||
result))
|
||||
(map string/trim))))
|
||||
|
||||
(def url-encoded-pattern #"(?i)%[0-9a-f]{2}") ;; (?i) for case-insensitive mode
|
||||
|
||||
(defn page-name-sanity
|
||||
"Sanitize the page-name. Unify different diacritics and other visual differences.
|
||||
Two objectives:
|
||||
1. To be the same as in the filesystem;
|
||||
2. To be easier to search"
|
||||
[page-name]
|
||||
(some-> page-name
|
||||
(remove-boundary-slashes)
|
||||
(path-normalize)))
|
||||
|
||||
(defn page-name-sanity-lc
|
||||
"Sanitize the query string for a page name (mandate for :block/name)"
|
||||
[s]
|
||||
(page-name-sanity (string/lower-case s)))
|
||||
|
||||
(defn safe-page-name-sanity-lc
|
||||
[s]
|
||||
(if (string? s)
|
||||
(page-name-sanity-lc s) s))
|
||||
|
||||
(defn capitalize-all
|
||||
[s]
|
||||
(some->> (string/split s #" ")
|
||||
(map string/capitalize)
|
||||
(string/join " ")))
|
||||
|
||||
(defn distinct-by
|
||||
"Copy from medley"
|
||||
[f coll]
|
||||
(let [step (fn step [xs seen]
|
||||
(lazy-seq
|
||||
((fn [[x :as xs] seen]
|
||||
(when-let [s (seq xs)]
|
||||
(let [fx (f x)]
|
||||
(if (contains? seen fx)
|
||||
(recur (rest s) seen)
|
||||
(cons x (step (rest s) (conj seen fx)))))))
|
||||
xs seen)))]
|
||||
(step (seq coll) #{})))
|
||||
|
||||
(defn distinct-by-last-wins
|
||||
[f col]
|
||||
{:pre [(sequential? col)]}
|
||||
(reverse (distinct-by f (reverse col))))
|
||||
|
||||
(defn normalize-format
|
||||
[format]
|
||||
(case (keyword format)
|
||||
:md :markdown
|
||||
;; default
|
||||
(keyword format)))
|
||||
|
||||
(defn path->file-ext
|
||||
[path-or-file-name]
|
||||
(let [last-part (last (string/split path-or-file-name #"/"))]
|
||||
(second (re-find #"(?:\.)(\w+)[^.]*$" last-part))))
|
||||
|
||||
(defn get-format
|
||||
"File path to format keyword, :org, :markdown, etc."
|
||||
[file]
|
||||
(when file
|
||||
(normalize-format (keyword (some-> (path->file-ext file) string/lower-case)))))
|
||||
|
||||
(defn get-file-ext
|
||||
"Copy of frontend.util/get-file-ext. Too basic to couple to main app"
|
||||
[file]
|
||||
(and
|
||||
(string? file)
|
||||
(string/includes? file ".")
|
||||
(some-> (path->file-ext file) string/lower-case)))
|
||||
|
||||
(defn valid-edn-keyword?
|
||||
"Determine if string is a valid edn keyword"
|
||||
[s]
|
||||
(try
|
||||
(boolean (and (= \: (first s))
|
||||
(edn/read-string (str "{" s " nil}"))))
|
||||
(catch :default _
|
||||
false)))
|
||||
|
||||
(defn safe-read-string
|
||||
"Reads an edn string and returns nil if it fails to parse"
|
||||
([content]
|
||||
(safe-read-string {} content))
|
||||
([{:keys [log-error?] :or {log-error? true} :as opts} content]
|
||||
(try
|
||||
(reader/read-string (dissoc opts :log-error?) content)
|
||||
(catch :default e
|
||||
(when log-error? (log/error :parse/read-string-failed e))
|
||||
nil))))
|
||||
|
||||
(defn safe-read-map-string
|
||||
"Reads an edn map string and returns {} if it fails to parse"
|
||||
([content]
|
||||
(safe-read-map-string {} content))
|
||||
([opts content]
|
||||
(try
|
||||
(reader/read-string opts content)
|
||||
(catch :default e
|
||||
(log/error :parse/read-string-failed e)
|
||||
{}))))
|
||||
|
||||
(defn safe-re-find
|
||||
{:malli/schema [:=> [:cat :any :string] [:or :nil :string [:vector [:maybe :string]]]]}
|
||||
[pattern s]
|
||||
(when-not (string? s)
|
||||
;; TODO: sentry
|
||||
(js/console.trace))
|
||||
(when (string? s)
|
||||
(re-find pattern s)))
|
||||
|
||||
(def uuid-pattern "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
|
||||
(defonce exactly-uuid-pattern (re-pattern (str "(?i)^" uuid-pattern "$")))
|
||||
|
||||
(defn uuid-string?
|
||||
{:malli/schema [:=> [:cat :string] :boolean]}
|
||||
[s]
|
||||
(boolean (safe-re-find exactly-uuid-pattern s)))
|
||||
|
||||
(defn format
|
||||
[fmt & args]
|
||||
(apply gstring/format fmt args))
|
||||
|
||||
(defn concat-without-nil
|
||||
[& cols]
|
||||
(->> (apply concat cols)
|
||||
(remove nil?)))
|
||||
|
||||
(defn time-ms
|
||||
"Current time in milliseconds"
|
||||
[]
|
||||
(tc/to-long (t/now)))
|
||||
|
||||
(defn get-page-title
|
||||
[page]
|
||||
(or (:block/title page)
|
||||
(:block/name page)))
|
||||
|
||||
(defn string-join-path
|
||||
"Replace all `strings/join` used to construct paths with this function to reduce lint output.
|
||||
https://github.com/logseq/logseq/pull/8679"
|
||||
[parts]
|
||||
#_:clj-kondo/ignore
|
||||
(string/join "/" parts))
|
||||
|
||||
(def ^:private escape-chars "\\[]{}().+*?|$^")
|
||||
|
||||
(defn escape-regex-chars
|
||||
"Escapes characters in string `old-value"
|
||||
[old-value]
|
||||
(reduce (fn [acc escape-char]
|
||||
(string/replace acc escape-char (str "\\" escape-char)))
|
||||
old-value escape-chars))
|
||||
|
||||
(defn replace-ignore-case
|
||||
[s old-value new-value]
|
||||
(string/replace s (re-pattern (str "(?i)" (escape-regex-chars old-value))) new-value))
|
||||
|
||||
(defn sort-coll-by-dependency
|
||||
"Sort the elements in the collection based on dependencies.
|
||||
coll: [{:id 1 :depend-on 2} {:id 2 :depend-on 3} {:id 3}]
|
||||
get-elem-id-fn: :id
|
||||
get-elem-dep-id-fn :depend-on
|
||||
return: [{:id 3} {:id 2 :depend-on 3} {:id 1 :depend-on 2}]"
|
||||
[get-elem-id-fn get-elem-dep-id-fn coll]
|
||||
(let [id->elem (into {} (keep (juxt get-elem-id-fn identity)) coll)
|
||||
id->dep-id (into {} (keep (juxt get-elem-id-fn get-elem-dep-id-fn)) coll)
|
||||
all-ids (set (keys id->dep-id))
|
||||
seen-ids (volatile! #{}) ; to check dep-cycle
|
||||
sorted-ids
|
||||
(loop [r []
|
||||
rest-ids all-ids
|
||||
id (first rest-ids)]
|
||||
(if-not id
|
||||
r
|
||||
(if-let [dep-id (id->dep-id id)]
|
||||
(let [next-id (get rest-ids dep-id)]
|
||||
(if (and next-id
|
||||
;; if found dep-cycle, break it
|
||||
(not (contains? @seen-ids next-id)))
|
||||
(do (vswap! seen-ids conj next-id)
|
||||
(recur r rest-ids next-id))
|
||||
(let [rest-ids* (disj rest-ids id)]
|
||||
(vreset! seen-ids #{})
|
||||
(recur (conj r id) rest-ids* (first rest-ids*)))))
|
||||
;; not found dep-id, so this id can be put into result now
|
||||
(let [rest-ids* (disj rest-ids id)]
|
||||
(vreset! seen-ids #{})
|
||||
(recur (conj r id) rest-ids* (first rest-ids*))))))]
|
||||
(mapv id->elem sorted-ids)))
|
||||
|
||||
(defonce markdown-heading-pattern #"^#+\s+")
|
||||
|
||||
(defn clear-markdown-heading
|
||||
[content]
|
||||
{:pre [(string? content)]}
|
||||
(string/replace-first content markdown-heading-pattern ""))
|
||||
|
||||
(defn block-with-timestamps
|
||||
"Adds updated-at timestamp and created-at if it doesn't exist"
|
||||
[block]
|
||||
(let [updated-at (time-ms)
|
||||
block (cond->
|
||||
(assoc block :block/updated-at updated-at)
|
||||
(nil? (:block/created-at block))
|
||||
(assoc :block/created-at updated-at))]
|
||||
block))
|
||||
|
||||
(defn get-timestamp
|
||||
[value]
|
||||
(let [now (t/now)
|
||||
f t/minus]
|
||||
(if (string? value)
|
||||
(case value
|
||||
"1 day ago"
|
||||
(tc/to-long (f now (t/days 1)))
|
||||
"3 days ago"
|
||||
(tc/to-long (f now (t/days 3)))
|
||||
"1 week ago"
|
||||
(tc/to-long (f now (t/weeks 1)))
|
||||
"1 month ago"
|
||||
(tc/to-long (f now (t/months 1)))
|
||||
"3 months ago"
|
||||
(tc/to-long (f now (t/months 3)))
|
||||
"1 year ago"
|
||||
(tc/to-long (f now (t/years 1)))
|
||||
nil)
|
||||
(tc/to-long (tc/to-date value)))))
|
||||
|
||||
(defn keyword->string
|
||||
[x]
|
||||
(if (keyword? x)
|
||||
(if-let [nn (namespace x)]
|
||||
(str nn "/" (name x))
|
||||
(name x))
|
||||
x))
|
||||
|
||||
(defn by-sorting
|
||||
[sorting]
|
||||
(let [get-value+cmp
|
||||
(map
|
||||
(fn [{:keys [get-value asc?]}]
|
||||
[get-value
|
||||
(if asc? compare #(compare %2 %1))])
|
||||
sorting)]
|
||||
(fn [a b]
|
||||
(reduce
|
||||
(fn [order [get-value cmp]]
|
||||
(if (zero? order)
|
||||
(cmp (get-value a) (get-value b))
|
||||
(reduced order)))
|
||||
0 get-value+cmp))))
|
||||
37
deps/common/src/logseq/common/util/block_ref.cljs
vendored
Normal file
37
deps/common/src/logseq/common/util/block_ref.cljs
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
(ns logseq.common.util.block-ref
|
||||
"Core vars and util fns for block-refs"
|
||||
(:require [clojure.string :as string]))
|
||||
|
||||
(def left-parens "Opening characters for block-ref" "((")
|
||||
(def right-parens "Closing characters for block-ref" "))")
|
||||
(def left-and-right-parens "Opening and closing characters for block-ref"
|
||||
(str left-parens right-parens))
|
||||
(def block-ref-re #"\(\(([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})\)\)")
|
||||
|
||||
(defn get-block-ref-id
|
||||
"Extracts block id from block-ref using regex"
|
||||
[s]
|
||||
(second (re-matches block-ref-re s)))
|
||||
|
||||
(defn get-string-block-ref-id
|
||||
"Extracts block id from block-ref by stripping parens e.g. ((123)) -> 123.
|
||||
This is a less strict version of get-block-ref-id"
|
||||
[s]
|
||||
(subs s 2 (- (count s) 2)))
|
||||
|
||||
(defn block-ref?
|
||||
"Determines if string is block ref using regex"
|
||||
[s]
|
||||
(boolean (get-block-ref-id s)))
|
||||
|
||||
(defn string-block-ref?
|
||||
"Determines if string is block ref by checking parens. This is less strict version
|
||||
of block-ref?"
|
||||
[s]
|
||||
(and (string/starts-with? s left-parens)
|
||||
(string/ends-with? s right-parens)))
|
||||
|
||||
(defn ->block-ref
|
||||
"Creates block ref string given id"
|
||||
[block-id]
|
||||
(str left-parens block-id right-parens))
|
||||
110
deps/common/src/logseq/common/util/date_time.cljs
vendored
Normal file
110
deps/common/src/logseq/common/util/date_time.cljs
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
(ns logseq.common.util.date-time
|
||||
"cljs-time util fns for deps"
|
||||
(:require [cljs-time.coerce :as tc]
|
||||
[cljs-time.core :as t]
|
||||
[cljs-time.format :as tf]
|
||||
[clojure.string :as string]
|
||||
[logseq.common.util :as common-util]))
|
||||
|
||||
(def ^:private yyyyMMdd-formatter (tf/formatter "yyyyMMdd"))
|
||||
(def ^:api default-journal-title-formatter "MMM do, yyyy")
|
||||
|
||||
;; (tf/parse (tf/formatter "dd.MM.yyyy") "2021Q4") => 20040120T000000
|
||||
(defn safe-journal-title-formatters
|
||||
[date-formatter]
|
||||
(->> [date-formatter default-journal-title-formatter "yyyy-MM-dd" "yyyy_MM_dd"]
|
||||
(remove string/blank?)
|
||||
distinct))
|
||||
|
||||
(defn journal-title->
|
||||
[journal-title then-fn formatters]
|
||||
(when-not (string/blank? journal-title)
|
||||
(when-let [time' (->> (map
|
||||
(fn [formatter]
|
||||
(try
|
||||
(tf/parse (tf/formatter formatter) (common-util/capitalize-all journal-title))
|
||||
(catch :default _e
|
||||
nil)))
|
||||
formatters)
|
||||
(filter some?)
|
||||
first)]
|
||||
(then-fn time'))))
|
||||
|
||||
(defn journal-title->int
|
||||
[journal-title formatters]
|
||||
(when journal-title
|
||||
(let [journal-title (common-util/capitalize-all journal-title)]
|
||||
(journal-title-> journal-title
|
||||
#(parse-long (tf/unparse yyyyMMdd-formatter %))
|
||||
formatters))))
|
||||
|
||||
(defn format
|
||||
[date date-formatter]
|
||||
(when date-formatter
|
||||
(tf/unparse (tf/formatter date-formatter) date)))
|
||||
|
||||
(defn int->local-date
|
||||
[day]
|
||||
(let [s (str day)
|
||||
year (js/parseInt (subs s 0 4))
|
||||
month (dec (js/parseInt (subs s 4 6)))
|
||||
day (js/parseInt (subs s 6))]
|
||||
(js/Date. year month day)))
|
||||
|
||||
(defn int->journal-title
|
||||
[day date-formatter]
|
||||
(when day
|
||||
(format (tf/parse yyyyMMdd-formatter (str day)) date-formatter)))
|
||||
|
||||
(defn- get-weekday
|
||||
[date]
|
||||
(.toLocaleString date "en-us" (clj->js {:weekday "long"})))
|
||||
|
||||
(defn- get-date
|
||||
([]
|
||||
(get-date (js/Date.)))
|
||||
([date]
|
||||
{:year (.getFullYear date)
|
||||
:month (inc (.getMonth date))
|
||||
:day (.getDate date)
|
||||
:weekday (get-weekday date)}))
|
||||
|
||||
(defn year-month-day-padded
|
||||
([]
|
||||
(year-month-day-padded (get-date)))
|
||||
([date]
|
||||
(let [{:keys [year month day]} date]
|
||||
{:year year
|
||||
:month (common-util/zero-pad month)
|
||||
:day (common-util/zero-pad day)})))
|
||||
|
||||
(defn ymd
|
||||
([]
|
||||
(ymd (js/Date.)))
|
||||
([date]
|
||||
(ymd date "/"))
|
||||
([date sep]
|
||||
(let [{:keys [year month day]} (year-month-day-padded (get-date date))]
|
||||
(str year sep month sep day))))
|
||||
|
||||
(defn date->int
|
||||
"Given a date object, returns its journal page integer"
|
||||
[date]
|
||||
(parse-long
|
||||
(string/replace (ymd date) "/" "")))
|
||||
|
||||
(defn journal-day->ms
|
||||
"Converts a journal's :block/journal-day integer into milliseconds"
|
||||
[day]
|
||||
(when day
|
||||
(-> (tf/parse yyyyMMdd-formatter (str day))
|
||||
(tc/to-long))))
|
||||
|
||||
(defn ms->journal-day
|
||||
"Converts a milliseconds timestamp to the nearest :block/journal-day"
|
||||
[ms]
|
||||
(some->> ms
|
||||
tc/from-long
|
||||
t/to-default-time-zone
|
||||
(tf/unparse yyyyMMdd-formatter)
|
||||
parse-long))
|
||||
38
deps/common/src/logseq/common/util/macro.cljs
vendored
Normal file
38
deps/common/src/logseq/common/util/macro.cljs
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
(ns logseq.common.util.macro
|
||||
"Core vars and util fns for built-in macros e.g. {{query }} and user macros e.g. {{foo}}"
|
||||
(:require [clojure.string :as string]))
|
||||
|
||||
(def left-braces "Opening characters for macro" "{{")
|
||||
(def right-braces "Closing characters for macro" "}}")
|
||||
|
||||
(defn macro?
|
||||
[*s]
|
||||
(when-let [s (and (string? *s) (string/trim *s))]
|
||||
(and (string/starts-with? s left-braces) (string/ends-with? s right-braces))))
|
||||
|
||||
(defn macro-subs
|
||||
[macro-content arguments]
|
||||
(loop [s macro-content
|
||||
args arguments
|
||||
n 1]
|
||||
(if (seq args)
|
||||
(recur
|
||||
(string/replace s (str "$" n) (first args))
|
||||
(rest args)
|
||||
(inc n))
|
||||
s)))
|
||||
|
||||
(defn- macro-expand-value
|
||||
"Checks a string for a macro and expands it if there's a macro entry for it.
|
||||
This is a slimmer version of macro-else-cp"
|
||||
[value macros]
|
||||
(if-let [[_ macro args] (and (string? value)
|
||||
(seq (re-matches #"\{\{(\S+)\s+(.*)\}\}" value)))]
|
||||
(if-let [content (get macros macro)]
|
||||
(macro-subs content (string/split args #"\s+"))
|
||||
value)
|
||||
value))
|
||||
|
||||
(defn expand-value-if-macro
|
||||
[s macros]
|
||||
(if (macro? s) (macro-expand-value s macros) s))
|
||||
27
deps/common/src/logseq/common/util/namespace.cljs
vendored
Normal file
27
deps/common/src/logseq/common/util/namespace.cljs
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
(ns logseq.common.util.namespace
|
||||
"Util fns for namespace and parent features"
|
||||
(:require [clojure.string :as string]
|
||||
[logseq.common.util :as common-util]))
|
||||
|
||||
;; Only used by DB graphs
|
||||
(defonce parent-char "/")
|
||||
(defonce parent-re #"/")
|
||||
;; Used by DB and file graphs
|
||||
(defonce namespace-char "/")
|
||||
|
||||
(defn namespace-page?
|
||||
"Used by DB and file graphs"
|
||||
[page-name]
|
||||
(and (string? page-name)
|
||||
(string/includes? page-name namespace-char)
|
||||
(not= (string/trim page-name) namespace-char)
|
||||
(not (string/starts-with? page-name "../"))
|
||||
(not (string/starts-with? page-name "./"))
|
||||
(not (common-util/url? page-name))))
|
||||
|
||||
(defn get-last-part
|
||||
"Get last part of a namespace page"
|
||||
[page-name]
|
||||
(if (namespace-page? page-name)
|
||||
(last (string/split page-name parent-char))
|
||||
page-name))
|
||||
53
deps/common/src/logseq/common/util/page_ref.cljs
vendored
Normal file
53
deps/common/src/logseq/common/util/page_ref.cljs
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
(ns logseq.common.util.page-ref
|
||||
"Core vars and util fns for page-ref. Currently this only handles a logseq
|
||||
page-ref e.g. [[page name]]"
|
||||
(:require ["path" :as node-path]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(def left-brackets "Opening characters for page-ref" "[[")
|
||||
(def right-brackets "Closing characters for page-ref" "]]")
|
||||
(def left-and-right-brackets "Opening and closing characters for page-ref"
|
||||
(str left-brackets right-brackets))
|
||||
|
||||
;; common regular expressions
|
||||
(def page-ref-re "Inner capture and doesn't match nested brackets" #"\[\[(.*?)\]\]")
|
||||
(def page-ref-without-nested-re "Matches most inner nested brackets" #"\[\[([^\[\]]+)\]\]")
|
||||
(def page-ref-any-re "Inner capture that matches anything between brackets" #"\[\[(.*)\]\]")
|
||||
(def markdown-page-ref-re #"\[(.*)\]\(file:.*\)")
|
||||
|
||||
(defn get-file-basename
|
||||
"Returns the basename of a file path. e.g. /a/b/c.md -> c.md"
|
||||
[path]
|
||||
(when-not (string/blank? path)
|
||||
(.-base (node-path/parse (string/replace path "+" "/")))))
|
||||
|
||||
(defn page-ref?
|
||||
"Determines if string is page-ref. Avoid using with format-specific page-refs e.g. org"
|
||||
[s]
|
||||
(and (string/starts-with? s left-brackets)
|
||||
(string/ends-with? s right-brackets)))
|
||||
|
||||
(defn ->page-ref
|
||||
"Create a page ref given a page name"
|
||||
[page-name]
|
||||
(str left-brackets page-name right-brackets))
|
||||
|
||||
(defn get-page-name
|
||||
"Extracts page names from md page-refs or logseq page-refs. Only call in
|
||||
contexts where md page-refs are used"
|
||||
[s]
|
||||
(and (string? s)
|
||||
(or (when-let [[_ label _path] (re-matches markdown-page-ref-re s)]
|
||||
(string/trim label))
|
||||
(-> (re-matches page-ref-any-re s)
|
||||
second))))
|
||||
|
||||
(defn get-page-name!
|
||||
"Extracts page-name from page-ref and fall back to arg. Useful for when user
|
||||
input may (not) be a page-ref"
|
||||
[s]
|
||||
(or (get-page-name s) s))
|
||||
|
||||
(defn page-ref-un-brackets!
|
||||
[s]
|
||||
(or (get-page-name s) s))
|
||||
66
deps/common/src/logseq/common/uuid.cljs
vendored
Normal file
66
deps/common/src/logseq/common/uuid.cljs
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
(ns logseq.common.uuid
|
||||
"uuid generators"
|
||||
(:require [datascript.core :as d]))
|
||||
|
||||
(defn- gen-journal-page-uuid
|
||||
"00000001-2024-0620-0000-000000000000
|
||||
first 8 chars as type, currently only '00000001' for journal-day-page.
|
||||
the remaining chars for data of this type"
|
||||
[journal-day]
|
||||
{:pre [(pos-int? journal-day)
|
||||
(> 1 (/ journal-day 100000000))]}
|
||||
(let [journal-day-str (str journal-day)
|
||||
part1 (subs journal-day-str 0 4)
|
||||
part2 (subs journal-day-str 4 8)]
|
||||
(uuid (str "00000001" "-" part1 "-" part2 "-0000-000000000000"))))
|
||||
|
||||
(defn- fill-with-0
|
||||
[s length]
|
||||
(let [s-length (count s)]
|
||||
(apply str s (repeat (- length s-length) "0"))))
|
||||
|
||||
(defn- gen-block-uuid
|
||||
"prefix-<hash-of-db-ident>-<padding-with-0>"
|
||||
[k prefix]
|
||||
(let [hash-num (str (Math/abs (hash k)))
|
||||
part1 (fill-with-0 (subs hash-num 0 4) 4)
|
||||
part2 (fill-with-0 (subs hash-num 4 8) 4)
|
||||
part3 (fill-with-0 (subs hash-num 8 12) 4)
|
||||
part4 (fill-with-0 (subs hash-num 12) 12)]
|
||||
(uuid (str prefix "-" part1 "-" part2 "-" part3 "-" part4))))
|
||||
|
||||
(defn- gen-db-ident-block-uuid
|
||||
"00000002-<hash-of-db-ident>-<padding-with-0>"
|
||||
[db-ident]
|
||||
{:pre [(keyword? db-ident)]}
|
||||
(gen-block-uuid db-ident "00000002"))
|
||||
|
||||
(defn gen-uuid
|
||||
"supported type:
|
||||
- :journal-page-uuid, 00000001
|
||||
- :db-ident-block-uuid, 00000002
|
||||
- :migrate-new-block-uuid, 00000003
|
||||
- :builtin-block-uuid, 00000004
|
||||
- :view-block-uuid, 00000006"
|
||||
([] (d/squuid))
|
||||
([type' v]
|
||||
(assert (some? v))
|
||||
(case type'
|
||||
:journal-page-uuid (gen-journal-page-uuid v)
|
||||
:db-ident-block-uuid (gen-db-ident-block-uuid v)
|
||||
:migrate-new-block-uuid (gen-block-uuid v "00000003")
|
||||
:builtin-block-uuid (gen-block-uuid v "00000004")
|
||||
:view-block-uuid (gen-block-uuid v "00000006"))))
|
||||
|
||||
(defn gen-journal-template-block
|
||||
"Persistent uuid for journal template block"
|
||||
[journal-uuid template-block-uuid]
|
||||
(assert (uuid? journal-uuid) (str journal-uuid))
|
||||
(assert (uuid? template-block-uuid) (str template-block-uuid))
|
||||
(uuid
|
||||
(str "00000005"
|
||||
"-"
|
||||
;; journal day
|
||||
(subs (str journal-uuid) 9 23)
|
||||
;; template block uuid
|
||||
(subs (str template-block-uuid) 23))))
|
||||
18
deps/common/src/logseq/common/version.cljs
vendored
Normal file
18
deps/common/src/logseq/common/version.cljs
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
(ns logseq.common.version
|
||||
"Version metadata helpers for build time and revision strings.")
|
||||
|
||||
(goog-define BUILD_TIME "unknown")
|
||||
(goog-define REVISION "dev")
|
||||
|
||||
(defn build-time
|
||||
[]
|
||||
BUILD_TIME)
|
||||
|
||||
(defn revision
|
||||
[]
|
||||
REVISION)
|
||||
|
||||
(defn format-version
|
||||
[]
|
||||
(str "Build time: " (build-time) "\n"
|
||||
"Revision: " (revision)))
|
||||
34
deps/common/test/logseq/common/config_test.cljs
vendored
Normal file
34
deps/common/test/logseq/common/config_test.cljs
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
(ns logseq.common.config-test
|
||||
(:require [cljs.test :refer [deftest is testing]]
|
||||
[logseq.common.config :as common-config]))
|
||||
|
||||
(deftest remove-hidden-files
|
||||
(let [files ["pages/foo.md" "pages/bar.md"
|
||||
"script/README.md" "script/config.edn"
|
||||
"dev/README.md" "dev/config.edn"]]
|
||||
(is (= ["pages/foo.md" "pages/bar.md"]
|
||||
(common-config/remove-hidden-files
|
||||
files
|
||||
{:hidden ["script" "/dev"]}
|
||||
identity))
|
||||
"Removes hidden relative files")
|
||||
|
||||
(is (= ["/pages/foo.md" "/pages/bar.md"]
|
||||
(common-config/remove-hidden-files
|
||||
(map #(str "/" %) files)
|
||||
{:hidden ["script" "/dev"]}
|
||||
identity))
|
||||
"Removes hidden files if they start with '/'")))
|
||||
|
||||
(deftest local-relative-asset?
|
||||
(testing "local-relative-asset?"
|
||||
(is (common-config/local-relative-asset? "assets/test.png"))
|
||||
(is (common-config/local-relative-asset? "../assets/test.png"))
|
||||
(is (common-config/local-relative-asset? "./assets/test.png"))
|
||||
(is (not (common-config/local-relative-asset? "assets://")))
|
||||
(is (not (common-config/local-relative-asset? "assets://test.png")))
|
||||
(is (not (common-config/local-relative-asset? "http://assets/test.png")))
|
||||
(is (not (common-config/local-relative-asset? "file://assets/test.png")))
|
||||
(is (not (common-config/local-relative-asset? nil))))
|
||||
(testing "Windows backslash paths"
|
||||
(is (common-config/local-relative-asset? "assets\\test.png"))))
|
||||
50
deps/common/test/logseq/common/graph_test.cljs
vendored
Normal file
50
deps/common/test/logseq/common/graph_test.cljs
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
(ns logseq.common.graph-test
|
||||
(:require [logseq.common.graph :as common-graph]
|
||||
[cljs.test :refer [deftest is use-fixtures async]]
|
||||
["fs" :as fs]
|
||||
["path" :as node-path]))
|
||||
|
||||
(use-fixtures
|
||||
:each
|
||||
;; Cleaning tmp/ before leaves last tmp/ after a test run for dev and debugging
|
||||
{:before
|
||||
#(async done
|
||||
(if (fs/existsSync "tmp")
|
||||
(fs/rm "tmp" #js {:recursive true} (fn [err]
|
||||
(when err (js/console.log err))
|
||||
(done)))
|
||||
(done)))})
|
||||
|
||||
(defn- create-logseq-graph
|
||||
"Creates a minimal mock graph"
|
||||
[dir]
|
||||
(fs/mkdirSync (node-path/join dir "logseq") #js {:recursive true})
|
||||
(fs/mkdirSync (node-path/join dir "journals"))
|
||||
(fs/mkdirSync (node-path/join dir "pages")))
|
||||
|
||||
(deftest get-files
|
||||
(create-logseq-graph "tmp/test-graph")
|
||||
;; Create files that are recognized
|
||||
(fs/writeFileSync "tmp/test-graph/pages/foo.md" "")
|
||||
(fs/writeFileSync "tmp/test-graph/journals/2023_05_09.md" "")
|
||||
(fs/mkdirSync (node-path/join "tmp/test-graph" "mirror" "markdown-notes") #js {:recursive true})
|
||||
(fs/mkdirSync (node-path/join "tmp/test-graph" "mirror" "markdown2") #js {:recursive true})
|
||||
(fs/writeFileSync "tmp/test-graph/mirror/markdown-notes/foo.md" "")
|
||||
(fs/writeFileSync "tmp/test-graph/mirror/markdown2/foo.md" "")
|
||||
;; Create files that are ignored
|
||||
(fs/mkdirSync (node-path/join "tmp/test-graph" "logseq" "bak"))
|
||||
(fs/mkdirSync (node-path/join "tmp/test-graph" "mirror" "markdown" "pages") #js {:recursive true})
|
||||
(fs/writeFileSync "tmp/test-graph/logseq/bak/baz.md" "")
|
||||
(fs/writeFileSync "tmp/test-graph/logseq/.gitignore" "")
|
||||
(fs/writeFileSync "tmp/test-graph/mirror/markdown/pages/foo.md" "")
|
||||
(is (= ["tmp/test-graph/journals/2023_05_09.md"
|
||||
"tmp/test-graph/mirror/markdown-notes/foo.md"
|
||||
"tmp/test-graph/mirror/markdown2/foo.md"
|
||||
"tmp/test-graph/pages/foo.md"]
|
||||
(common-graph/get-files "tmp/test-graph"))))
|
||||
|
||||
(deftest ignored-markdown-mirror-path-honors-directory-boundary-test
|
||||
(is (common-graph/ignored-path? "tmp/test-graph" "tmp/test-graph/mirror/markdown"))
|
||||
(is (common-graph/ignored-path? "tmp/test-graph" "tmp/test-graph/mirror/markdown/pages/foo.md"))
|
||||
(is (not (common-graph/ignored-path? "tmp/test-graph" "tmp/test-graph/mirror/markdown-notes/foo.md")))
|
||||
(is (not (common-graph/ignored-path? "tmp/test-graph" "tmp/test-graph/mirror/markdown2/foo.md"))))
|
||||
73
deps/common/test/logseq/common/path_test.cljs
vendored
Normal file
73
deps/common/test/logseq/common/path_test.cljs
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
(ns logseq.common.path-test
|
||||
(:require [cljs.test :refer [deftest is testing]]
|
||||
[logseq.common.path :as path]))
|
||||
|
||||
(deftest filename
|
||||
(is (= nil (path/filename "/path/to/dir/")))
|
||||
(is (= "file-name" (path/filename "/path/to/dir/file-name")))
|
||||
(is (= "file-name" (path/filename "dir/file-name"))))
|
||||
|
||||
(deftest split-ext
|
||||
(is (= ["some-song" "mp3"] (path/split-ext "some-song.MP3")))
|
||||
(is (= ["some-song" ""] (path/split-ext "some-song")))
|
||||
(is (= ["some-file.edn" "txt"] (path/split-ext "some-file.edn.txt"))))
|
||||
|
||||
(deftest path-join
|
||||
(testing "path-join"
|
||||
(is (= "foo/bar" (path/path-join "foo" "bar")))
|
||||
(is (= "foo/bar" (path/path-join "foo/" "bar")))
|
||||
(is (= "foo/bar" (path/path-join nil "foo" "bar"))
|
||||
"global dir")
|
||||
(is (= "/foo/bar/baz/asdf" (path/path-join "/foo/bar//baz/asdf/quux/..")))
|
||||
(is (= "assets:///foo.bar/baz" (path/path-join "assets:///foo.bar" "baz")))
|
||||
(is (= "assets:///foo.bar/baz" (path/path-join "assets:///foo.bar/" "baz")))
|
||||
(is (= "file:///D:/a.txt" (path/path-join "file://" "D:/a.txt")))
|
||||
(is (= "file:///c:/x" (path/path-join "file://" "c:/x")))
|
||||
(is (= "//NAS/MyGraph/logseq/config.edn" (path/path-join "//NAS/MyGraph" "logseq/config.edn")))))
|
||||
|
||||
(deftest prepend-protocol
|
||||
(testing "prepend-protocol"
|
||||
(is (= "file:///home/logseq/graph" (path/prepend-protocol "file:" "/home/logseq/graph")))
|
||||
(is (= "file:///C:/Graph/pages" (path/prepend-protocol "file:" "C:/Graph/pages")))
|
||||
(is (= "file://NAS/MyGraph" (path/prepend-protocol "file:" "//NAS/MyGraph"))
|
||||
"Windows UNC URL")))
|
||||
|
||||
(deftest file-url-or-path->path
|
||||
(testing "file URL paths"
|
||||
(is (= "D:/a.txt" (path/file-url-or-path->path "file:///D:/a.txt")))
|
||||
(is (= "c:/x" (path/file-url-or-path->path "file:///c:/x")))
|
||||
(is (= "//nas/share/x" (path/file-url-or-path->path "file://NAS/share/x")))
|
||||
(is (= "D:/a.txt" (path/file-url-or-path->path "file:///D%3A/a.txt")))
|
||||
(is (= "/home/admin/a.txt" (path/file-url-or-path->path "file:///home/admin/a.txt")))
|
||||
(is (= "/D:/a.txt" (path/file-url-or-path->path "/D:/a.txt")))
|
||||
(is (= "/D:\\a.txt" (path/file-url-or-path->path "/D:\\a.txt")))
|
||||
(is (= "/home/admin/a.txt" (path/file-url-or-path->path "/home/admin/a.txt")))))
|
||||
|
||||
(deftest path-absolute
|
||||
(testing "absolute"
|
||||
(is (true? (path/absolute? "D:\\sources\\sources.md")))
|
||||
(is (true? (path/absolute? "/home/xxx/logseq/test.md")))
|
||||
(is (false? (path/absolute? "logseq/test.md")))
|
||||
(is (false? (path/absolute? "test.md")))
|
||||
(is (false? (path/absolute? "test")))
|
||||
(is (false? (path/absolute? "D:test.md")))))
|
||||
|
||||
(deftest protocol-url
|
||||
(testing "protocol url"
|
||||
(is (true? (path/protocol-url? "mailto:help@logseq.com")))
|
||||
(is (true? (path/protocol-url? "https://logseq.com")))
|
||||
(is (true? (path/protocol-url? "ftp://logseq.com")))
|
||||
(is (true? (path/protocol-url? "file:///home/xxx/logseq/test.md")))
|
||||
(is (true? (path/protocol-url? "assets:///home/xxx/logseq/test.md")))
|
||||
(is (false? (path/protocol-url? "logseq/test.md")))
|
||||
(is (false? (path/protocol-url? "test.md")))
|
||||
(is (false? (path/protocol-url? "test")))
|
||||
(is (false? (path/protocol-url? "D:test.md")))))
|
||||
|
||||
(deftest invalid-or-bare-protocol-urls
|
||||
(testing "invalid or bare protocol URLs do not crash"
|
||||
(is (string? (path/url-to-path "assets://")))
|
||||
(is (string? (path/url-to-path "file://")))
|
||||
(is (= "assets:///foo" (path/path-join "assets://" "foo")))
|
||||
(is (string? (path/path-join "file://" "assets://")))
|
||||
(is (string? (path/url-normalize "assets://")))))
|
||||
11
deps/common/test/logseq/common/util/page_ref_test.cljs
vendored
Normal file
11
deps/common/test/logseq/common/util/page_ref_test.cljs
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
(ns logseq.common.util.page-ref-test
|
||||
(:require [logseq.common.util.page-ref :as page-ref]
|
||||
[cljs.test :refer [are deftest]]))
|
||||
|
||||
(deftest page-ref?
|
||||
(are [x y] (= (page-ref/page-ref? x) y)
|
||||
"[[page]]" true
|
||||
"[[another page]]" true
|
||||
"[[some [[nested]] page]]" true
|
||||
"[single bracket]" false
|
||||
"no brackets" false))
|
||||
68
deps/common/test/logseq/common/util_test.cljs
vendored
Normal file
68
deps/common/test/logseq/common/util_test.cljs
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
(ns logseq.common.util-test
|
||||
(:require [clojure.test :refer [deftest are testing]]
|
||||
[logseq.common.util :as common-util]))
|
||||
|
||||
(deftest valid-edn-keyword?
|
||||
(are [x y]
|
||||
(= (common-util/valid-edn-keyword? x) y)
|
||||
|
||||
":foo-bar" true
|
||||
":foo!" true
|
||||
":foo,bar" false
|
||||
"4" false
|
||||
"foo bar" false
|
||||
"`property" false))
|
||||
|
||||
(deftest extract-file-extension?
|
||||
(are [x y]
|
||||
(= (common-util/path->file-ext x) y)
|
||||
"foo.bar" "bar"
|
||||
"foo" nil
|
||||
"foo.bar.baz" "baz"
|
||||
"../assets/audio.mp3" "mp3"
|
||||
;; From https://www.w3.org/TR/media-frags/
|
||||
"../assets/audio.mp3?t=10,20" "mp3"
|
||||
"../assets/audio.mp3?t=10,20#t=10" "mp3"
|
||||
"/root/Documents/audio.mp3" "mp3"
|
||||
"C:\\Users\\foo\\Documents\\audio.mp3" "mp3"
|
||||
"/root/Documents/audio" nil
|
||||
"/root/Documents/audio." nil
|
||||
"special/characters/aäääöüß.7z" "7z"
|
||||
"asldk lakls .lsad" "lsad"
|
||||
"中文asldk lakls .lsad" "lsad"))
|
||||
|
||||
(deftest url?
|
||||
(are [x y]
|
||||
(= (common-util/url? x) y)
|
||||
"http://logseq.com" true
|
||||
"prop:: value" false
|
||||
"a:" false))
|
||||
|
||||
(deftest escape-regex-chars
|
||||
(testing "ensure the result is a valid regex string"
|
||||
(are [x]
|
||||
(some? (re-find (re-pattern (common-util/escape-regex-chars x)) x))
|
||||
"[[page-name]]"
|
||||
"end-with-backslash\\"
|
||||
"\\[]{}().+*?|$^")))
|
||||
|
||||
(deftest safe-subs
|
||||
(testing "behaves like subs for in-range indices"
|
||||
(are [args y]
|
||||
(= (apply common-util/safe-subs args) y)
|
||||
["hello" 1 3] "el"
|
||||
["hello" 2] "llo"
|
||||
["hello" 0 5] "hello"))
|
||||
(testing "clamps out-of-range indices instead of throwing"
|
||||
(are [args y]
|
||||
(= (apply common-util/safe-subs args) y)
|
||||
["hello" 0 99] "hello"
|
||||
["hello" 99 99] ""
|
||||
["hello" 99] ""))
|
||||
(testing "returns \"\" for a non-string instead of throwing"
|
||||
(are [args y]
|
||||
(= (apply common-util/safe-subs args) y)
|
||||
[nil 0 0] ""
|
||||
[nil 0 5] ""
|
||||
[nil 0] ""
|
||||
[42 0 1] "")))
|
||||
Reference in New Issue
Block a user