Ver Fonte

Merge branch 'master' into refactor/core

Tienson Qin há 4 anos atrás
pai
commit
72f5a7c07b
57 ficheiros alterados com 797 adições e 566 exclusões
  1. 1 1
      .github/workflows/build.yml
  2. 15 0
      .prettierrc.js
  3. 11 41
      gulpfile.js
  4. 15 12
      package.json
  5. 4 2
      postcss.config.js
  6. 0 16
      resources/css/style.css
  7. 0 15
      resources/css/style.dev.css
  8. 0 25
      resources/dev.html
  9. 2 2
      resources/electron-dev.html
  10. 20 11
      resources/js/preload.js
  11. 2 1
      resources/package.json
  12. 42 2
      src/electron/electron/core.cljs
  13. 1 1
      src/electron/electron/handler.cljs
  14. 56 9
      src/main/frontend/components/block.cljs
  15. 4 0
      src/main/frontend/components/editor.cljs
  16. 30 0
      src/main/frontend/components/export.cljs
  17. 5 8
      src/main/frontend/components/header.cljs
  18. 0 1
      src/main/frontend/components/journal.cljs
  19. 3 3
      src/main/frontend/components/onboarding.cljs
  20. 34 61
      src/main/frontend/components/page.cljs
  21. 1 7
      src/main/frontend/components/repo.cljs
  22. 3 2
      src/main/frontend/components/right_sidebar.cljs
  23. 1 1
      src/main/frontend/components/search.cljs
  24. 7 1
      src/main/frontend/components/sidebar.cljs
  25. 8 1
      src/main/frontend/config.cljs
  26. 1 1
      src/main/frontend/db.cljs
  27. 2 1
      src/main/frontend/db/model.cljs
  28. 49 40
      src/main/frontend/db/query_dsl.cljs
  29. 1 1
      src/main/frontend/db/query_react.cljs
  30. 1 1
      src/main/frontend/db/react.cljs
  31. 3 0
      src/main/frontend/db/utils.cljs
  32. 10 5
      src/main/frontend/dicts.cljs
  33. 5 0
      src/main/frontend/extensions/code.cljs
  34. 4 6
      src/main/frontend/external/roam.cljc
  35. 6 4
      src/main/frontend/format.cljs
  36. 3 1
      src/main/frontend/format/adoc.cljs
  37. 14 7
      src/main/frontend/format/block.cljs
  38. 36 13
      src/main/frontend/format/mldoc.cljs
  39. 2 1
      src/main/frontend/format/protocol.cljs
  40. 1 1
      src/main/frontend/fs/nfs.cljs
  41. 1 1
      src/main/frontend/fs/node.cljs
  42. 4 3
      src/main/frontend/handler.cljs
  43. 41 0
      src/main/frontend/handler/editor.cljs
  44. 148 18
      src/main/frontend/handler/export.cljs
  45. 27 23
      src/main/frontend/handler/page.cljs
  46. 22 15
      src/main/frontend/handler/repo.cljs
  47. 3 4
      src/main/frontend/handler/route.cljs
  48. 1 1
      src/main/frontend/publishing/html.cljs
  49. 6 2
      src/main/frontend/search.cljs
  50. 5 0
      src/main/frontend/state.cljs
  51. 4 0
      src/main/frontend/text.cljs
  52. 1 1
      src/main/frontend/ui.css
  53. 14 0
      src/main/frontend/util.cljc
  54. 1 1
      src/main/frontend/version.cljs
  55. 18 0
      tailwind.all.css
  56. 0 3
      tailwind.css
  57. 98 189
      yarn.lock

+ 1 - 1
.github/workflows/build.yml

@@ -65,7 +65,7 @@ jobs:
           restore-keys: |
             ${{ runner.os }}-yarn-
       - name: Fetch yarn deps
-        run: yarn cache clean && yarn install --frozen-lockfile
+        run: yarn cache clean && yarn install
 
       - name: Run Clojure test
         run: |

+ 15 - 0
.prettierrc.js

@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+'use strict'
+
+module.exports = {
+  singleQuote: true,
+  trailingComma: 'es5',
+  semi: false,
+}

+ 11 - 41
gulpfile.js

@@ -1,11 +1,9 @@
 const fs = require('fs')
+const utils = require('util')
 const cp = require('child_process')
+const exec = utils.promisify(cp.exec)
 const path = require('path')
 const gulp = require('gulp')
-const postcss = require('gulp-postcss')
-const concat = require('gulp-concat')
-const cached = require('gulp-cached')
-const remember = require('gulp-remember')
 const cleanCSS = require('gulp-clean-css')
 const del = require('del')
 
@@ -14,51 +12,23 @@ const resourcesPath = path.join(__dirname, 'resources')
 const sourcePath = path.join(__dirname, 'src/main/frontend')
 const resourceFilePath = path.join(resourcesPath, '**')
 
-const tailwindCoreEntry = path.join(__dirname, 'tailwind.css')
-const tailwindBuildEntry = path.join(sourcePath, '**/*.css')
-const tailwind = {
-  paths: [tailwindCoreEntry, tailwindBuildEntry],
-  outputDir: path.join(outputPath, 'css'),
-  outputName: 'tailwind.build.css',
-}
-
 const css = {
-  async watchCSS () {
-    // remove tailwind core css
-    await new Promise((resolve) => {
-      css._buildTailwind(
-        tailwind.paths.shift(),
-        'tailwind.core.css'
-      )
-        .on('end', resolve)
-    })
-
-    return gulp.watch(
-      tailwind.paths, { ignoreInitial: false },
-      css._buildTailwind.bind(null, void 0, void 0))
+  watchCSS () {
+    return exec(`yarn css:watch`, {})
   },
 
   buildCSS (...params) {
     return gulp.series(
-      css._buildTailwind.bind(null, tailwindCoreEntry, 'tailwind.core.css'),
-      css._buildTailwind.bind(null, tailwindBuildEntry, 'tailwind.build.css'),
-      css._optimizeCSSForRelease)(...params)
-  },
-
-  _buildTailwind (entry, output) {
-    return gulp.src(entry || tailwind.paths)
-      .pipe(cached('postcss-' + entry))
-      .pipe(postcss())
-      .pipe(remember('postcss-' + entry))
-      .pipe(concat(output || tailwind.outputName))
-      .pipe(gulp.dest(tailwind.outputDir))
+      () => exec(`yarn css:build`, {}),
+      css._optimizeCSSForRelease
+    )(...params)
   },
 
   _optimizeCSSForRelease () {
     return gulp.src(path.join(outputPath, 'css', 'style.css'))
       .pipe(cleanCSS())
       .pipe(gulp.dest(path.join(outputPath, 'css')))
-  },
+  }
 }
 
 const common = {
@@ -71,7 +41,7 @@ const common = {
   },
 
   keepSyncResourceFile () {
-    return gulp.watch(resourceFilePath, { ignoreInitial: false }, common.syncResourceFile)
+    return gulp.watch(resourceFilePath, { ignoreInitial: true }, common.syncResourceFile)
   }
 }
 
@@ -90,7 +60,7 @@ exports.electron = () => {
 }
 
 exports.electronMaker = async () => {
-  cp.execSync('yarn cljs:electron-release', {
+  cp.execSync('yarn cljs:release', {
     stdio: 'inherit'
   })
 
@@ -120,5 +90,5 @@ exports.electronMaker = async () => {
 }
 
 exports.clean = common.clean
-exports.watch = gulp.parallel(common.keepSyncResourceFile, css.watchCSS)
+exports.watch = gulp.series(common.syncResourceFile, gulp.parallel(common.keepSyncResourceFile, css.watchCSS))
 exports.build = gulp.series(common.clean, common.syncResourceFile, css.buildCSS)

+ 15 - 12
package.json

@@ -4,6 +4,7 @@
     "private": true,
     "main": "static/electron.js",
     "devDependencies": {
+        "@tailwindcss/jit": "^0.1.1",
         "@tailwindcss/ui": "0.7.2",
         "@types/gulp": "^4.0.7",
         "cross-env": "^7.0.3",
@@ -11,29 +12,28 @@
         "del": "^6.0.0",
         "gulp": "^4.0.2",
         "gulp-clean-css": "^4.3.0",
-        "gulp-concat": "^2.6.1",
-        "gulp-postcss": "^9.0.0",
-        "gulp-remember": "^1.0.1",
         "npm-run-all": "^4.1.5",
-        "postcss": "8.2.1",
-        "postcss-cli": "8.3.0",
-        "postcss-nested": "^5.0.1",
-        "purgecss": "3.0.0",
+        "postcss": "8.2.8",
+        "postcss-cli": "8.3.1",
+        "postcss-import": "^14.0.0",
+        "postcss-import-ext-glob": "^2.0.1",
+        "postcss-nested": "5.0.5",
+        "purgecss": "4.0.2",
         "shadow-cljs": "2.11.11",
         "stylelint": "^13.8.0",
         "stylelint-config-standard": "^20.0.0",
-        "tailwindcss": "2.0.1"
+        "tailwindcss": "2.0.3"
     },
     "scripts": {
-        "watch": "run-p gulp:build gulp:watch cljs:watch",
-        "electron-watch": "run-p gulp:build gulp:watch cljs:electron-watch",
+        "watch": "run-p gulp:watch cljs:watch",
+        "electron-watch": "run-p gulp:watch cljs:electron-watch",
         "release": "run-s gulp:build cljs:release",
         "watch-app": "run-p gulp:watch cljs:watch-app",
         "release-app": "run-s gulp:build cljs:release-app",
         "release-publishing": "run-s gulp:build cljs:release-publishing",
         "dev-release-app": "run-s gulp:build cljs:dev-release-app",
         "dev-electron-app": "gulp electron",
-        "release-electron": "gulp build && gulp electronMaker",
+        "release-electron": "run-s gulp:build && gulp electronMaker",
         "debug-electron": "cd static/ && yarn electron:debug",
         "clean": "gulp clean",
         "test": "run-s cljs:test cljs:run-test",
@@ -41,6 +41,8 @@
         "style:lint": "stylelint \"src/**/*.css\" ",
         "gulp:watch": "gulp watch",
         "gulp:build": "cross-env NODE_ENV=production gulp build",
+        "css:build": "postcss tailwind.all.css -o static/css/style.css --verbose --env production",
+        "css:watch": "postcss tailwind.all.css -o static/css/style.css --verbose --watch",
         "cljs:watch": "clojure -M:cljs watch app publishing electron",
         "cljs:electron-watch": "clojure -M:cljs watch app electron",
         "cljs:release": "clojure -M:cljs release app publishing electron",
@@ -65,11 +67,12 @@
         "diff-match-patch": "^1.0.5",
         "electron": "^11.2.0",
         "fs": "^0.0.1-security",
+        "fs-extra": "^9.1.0",
         "fuse.js": "^6.4.6",
         "gulp-cached": "^1.1.1",
         "ignore": "^5.1.8",
         "jszip": "^3.5.0",
-        "mldoc": "^0.5.0",
+        "mldoc": "0.5.5",
         "mousetrap": "^1.6.5",
         "path": "^0.12.7",
         "react": "^17.0.1",

+ 4 - 2
postcss.config.js

@@ -1,6 +1,8 @@
 module.exports = (ctx) => ({
   plugins: [
-    require('postcss-nested'),
-    require('tailwindcss')('tailwind.config.js'),
+    require('postcss-nested')({}),
+    require('postcss-import-ext-glob')({}),
+    require('postcss-import')({}),
+    require('@tailwindcss/jit')('tailwind.config.js'),
   ],
 })

+ 0 - 16
resources/css/style.css

@@ -1,16 +0,0 @@
-@charset "utf-8";
-@import "./inter.css";
-@import "./reveal.min.css";
-@import "./reveal_black.min.css";
-@import "./fonts.css";
-@import "./excalidraw.min.css";
-@import "./katex.min.css";
-@import "./codemirror.min.css";
-@import "./animation.css";
-@import "./table.css";
-@import "./datepicker.css";
-@import "./highlight.css";
-@import "./tailwind.core.css"; /* Build by gulp. Check `_buildTailwind` for more detail */
-@import "./tooltip.css";
-@import "./common.css";
-@import "./tailwind.build.css"; /* Build by gulp. Check `_buildTailwind` for more detail */

+ 0 - 15
resources/css/style.dev.css

@@ -1,15 +0,0 @@
-@charset "utf-8";
-@import "./inter.css";
-@import "./reveal.min.css";
-@import "./reveal_black.min.css";
-@import "./fonts.css";
-@import "./excalidraw.min.css";
-@import "./katex.min.css";
-@import "./codemirror.min.css";
-@import "./animation.css";
-@import "./table.css";
-@import "./datepicker.css";
-@import "./highlight.css";
-@import "./tooltip.css";
-@import "./tailwind.core.css"; /* Build by gulp. Check `_buildTailwind` for more detail */
-@import "./common.css";

+ 0 - 25
resources/dev.html

@@ -1,25 +0,0 @@
-<!doctype html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta name="viewport"
-        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
-  <meta http-equiv="X-UA-Compatible" content="ie=edge">
-  <title>Electron Development Entries</title>
-</head>
-<body>
-<div style="padding: 50px; text-align: center;">
-  <h1>
-    Development Mode :)
-  </h1>
-  <h3>
-    <a href="http://localhost:3000">
-      http://localhost:3000
-    </a> <br> <br>
-    <a href="http://localhost:3001">
-      http://localhost:3001
-    </a>
-  </h3>
-</div>
-</body>
-</html>

+ 2 - 2
resources/electron-dev.html

@@ -3,8 +3,7 @@
 <head>
   <meta charset="utf-8">
   <meta content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no" name="viewport">
-  <link href="./css/style.dev.css" rel="stylesheet" type="text/css">
-  <link href="./css/tailwind.build.css" rel="stylesheet" type="text/css">
+  <link href="./css/style.css" rel="stylesheet" type="text/css">
   <link href="./img/logo.png" rel="shortcut icon" type="image/png">
   <link href="./img/logo.png" rel="shortcut icon" sizes="192x192">
   <link href="./img/logo.png" rel="apple-touch-icon">
@@ -27,6 +26,7 @@
   <title>Logseq: A local-first knowledge base</title>
   <meta content="logseq" property="og:site_name">
   <meta content="A local-first knowledge base which can be synced using Git." name="description">
+  <script>window.localStorage.getItem('http-entry-port') && (location.href = 'http://localhost:' + window.localStorage.getItem('http-entry-port'))</script>
 </head>
 <body>
 <div id="root">

+ 20 - 11
resources/js/preload.js

@@ -5,7 +5,7 @@ const { ipcRenderer, contextBridge, shell, clipboard } = require('electron')
 const IS_MAC = process.platform === 'darwin'
 const IS_WIN32 = process.platform === 'win32'
 
-function getFilePathFromClipboard () {
+function getFilePathFromClipboard() {
   if (IS_WIN32) {
     const rawFilePath = clipboard.read('FileNameW')
     return rawFilePath.replace(new RegExp(String.fromCharCode(0), 'g'), '')
@@ -16,7 +16,7 @@ function getFilePathFromClipboard () {
   }
 }
 
-function isClipboardHasImage () {
+function isClipboardHasImage() {
   return !clipboard.readImage().isEmpty()
 }
 
@@ -34,7 +34,7 @@ contextBridge.exposeInMainWorld('apis', {
     await ipcRenderer.invoke('check-for-updates', ...args)
   },
 
-  setUpdatesCallback (cb) {
+  setUpdatesCallback(cb) {
     if (typeof cb !== 'function') return
 
     const channel = 'updates-callback'
@@ -42,19 +42,19 @@ contextBridge.exposeInMainWorld('apis', {
     ipcRenderer.on(channel, cb)
   },
 
-  installUpdatesAndQuitApp () {
+  installUpdatesAndQuitApp() {
     ipcRenderer.invoke('install-updates', true)
   },
 
-  async openExternal (url, options) {
+  async openExternal(url, options) {
     await shell.openExternal(url, options)
   },
 
-  async openPath (path) {
+  async openPath(path) {
     await shell.openPath(path)
   },
 
-  showItemInFolder (fullpath) {
+  showItemInFolder(fullpath) {
     if (IS_WIN32) {
       shell.openPath(path.dirname(fullpath))
     } else {
@@ -62,6 +62,15 @@ contextBridge.exposeInMainWorld('apis', {
     }
   },
 
+  /**
+   * save all publish assets to disk
+   *
+   * @param {string} html html file with embedded state
+   */
+  exportPublishAssets(html) {
+    ipcRenderer.invoke('export-publish-assets', html)
+  },
+
   /**
    * When from is empty. The resource maybe from
    * client paste or screenshoot.
@@ -70,7 +79,7 @@ contextBridge.exposeInMainWorld('apis', {
    * @param from?
    * @returns {Promise<void>}
    */
-  async copyFileToAssets (repoPathRoot, to, from) {
+  async copyFileToAssets(repoPathRoot, to, from) {
     if (from && fs.statSync(from).isDirectory()) {
       throw new Error('not support copy directory')
     }
@@ -105,7 +114,7 @@ contextBridge.exposeInMainWorld('apis', {
     }
   },
 
-  toggleMaxOrMinActiveWindow (isToggleMin = false) {
+  toggleMaxOrMinActiveWindow(isToggleMin = false) {
     ipcRenderer.invoke('toggle-max-or-min-active-win', isToggleMin)
   },
 
@@ -115,10 +124,10 @@ contextBridge.exposeInMainWorld('apis', {
    * @param args
    * @private
    */
-  async _callApplication (type, ...args) {
+  async _callApplication(type, ...args) {
     return await ipcRenderer.invoke('call-application', type, ...args)
   },
 
   getFilePathFromClipboard,
-  isClipboardHasImage
+  isClipboardHasImage,
 })

+ 2 - 1
resources/package.json

@@ -19,7 +19,8 @@
     "update-electron-app": "^2.0.1",
     "node-fetch": "^2.6.1",
     "open": "^7.3.1",
-    "chokidar": "^3.5.1"
+    "chokidar": "^3.5.1",
+    "fs-extra": "^9.1.0"
   },
   "devDependencies": {
     "@electron-forge/cli": "^6.0.0-beta.54",

+ 42 - 2
src/electron/electron/core.cljs

@@ -3,9 +3,10 @@
             [electron.updater :refer [init-updater]]
             [electron.utils :refer [mac? win32? prod? dev? logger open]]
             [clojure.string :as string]
-            ["fs" :as fs]
+            [promesa.core :as p]
+            ["fs-extra" :as fs]
             ["path" :as path]
-            ["electron" :refer [BrowserWindow app protocol ipcMain] :as electron]
+            ["electron" :refer [BrowserWindow app protocol ipcMain dialog] :as electron]
             [clojure.core.async :as async]
             [electron.state :as state]))
 
@@ -54,10 +55,44 @@
        (callback #js {:path path}))))
   #(.unregisterProtocol protocol "assets"))
 
+(defn- handle-export-publish-assets [_event html]
+  (let [app-path (. app getAppPath)
+        paths (js->clj (. dialog showOpenDialogSync (clj->js {:properties ["openDirectory" "createDirectory" "promptToCreate", "multiSelections"]})))]
+    (when (seq paths)
+      (let [root-dir (first paths)
+            static-dir (path/join root-dir "static")
+            path (path/join root-dir "index.html")]
+        (p/let [_ (. fs ensureDir static-dir)
+                _ (p/all  (concat
+                           [(. fs writeFile path html)
+                            (. fs copy (path/join app-path "404.html") (path/join root-dir "404.html"))]
+
+                           (map
+                             (fn [part]
+                               (. fs copy (path/join app-path part) (path/join static-dir part)))
+                             ["css" "fonts" "icons" "img" "js"])))
+                js-files ["main.js" "code-editor.js" "excalidraw.js"]
+                _ (p/all (map (fn [file]
+                                (. fs removeSync (path/join static-dir "js" file)))
+                           js-files))
+                _ (p/all (map (fn [file]
+                                (. fs moveSync
+                                   (path/join static-dir "js" "publishing" file)
+                                   (path/join static-dir "js" file)))
+                           js-files))
+                _ (. fs removeSync (path/join static-dir "js" "publishing"))
+                ;; remove source map files
+                ;; TODO: ugly, replace with ls-files and filter with ".map"
+                _ (p/all (map (fn [file]
+                                (. fs removeSync (path/join static-dir "js" (str file ".map"))))
+                           ["main.js" "code-editor.js" "excalidraw.js" "age-encryption.js"]))]
+          (. dialog showMessageBox (clj->js {:message (str "Export publish assets to " root-dir " successfully")})))))))
+
 (defn setup-app-manager!
   [^js win]
   (let [toggle-win-channel "toggle-max-or-min-active-win"
         call-app-channel "call-application"
+        export-publish-assets "export-publish-assets"
         web-contents (. win -webContents)]
     (doto ipcMain
       (.handle toggle-win-channel
@@ -70,6 +105,9 @@
                      (if (.isMaximized active-win)
                        (.unmaximize active-win)
                        (.maximize active-win))))))
+
+      (.handle export-publish-assets handle-export-publish-assets)
+
       (.handle call-app-channel
                (fn [_ type & args]
                  (try
@@ -91,10 +129,12 @@
       (.on "leave-full-screen" #(.send web-contents "full-screen" "leave")))
 
     #(do (.removeHandler ipcMain toggle-win-channel)
+         (.removeHandler ipcMain export-publish-assets)
          (.removeHandler ipcMain call-app-channel))))
 
 (defonce *win (atom nil))
 
+
 (defn- destroy-window!
   [^js win]
   (.destroy win))

+ 1 - 1
src/electron/electron/handler.cljs

@@ -71,7 +71,7 @@
 ;; TODO: Is it going to be slow if it's a huge directory
 (defmethod handle :openDir [^js window _messages]
   (let [result (.showOpenDialogSync dialog (bean/->js
-                                            {:properties ["openDirectory"]}))
+                                            {:properties ["openDirectory" "createDirectory" "promptToCreate"]}))
         path (first result)]
     (.. ^js window -webContents
         (send "open-dir-confirmed"

+ 56 - 9
src/main/frontend/components/block.cljs

@@ -48,7 +48,8 @@
             [lambdaisland.glogi :as log]
             [frontend.context.i18n :as i18n]
             [frontend.template :as template]
-            [shadow.loader :as loader]))
+            [shadow.loader :as loader]
+            [frontend.search :as search]))
 
 ;; TODO: remove rum/with-context because it'll make reactive queries not working
 
@@ -638,7 +639,7 @@
           [:span.warning {:title "Invalid link"} full_text]
 
           ;; image
-          (some (fn [fmt] (re-find (re-pattern (str "(?i)\\." fmt)) s)) img-formats)
+          (text/image-link? img-formats s)
           (image-link config url s label metadata full_text)
 
           (= \# (first s))
@@ -676,7 +677,7 @@
             (block-reference config (:link (second url)))
 
             (= protocol "file")
-            (if (some (fn [fmt] (re-find (re-pattern (str "(?i)\\." fmt)) href)) img-formats)
+            (if (text/image-link? img-formats href)
               (image-link config url href label metadata full_text)
               (let [label-text (get-label-text label)
                     page (if (string/blank? label-text)
@@ -701,7 +702,7 @@
                    (map-inline config label)))))
 
             ;; image
-            (some (fn [fmt] (re-find (re-pattern (str "(?i)\\." fmt)) href)) img-formats)
+            (text/image-link? img-formats href)
             (image-link config url href label metadata full_text)
 
             :else
@@ -803,7 +804,7 @@
                   [:iframe
                    {:allow-full-screen "allowfullscreen"
                     :allow
-                    "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
+                    "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope"
                     :frame-border "0"
                     :src (str "https://www.youtube.com/embed/" youtube-id)
                     :height height
@@ -820,7 +821,7 @@
                   [:iframe
                    {:allow-full-screen "allowfullscreen"
                     :allow
-                    "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
+                    "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope"
                     :frame-border "0"
                     :src (str "https://player.vimeo.com/video/" vimeo-id)
                     :height height
@@ -889,13 +890,50 @@
                                    (get name))
                                (get (state/get-macros) name)
                                (get (state/get-macros) (keyword name)))
-                macro-content (if (and (seq arguments) macro-content)
+                macro-content (cond
+                                (= (str name) "img")
+                                (case (count arguments)
+                                  1
+                                  (util/format "[:img {:src \"%s\"}]" (first arguments))
+                                  4
+                                  (if (and (util/safe-parse-int (nth arguments 1))
+                                           (util/safe-parse-int (nth arguments 2)))
+                                    (util/format "[:img.%s {:src \"%s\" :style {:width %s :height %s}}]"
+                                                 (nth arguments 3)
+                                                 (first arguments)
+                                                 (util/safe-parse-int (nth arguments 1))
+                                                 (util/safe-parse-int (nth arguments 2))))
+                                  3
+                                  (if (and (util/safe-parse-int (nth arguments 1))
+                                           (util/safe-parse-int (nth arguments 2)))
+                                    (util/format "[:img {:src \"%s\" :style {:width %s :height %s}}]"
+                                                 (first arguments)
+                                                 (util/safe-parse-int (nth arguments 1))
+                                                 (util/safe-parse-int (nth arguments 2))))
+
+                                  2
+                                  (cond
+                                    (and (util/safe-parse-int (nth arguments 1)))
+                                    (util/format "[:img {:src \"%s\" :style {:width %s}}]"
+                                                 (first arguments)
+                                                 (util/safe-parse-int (nth arguments 1)))
+                                    (contains? #{"left" "right" "center"} (string/lower-case (nth arguments 1)))
+                                    (util/format "[:img.%s {:src \"%s\"}]"
+                                                 (string/lower-case (nth arguments 1))
+                                                 (first arguments))
+                                    :else
+                                    macro-content)
+
+                                  macro-content)
+
+                                (and (seq arguments) macro-content)
                                 (block/macro-subs macro-content arguments)
+
+                                :else
                                 macro-content)
                 macro-content (when macro-content
                                 (template/resolve-dynamic-template! macro-content))]
             (render-macro config name arguments macro-content format))
-
           (when-let [macro-txt (macro->text name arguments)]
             (let [macro-txt (when macro-txt
                               (template/resolve-dynamic-template! macro-txt))
@@ -1747,7 +1785,15 @@
   [state]
   (let [[config query] (:rum/args state)
         query-atom (if (:dsl-query? config)
-                     (query-dsl/query (state/get-current-repo) (:query query))
+                     (let [result (query-dsl/query (state/get-current-repo) (:query query))]
+                       (if (string? result) ; full-text search
+                         (atom
+                          (if (string/blank? result)
+                            []
+                            (let [blocks (search/block-search result 50)]
+                              (when (seq blocks)
+                                (db/pull-many (state/get-current-repo) '[*] (map (fn [b] [:block/uuid (uuid (:block/uuid b))]) blocks))))))
+                         result))
                      (db/custom-query query))]
     (assoc state :query-atom query-atom)))
 
@@ -2041,6 +2087,7 @@
                      (fn [acc block]
                        (let [block (dissoc block :block/meta)
                              level (:block/level block)
+                             config (assoc config :block/uuid (:block/uuid block))
                              block-cp (if build-block-fn
                                         (build-block-fn config block)
                                         (rum/with-key

+ 4 - 0
src/main/frontend/components/editor.cljs

@@ -321,6 +321,10 @@
     38 (editor-handler/keydown-up-down-handler input true)
     ;; down
     40 (editor-handler/keydown-up-down-handler input false)
+    ;; left
+    37 (editor-handler/keydown-arrow-handler input :left)
+    ;; right
+    39 (editor-handler/keydown-arrow-handler input :right)
     ;; backspace
     8 (editor-handler/keydown-backspace-handler repo input input-id)
     ;; tab

+ 30 - 0
src/main/frontend/components/export.cljs

@@ -0,0 +1,30 @@
+(ns frontend.components.export
+  (:require [rum.core :as rum]
+            [frontend.ui :as ui]
+            [frontend.util :as util]
+            [frontend.handler.export :as export]
+            [frontend.state :as state]
+            [frontend.context.i18n :as i18n]))
+
+(rum/defc export
+  []
+  (when-let [current-repo (state/get-current-repo)]
+    (rum/with-context [[t] i18n/*tongue-context*]
+      [:div.export.w-96
+       [:h1.title "Export"]
+
+       [:ul.mr-1
+        (when (util/electron?)
+          [:li.mb-4
+           [:a.font-medium {:on-click #(export/export-repo-as-html! current-repo)}
+            (t :export-public-pages)]])
+        [:li.mb-4
+         [:a.font-medium {:on-click #(export/export-repo-as-markdown! current-repo)}
+          (t :export-markdown)]]
+        [:li.mb-4
+         [:a.font-medium {:on-click #(export/export-repo-as-edn! current-repo)}
+          (t :export-edn)]]]
+       [:a#download-as-edn.hidden]
+       [:a#download-as-html.hidden]
+       [:a#download-as-zip.hidden]
+       [:a#export-as-markdown.hidden]])))

+ 5 - 8
src/main/frontend/components/header.cljs

@@ -11,10 +11,10 @@
             [frontend.context.i18n :as i18n]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.user :as user-handler]
-            [frontend.handler.export :as export]
             [frontend.components.svg :as svg]
             [frontend.components.repo :as repo]
             [frontend.components.search :as search]
+            [frontend.components.export :as export]
             [frontend.handler.project :as project-handler]
             [frontend.handler.page :as page-handler]
             [frontend.handler.web.nfs :as nfs]
@@ -128,9 +128,9 @@
 
        (when current-repo
          {:title (t :export)
-          :options {:on-click (fn []
-                                (export/export-repo-as-html! current-repo))}
+          :options {:on-click #(state/set-modal! export/export)}
           :icon nil})
+
        (when current-repo
          {:title (t :import)
           :options {:href (rfe/href :import)}
@@ -148,7 +148,7 @@
       (remove nil?))
      ;; {:links-footer (when (and (util/electron?) (not logged?))
      ;;                  [:div.px-2.py-2 (login logged?)])}
-)))
+     )))
 
 (rum/defc header
   < rum/reactive
@@ -212,7 +212,4 @@
        (dropdown-menu {:me me
                        :t t
                        :current-repo current-repo
-                       :default-home default-home})
-
-       [:a#download-as-html.hidden]
-       [:a#download-as-zip.hidden]])))
+                       :default-home default-home})])))

+ 0 - 1
src/main/frontend/components/journal.cljs

@@ -70,7 +70,6 @@
   (let [;; Don't edit the journal title
         page (string/lower-case title)
         repo (state/sub :git/current-repo)
-        encoded-page-name (util/encode-str page)
         today? (= (string/lower-case title)
                   (string/lower-case (date/journal-name)))
         intro? (and (not (state/logged?))

+ 3 - 3
src/main/frontend/components/onboarding.cljs

@@ -241,8 +241,8 @@
          [:tr [:td (t :help/new-line-in-block)] [:td "Shift-Enter"]]
          [:tr [:td (t :undo)] [:td (util/->platform-shortcut "Ctrl-z")]]
          [:tr [:td (t :redo)] [:td (util/->platform-shortcut "Ctrl-y")]]
-         [:tr [:td (t :help/zoom-in)] [:td (util/->platform-shortcut (if util/mac? "Alt-." "Alt-Right"))]]
-         [:tr [:td (t :help/zoom-out)] [:td (util/->platform-shortcut (if util/mac? "Alt-," "Alt-left"))]]
+         [:tr [:td (t :help/zoom-in)] [:td (util/->platform-shortcut (if util/mac? "Cmd-." "Alt-Right"))]]
+         [:tr [:td (t :help/zoom-out)] [:td (util/->platform-shortcut (if util/mac? "Cmd-," "Alt-left"))]]
          [:tr [:td (t :help/follow-link-under-cursor)] [:td (util/->platform-shortcut "Ctrl-o")]]
          [:tr [:td (t :help/open-link-in-sidebar)] [:td (util/->platform-shortcut "Ctrl-shift-o")]]
          [:tr [:td (t :expand)] [:td (util/->platform-shortcut "Ctrl-Down")]]
@@ -268,7 +268,7 @@
          [:tr [:td (t :help/toggle-right-sidebar)] [:td "t r"]]
          [:tr [:td (t :help/toggle-settings)] [:td "t s"]]
          [:tr [:td (t :help/toggle-insert-new-block)] [:td "t e"]]
-         [:tr [:td (t :help/jump-to-journals)] [:td (util/->platform-shortcut "Ctrl-j")]]]]
+         [:tr [:td (t :help/jump-to-journals)] [:td (if util/mac? "Cmd-j" "Alt-j")]]]]
        [:table
         [:thead
          [:tr

+ 34 - 61
src/main/frontend/components/page.cljs

@@ -173,14 +173,14 @@
 
 (rum/defcs rename-page-dialog-inner <
   (rum/local "" ::input)
-  [state page-name close-fn]
+  [state title page-name close-fn]
   (let [input (get state ::input)]
     (rum/with-context [[t] i18n/*tongue-context*]
       [:div.w-full.sm:max-w-lg.sm:w-96
        [:div.sm:flex.sm:items-start
         [:div.mt-3.text-center.sm:mt-0.sm:text-left
          [:h3#modal-headline.text-lg.leading-6.font-medium
-          (t :page/rename-to page-name)]]]
+          (t :page/rename-to title)]]]
 
        [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2
         {:auto-focus true
@@ -193,11 +193,10 @@
          [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
           {:type "button"
            :on-click (fn []
-                       (let [value @input]
-                         (let [value (string/trim value)]
-                           (when-not (string/blank? value)
-                             (page-handler/rename! page-name value)
-                             (state/close-modal!)))))}
+                       (let [value (string/trim @input)]
+                         (when-not (string/blank? value)
+                           (page-handler/rename! page-name value)
+                           (state/close-modal!))))}
           (t :submit)]]
         [:span.mt-3.flex.w-full.rounded-md.shadow-sm.sm:mt-0.sm:w-auto
          [:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
@@ -206,9 +205,9 @@
           (t :cancel)]]]])))
 
 (defn rename-page-dialog
-  [page-name]
+  [title page-name]
   (fn [close-fn]
-    (rename-page-dialog-inner page-name close-fn)))
+    (rename-page-dialog-inner title page-name close-fn)))
 
 (defn tagged-pages
   [repo tag]
@@ -235,9 +234,8 @@
   [state {:keys [repo] :as option}]
   (let [current-repo (state/sub :git/current-repo)
         repo (or repo current-repo)
-        encoded-page-name (or (get-page-name state)
-                              (state/get-current-page))
-        path-page-name (util/url-decode encoded-page-name)
+        path-page-name (or (get-page-name state)
+                           (state/get-current-page))
         page-name (string/lower-case path-page-name)
         marker-page? (util/marker? page-name)
         priority-page? (contains? #{"a" "b" "c"} page-name)
@@ -303,7 +301,7 @@
 
                             (when-not contents?
                               {:title (t :page/rename)
-                               :options {:on-click #(state/set-modal! (rename-page-dialog page-name))}})
+                               :options {:on-click #(state/set-modal! (rename-page-dialog title page-name))}})
 
                             (when (and file-path (util/electron?))
                               [{:title   (t :page/open-in-finder)
@@ -315,38 +313,14 @@
                               {:title (t :page/delete)
                                :options {:on-click #(state/set-modal! (delete-page-dialog page-name))}})
 
-                            {:title   (t :page/action-publish)
-                             :options {:on-click
-                                       (fn []
-                                         (state/set-modal!
-                                          (fn []
-                                            [:div.cp__page-publish-actions
-                                             (mapv (fn [{:keys [title options]}]
-                                                     (when title
-                                                       [:div.it
-                                                        (apply (partial ui/button title) (flatten (seq options)))]))
-                                                   [(if published?
-                                                      {:title   (t :page/unpublish)
-                                                       :options {:on-click (fn []
-                                                                             (page-handler/unpublish-page! page-name))}}
-                                                      {:title   (t :page/publish)
-                                                       :options {:on-click (fn []
-                                                                             (page-handler/publish-page!
-                                                                              page-name project/add-project
-                                                                              html-export/export-page))}})
-                                                    (when-not published?
-                                                      {:title   (t :page/publish-as-slide)
-                                                       :options {:on-click (fn []
-                                                                             (page-handler/publish-page-as-slide!
-                                                                              page-name project/add-project
-                                                                              html-export/export-page))}})
-                                                    {:title   (t (if public? :page/make-private :page/make-public))
-                                                     :options {:background (if public? "gray" "indigo")
-                                                               :on-click (fn []
-                                                                           (page-handler/update-public-attribute!
-                                                                            page-name
-                                                                            (if public? false true))
-                                                                           (state/close-modal!))}}])])))}}
+                            (when (util/electron?)
+                              {:title  (t (if public? :page/make-private :page/make-public))
+                               :options {:on-click
+                                         (fn []
+                                           (page-handler/update-public-attribute!
+                                            page-name
+                                            (if public? false true))
+                                           (state/close-modal!))}})
 
                             (when file
                               {:title (t :page/re-index)
@@ -437,7 +411,7 @@
                   (block/block-parents config repo block-id format)]))
 
              ;; blocks
-             (page-blocks-cp repo page file-path page-name page-original-name encoded-page-name sidebar? journal? block? block-id format)]]
+             (page-blocks-cp repo page file-path page-name page-original-name page-name sidebar? journal? block? block-id format)]]
 
            (when-not block?
              (today-queries repo today? sidebar?))
@@ -516,21 +490,20 @@
               [:th (t :file/last-modified-at)]]]
             [:tbody
              (for [page pages]
-               (let [encoded-page (util/encode-str page)]
-                 [:tr {:key encoded-page}
-                  [:td [:a {:on-click (fn [e]
-                                        (let [repo (state/get-current-repo)
-                                              page (db/pull repo '[*] [:block/name (string/lower-case page)])]
-                                          (when (gobj/get e "shiftKey")
-                                            (state/sidebar-add-block!
-                                             repo
-                                             (:db/id page)
-                                             :page
-                                             {:page page}))))
-                            :href (rfe/href :page {:name encoded-page})}
-                        page]]
-                  [:td [:span.text-gray-500.text-sm
-                        (t :file/no-data)]]]))]]))])))
+               [:tr {:key page}
+                [:td [:a {:on-click (fn [e]
+                                      (let [repo (state/get-current-repo)
+                                            page (db/pull repo '[*] [:block/name (string/lower-case page)])]
+                                        (when (gobj/get e "shiftKey")
+                                          (state/sidebar-add-block!
+                                           repo
+                                           (:db/id page)
+                                           :page
+                                           {:page page}))))
+                          :href (rfe/href :page {:name page})}
+                      page]]
+                [:td [:span.text-gray-500.text-sm
+                      (t :file/no-data)]]])]]))])))
 
 (rum/defcs new < rum/reactive
   (rum/local "" ::title)

+ 1 - 7
src/main/frontend/components/repo.cljs

@@ -78,16 +78,10 @@
                                   :on-click (fn []
                                               (repo-handler/re-index! nfs-handler/rebuild-index!))}
                  "Re-index"]
-                ;; [:a.control.ml-4 {:title "Export as JSON"
-                ;;                   :on-click (fn []
-                ;;                               (export-handler/export-repo-as-json! (:url repo)))}
-                ;;  "Export as JSON"]
                 [:a.text-gray-400.ml-4 {:title "No worries, unlink this graph will clear its cache only, it does not remove your files on the disk."
                                         :on-click (fn []
                                                     (repo-handler/remove-repo! repo))}
-                 "Unlink"]]]))]
-
-         [:a#download-as-json.hidden]]
+                 "Unlink"]]]))]]
         (widgets/add-graph)))))
 
 (rum/defc sync-status < rum/reactive

+ 3 - 2
src/main/frontend/components/right_sidebar.cljs

@@ -56,7 +56,8 @@
 
 (defn recent-pages
   []
-  (let [pages (db/get-key-value :recent/pages)]
+  (let [pages (->> (db/get-key-value :recent/pages)
+                   (remove #(= (string/lower-case %) "contents")))]
     [:div.recent-pages.text-sm.flex-col.flex.ml-3.mt-2
      (if (seq pages)
        (for [page pages]
@@ -200,7 +201,7 @@
 
                (date/journal-name))]
     (if page
-      (util/url-decode (string/lower-case page)))))
+      (string/lower-case page))))
 
 (defn get-current-page
   []

+ 1 - 1
src/main/frontend/components/search.cljs

@@ -244,7 +244,7 @@
         search-q (state/sub :search/q)
         show-result? (boolean (seq search-result))
         blocks-count (or (db/blocks-count) 0)
-        timeout (if (> blocks-count 2000) 500 100)]
+        timeout (if (> blocks-count 2000) 500 300)]
     (rum/with-context [[t] i18n/*tongue-context*]
       [:div#search.flex-1.flex
        [:div.inner

+ 7 - 1
src/main/frontend/components/sidebar.cljs

@@ -131,7 +131,8 @@
            (ui/loading (t :loading))]]
 
          :else
-         [:div.max-w-7xl.mx-auto {:style {:margin-bottom (if global-graph-pages? 0 120)}}
+         [:div {:class (if global-graph-pages? "" (util/hiccup->class "max-w-7xl.mx-auto"))
+                :style {:margin-bottom (if global-graph-pages? 0 120)}}
           main-content])]]
      (right-sidebar/sidebar)]))
 
@@ -184,6 +185,11 @@
          (route-handler/redirect! {:to :page
                                    :path-params {:name (:page default-home)}})
 
+         (and config/publishing?
+              (not default-home)
+              (empty? latest-journals))
+         (route-handler/redirect! {:to :all-pages})
+
          importing-to-db?
          (ui/loading (t :parsing-files))
 

+ 8 - 1
src/main/frontend/config.cljs

@@ -28,10 +28,17 @@
 (def asset-domain (util/format "https://asset.%s.com"
                                app-name))
 
+;; TODO: Remove this, switch to lazy loader
 (defn asset-uri
   [path]
-  (if (util/file-protocol?)
+  (cond
+    publishing?
+    path
+
+    (util/file-protocol?)
     (string/replace path "/static/" "./")
+
+    :else
     (if dev? path
         (str asset-domain path))))
 

+ 1 - 1
src/main/frontend/db.cljs

@@ -30,7 +30,7 @@
   remove-conn!]
 
  [frontend.db.utils
-  date->int db->json db->string get-max-tx-id get-tx-id
+  date->int db->json db->edn-str db->string get-max-tx-id get-tx-id
   group-by-page seq-flatten sort-by-pos
   string->db with-repo
 

+ 2 - 1
src/main/frontend/db/model.cljs

@@ -570,7 +570,8 @@
         (->> (string/split-lines content)
              (take-while (fn [line]
                            (or (string/blank? line)
-                               (string/starts-with? line "#+"))))
+                               (string/starts-with? line "#+")
+                               (re-find #"^:.+?:" line))))
              (string/join "\n"))
 
         :markdown

+ 49 - 40
src/main/frontend/db/query_dsl.cljs

@@ -46,16 +46,17 @@
 
 (defn query-wrapper
   [where blocks?]
-  (let [q (if blocks?                   ; FIXME: it doesn't need to be either blocks or pages
-            '[:find (pull ?b [*])
-              :where]
-            '[:find (pull ?p [*])
-              :where])
-        result (if (coll? (first where))
-                 (apply conj q where)
-                 (conj q where))]
-    (prn "Datascript query: " result)
-    result))
+  (when where
+    (let [q (if blocks?                   ; FIXME: it doesn't need to be either blocks or pages
+             '[:find (pull ?b [*])
+               :where]
+               '[:find (pull ?p [*])
+                 :where])
+         result (if (coll? (first where))
+                  (apply conj q where)
+                  (conj q where))]
+     (prn "Datascript query: " result)
+     result)))
 
 ;; (between -7d +7d)
 (defn- ->journal-day-int [input]
@@ -151,7 +152,7 @@
        (contains? #{'and 'or 'not} fe)
        (let [clauses (->> (map (fn [form]
                                  (build-query repo form (assoc env :current-filter fe) (inc level)))
-                               (rest e))
+                            (rest e))
                           remove-nil?
                           (distinct))]
          (when (seq clauses)
@@ -221,7 +222,7 @@
                        (text/page-ref-un-brackets!))
              sym (if (= current-filter 'or)
                    '?v
-                   (uniq-symbol counter "?v"))]
+                     (uniq-symbol counter "?v"))]
          [['?b :block/properties '?prop]
           [(list 'get '?prop (name (nth e 1))) sym]
           (list
@@ -354,39 +355,47 @@
       (try
         (let [form (some-> s
                            (pre-transform)
-                           (reader/read-string))
-              sort-by (atom nil)
-              blocks? (atom nil)
-              result (when form (build-query repo form {:sort-by sort-by
-                                                        :blocks? blocks?
-                                                        :counter counter}))
-              result (when (seq result)
-                       (let [key (if (coll? (first result))
-                                   (keyword (ffirst result))
-                                   (keyword (first result)))
-                             result (case key
-                                      :and
-                                      (rest result)
-
-                                      result)]
-                         (add-bindings! result)))]
-          {:query result
-           :sort-by @sort-by
-           :blocks? (boolean @blocks?)})
+                           (reader/read-string))]
+          (if (symbol? form)
+            (str form)
+            (let [sort-by (atom nil)
+                  blocks? (atom nil)
+                  result (when form (build-query repo form {:sort-by sort-by
+                                                            :blocks? blocks?
+                                                            :counter counter}))]
+              (if (string? result)
+                result
+                (let [result (when (seq result)
+                              (let [key (if (coll? (first result))
+                                          (keyword (ffirst result))
+                                          (keyword (first result)))
+                                    result (case key
+                                             :and
+                                             (rest result)
+
+                                             result)]
+                                (add-bindings! result)))]
+                 {:query result
+                  :sort-by @sort-by
+                  :blocks? (boolean @blocks?)})))))
         (catch js/Error e
           (log/error :query-dsl/parse-error e))))))
 
 (defn query
   [repo query-string]
   (when (string? query-string)
-    (let [query-string (template/resolve-dynamic-template! query-string)
-          {:keys [query sort-by blocks?]} (parse repo query-string)]
-      (when query
-        (let [query (query-wrapper query blocks?)]
-          (react/react-query repo
-                             {:query query}
-                             (if sort-by
-                               {:transform-fn sort-by})))))))
+    (let [query-string (template/resolve-dynamic-template! query-string)]
+      (when-not (string/blank? query-string)
+        (let [{:keys [query sort-by blocks?] :as result} (parse repo query-string)]
+          (if (string? result)
+            (if (= "\"" (first result) (last result))
+              (subs result 1 (dec (count result)))
+              result)
+            (when-let [query (query-wrapper query blocks?)]
+              (react/react-query repo
+                                 {:query query}
+                                 (if sort-by
+                                   {:transform-fn sort-by})))))))))
 
 (defn custom-query
   [repo query-m query-opts]
@@ -395,7 +404,7 @@
           query-string (template/resolve-dynamic-template! query-string)
           {:keys [query sort-by blocks?]} (parse repo query-string)]
       (when query
-        (let [query (query-wrapper query blocks?)]
+        (when-let [query (query-wrapper query blocks?)]
           (react/react-query repo
                              (merge
                               query-m

+ 1 - 1
src/main/frontend/db/query_react.cljs

@@ -99,5 +99,5 @@
           k [:custom query']]
       (apply react/q repo k query-opts query inputs))
     (catch js/Error e
-      (println "Custom query failed: ")
+      (println "Custom query failed: " {:query query'})
       (js/console.dir e))))

+ 1 - 1
src/main/frontend/db/react.cljs

@@ -174,7 +174,7 @@
 
                (date/journal-name))]
     (when page
-      (let [page-name (util/url-decode (string/lower-case page))]
+      (let [page-name (string/lower-case page)]
         (db-utils/entity [:block/name page-name])))))
 
 (defn get-current-priority

+ 3 - 0
src/main/frontend/db/utils.cljs

@@ -20,6 +20,9 @@
     (for [d (d/datoms db :eavt)]
       #js [(:e d) (name (:a d)) (:v d)]))))
 
+(defn db->edn-str [db]
+  (pr-str db))
+
 (defn string->db [s]
   (dt/read-transit-str s))
 

+ 10 - 5
src/main/frontend/dicts.cljs

@@ -42,7 +42,7 @@ some changes on the right sidebar, those referenced blocks will be changed too!
         [:iframe
          {:allowFullScreen \"allowfullscreen\"
           :allow
-          \"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture\"
+          \"accelerometer; autoplay; encrypted-media; gyroscope\"
         :frameBorder \"0\"
         :src \"https://www.youtube.com/embed/Afmqowr0qEQ\"
         :height \"367\"
@@ -50,7 +50,7 @@ some changes on the right sidebar, those referenced blocks will be changed too!
 ### DONE Create a page
 ### CANCELED [#C] Write a page with more than 1000 blocks
 ## That's it! You can create more bullets or open a local directory to import some notes now!
-##
+## You can also download our desktop app at https://github.com/logseq/logseq/releases
 "
         :tutorial/dummy-notes "---
 title: How to take dummy notes?
@@ -229,7 +229,7 @@ title: How to take dummy notes?
         :page/open-in-finder "Open in directory"
         :page/open-with-default-app "Open with default app"
         :page/action-publish "Publish"
-        :page/make-public "Publish it when exporting to an html file"
+        :page/make-public "Make it public for publishing"
         :page/make-private "Make it private"
         :page/delete "Delete page"
         :page/publish "Publish this page on Logseq"
@@ -310,7 +310,6 @@ title: How to take dummy notes?
         :cancel "Cancel"
         :close "Close"
         :re-index "Re-index"
-        :export-json "Export as JSON"
         :unlink "unlink"
         :search (if config/publishing?
                   "Search"
@@ -320,7 +319,11 @@ title: How to take dummy notes?
         :graph "Graph"
         :graph-view "View Graph"
         :publishing "Publishing"
-        :export "Export public pages"
+        :export "Export"
+        :export-json "Export as JSON"
+        :export-markdown "Export as Markdown"
+        :export-public-pages "Export public pages"
+        :export-edn "Export as EDN"
         :all-graphs "All graphs"
         :all-pages "All pages"
         :all-files "All files"
@@ -1033,6 +1036,7 @@ title: How to take dummy notes?
            :cancel "取消"
            :re-index "重新建立索引"
            :export-json "以 JSON 格式导出"
+           :export-markdown "以 Markdown 格式导出"
            :unlink "解除绑定"
            :search (if config/publishing?
                      "搜索"
@@ -1297,6 +1301,7 @@ title: How to take dummy notes?
              :cancel "取消"
              :re-index "重新建立索引"
              :export-json "以 JSON 格式導出"
+             :export-markdown "以 Markdown 格式導出"
              :unlink "解除綁定"
              :search (if config/publishing?
                        "搜索"

+ 5 - 0
src/main/frontend/extensions/code.cljs

@@ -16,6 +16,7 @@
             ["codemirror/mode/clojure/clojure"]
             ["codemirror/mode/powershell/powershell"]
             ["codemirror/mode/javascript/javascript"]
+            ["codemirror/mode/jsx/jsx"]
             ["codemirror/mode/clike/clike"]
             ["codemirror/mode/vue/vue"]
             ["codemirror/mode/commonlisp/commonlisp"]
@@ -100,6 +101,10 @@
         "csharp" "text/x-csharp"
         "objective-c" "text/x-objectivec"
         "scala" "text/x-scala"
+        "js" "text/javascript"
+        "typescript" "text/typescript"
+        "ts" "text/typescript"
+        "tsx" "text/typescript-jsx"
         mode))))
 
 (defn render!

+ 4 - 6
src/main/frontend/external/roam.cljc

@@ -5,7 +5,8 @@
             [medley.core :as medley]
             [clojure.walk :as walk]
             [clojure.string :as string]
-            [frontend.util :as util]))
+            [frontend.util :as util]
+            [frontend.text :as text]))
 
 (defonce all-refed-uids (atom #{}))
 (defonce uid->uuid (atom {}))
@@ -34,12 +35,9 @@
 (defn macro-transform
   [text]
   (string/replace text macro-pattern (fn [[original text]]
-                                       (let [[name arg] (-> (string/replace text #" " "")
-                                                            (string/split #":"))]
+                                       (let [[name arg] (util/split-first ":" text)]
                                          (if name
-                                           (let [name (case name
-                                                        "[[embed]]" "embed"
-                                                        name)]
+                                           (let [name (text/page-ref-un-brackets! name)]
                                              (util/format "{{%s %s}}" name arg))
                                            original)))))
 

+ 6 - 4
src/main/frontend/format.cljs

@@ -33,8 +33,10 @@
 
 ;; html
 (defn get-default-config
-  [format]
-  (mldoc/default-config format))
+  ([format]
+   (mldoc/default-config format))
+  ([format heading-to-list?]
+   (mldoc/default-config format heading-to-list?)))
 
 (defn to-html
   ([content format]
@@ -62,7 +64,7 @@
     (protocol/loaded? record)))
 
 (def marker-pattern
-  #"^(NOW|LATER|TODO|DOING|DONE|WAIT|WAITING|CANCELED|CANCELLED|STARTED|IN-PROGRESS)?\s?")
+  #"^(NOW|LATER|TODO|DOING|DONE|WAITING|WAIT|CANCELED|CANCELLED|STARTED|IN-PROGRESS)?\s?")
 
 (def bare-marker-pattern
-  #"^(NOW|LATER|TODO|DOING|DONE|WAIT|WAITING|CANCELED|CANCELLED|STARTED|IN-PROGRESS){1}\s+")
+  #"^(NOW|LATER|TODO|DOING|DONE|WAITING|WAIT|CANCELED|CANCELLED|STARTED|IN-PROGRESS){1}\s+")

+ 3 - 1
src/main/frontend/format/adoc.cljs

@@ -26,4 +26,6 @@
   (lazyLoad [this ok-handler]
     (loader/load
      "https://cdnjs.cloudflare.com/ajax/libs/asciidoctor.js/1.5.9/asciidoctor.min.js"
-     ok-handler)))
+     ok-handler))
+  (exportMarkdown [this content config references]
+    (throw "not support")))

+ 14 - 7
src/main/frontend/format/block.cljs

@@ -60,11 +60,11 @@
                (and (vector? block)
                     (= "Macro" (first block)))
                (let [{:keys [name arguments]} (second block)]
-                 (when (and (= name "embed")
-                            (string? (first arguments))
-                            (string/starts-with? (first arguments) "[[")
-                            (string/ends-with? (first arguments) "]]"))
-                   (subs (first arguments) 2 (- (count (first arguments)) 2))))
+                 (let [argument (string/join ", " arguments)]
+                   (when (and (= name "embed")
+                              (string? argument)
+                              (text/page-ref? argument))
+                     (text/page-ref-un-brackets! argument))))
                :else
                nil)]
     (when (and
@@ -234,8 +234,15 @@
            (swap! ref-pages conj tag)))
        form)
      (concat title body))
-    (let [ref-pages (remove string/blank? @ref-pages)]
-      (assoc block :ref-pages (vec ref-pages)))))
+    (let [ref-pages (remove string/blank? @ref-pages)
+          children-pages (->> (mapcat (fn [p]
+                                        (if (string/includes? p "/")
+                                          ;; Don't create the last page for now
+                                          (butlast (string/split p #"/"))))
+                                      ref-pages)
+                              (remove string/blank?))
+          ref-pages (distinct (concat ref-pages children-pages))]
+      (assoc block :ref-pages ref-pages))))
 
 (defn with-block-refs
   [{:keys [title body] :as block}]

+ 36 - 13
src/main/frontend/format/mldoc.cljs

@@ -14,16 +14,26 @@
 (defonce parseInlineJson (gobj/get Mldoc "parseInlineJson"))
 (defonce parseHtml (gobj/get Mldoc "parseHtml"))
 (defonce anchorLink (gobj/get Mldoc "anchorLink"))
+(defonce parseAndExportMarkdown (gobj/get Mldoc "parseAndExportMarkdown"))
 
 (defn default-config
-  [format]
-  (let [format (string/capitalize (name (or format :markdown)))]
-    (js/JSON.stringify
-     (bean/->js
-      (assoc {:toc false
-              :heading_number false
-              :keep_line_break true}
-             :format format)))))
+  ([format]
+   (default-config format false))
+  ([format export-heading-to-list?]
+   (let [format (string/capitalize (name (or format :markdown)))]
+     (js/JSON.stringify
+      (bean/->js
+       {:toc false
+        :heading_number false
+        :keep_line_break true
+        :format format
+        :heading_to_list export-heading-to-list?})))))
+
+(def default-references
+  (js/JSON.stringify
+   (clj->js {:embed_blocks []
+             :embed_pages []
+             :refer_blocks []})))
 
 (defn parse-json
   [content config]
@@ -33,6 +43,12 @@
   [text config]
   (parseInlineJson text (or config default-config)))
 
+(defn parse-export-markdown
+  [content config references]
+  (parseAndExportMarkdown content
+                          (or config default-config)
+                          (or references default-references)))
+
 ;; Org-roam
 (defn get-tags-from-definition
   [ast]
@@ -82,8 +98,12 @@
   (if (seq ast)
     (let [original-ast ast
           ast (map first ast)           ; without position meta
-          directive? (fn [item] (= "directive" (string/lower-case (first item))))
-          properties (->> (take-while directive? ast)
+          directive?
+          (fn [[item _]] (= "directive" (string/lower-case (first item))))
+          grouped-ast (group-by directive? original-ast)
+          [directive-ast other-ast]
+          [(get grouped-ast true) (get grouped-ast false)]
+          properties (->> (map first directive-ast)
                           (map (fn [[_ k v]]
                                  (let [k (keyword (string/lower-case k))
                                        comma? (contains? #{:tags :alias :roam_tags} k)
@@ -91,6 +111,7 @@
                                            v
                                            (text/split-page-refs-without-brackets v comma?))]
                                    [k v])))
+                          (reverse)
                           (into {}))
           macro-properties (filter (fn [x] (= :macro (first x))) properties)
           macros (if (seq macro-properties)
@@ -134,8 +155,7 @@
                          (update :roam_alias ->vec)
                          (update :roam_tags (constantly roam-tags))
                          (update :filetags (constantly filetags)))
-          properties (medley/filter-kv (fn [k v] (not (empty? v))) properties)
-          other-ast (drop-while (fn [[item _pos]] (directive? item)) original-ast)]
+          properties (medley/filter-kv (fn [k v] (not (empty? v))) properties)]
       (if (seq properties)
         (cons [["Properties" properties] nil] other-ast)
         original-ast))
@@ -174,7 +194,10 @@
   (loaded? [this]
     true)
   (lazyLoad [this ok-handler]
-    true))
+    true)
+  (exportMarkdown [this content config references]
+    (parse-export-markdown content config references))
+  )
 
 (defn plain->text
   [plains]

+ 2 - 1
src/main/frontend/format/protocol.cljs

@@ -4,4 +4,5 @@
   (toEdn [this content config])
   (toHtml [this content config])
   (loaded? [this])
-  (lazyLoad [this ok-handler]))
+  (lazyLoad [this ok-handler])
+  (exportMarkdown [this content config references]))

+ 1 - 1
src/main/frontend/fs/nfs.cljs

@@ -152,7 +152,7 @@
                   (when file
                     (nfs-saved-handler repo path file))))
               (do
-                (js/alert (str "The file has been modified in your local disk! File path: " path
+                (js/alert (str "The file has been modified on your local disk! File path: " path
                                ", please save your changes and click the refresh button to reload it.")))))
            ;; create file handle
           (->

+ 1 - 1
src/main/frontend/fs/node.cljs

@@ -28,7 +28,7 @@
   (p/let [disk-mtime (when stat (gobj/get stat "mtime"))
           db-mtime (db/get-file-last-modified-at repo path)]
     (if (not= disk-mtime db-mtime)
-      (js/alert (str "The file has been modified in your local disk! File path: " path
+      (js/alert (str "The file has been modified on your local disk! File path: " path
                      ", please save your changes and click the refresh button to reload it."))
       (->
        (p/let [result (ipc/ipc "writeFile" path content)

+ 4 - 3
src/main/frontend/handler.cljs

@@ -31,7 +31,8 @@
   []
   (let [f (fn []
             (when-not (state/nfs-refreshing?)
-              (repo-handler/create-today-journal!))
+              ;; Don't create the journal file until user writes something
+              (repo-handler/create-today-journal! false))
             (when-let [repo (state/get-current-repo)]
               (when (and (search-db/empty? repo)
                          (state/input-idle? repo))
@@ -155,8 +156,8 @@
 (defn on-load-events
   []
   (let [f (fn []
-            (init-sentry))]
-   (set! js/window.onload f)))
+            (when-not config/dev? (init-sentry)))]
+    (set! js/window.onload f)))
 
 (defn start!
   [render]

+ 41 - 0
src/main/frontend/handler/editor.cljs

@@ -2336,6 +2336,47 @@
            (not (in-auto-complete? input)))
       (on-up-down state e up?))))
 
+(defn- move-to-block-when-cross-boundrary
+  [state e direction]
+  (let [up? (= :left direction)
+        pos (if up? :max 0)
+        {:keys [id block-id block block-parent-id dummy? value format] :as block-state} (get-state state)
+        element (gdom/getElement id)]
+    (when block-id
+      (let [f (if up? get-prev-block-non-collapsed get-next-block-non-collapsed)
+            sibling-block (f (gdom/getElement block-parent-id))]
+        (when sibling-block
+          (when-let [sibling-block-id (d/attr sibling-block "blockid")]
+            (let [state (get-state state)
+                  content (:block/content block)
+                  value (:value state)]
+              (when (not= (-> content
+                              (text/remove-level-spaces format)
+                              text/remove-properties!
+                              string/trim)
+                          (string/trim value))
+                (save-block! state (:value state))))
+            (let [block (db/pull (state/get-current-repo) '[*] [:block/uuid (uuid sibling-block-id)])]
+              (edit-block! block pos format id)
+              (util/stop e))))))))
+
+(defn- on-arrow-move-to-boundray
+  [state input e direction]
+  (when (or (and (= :left direction) (util/input-start? input))
+            (and (= :right direction) (util/input-end? input)))
+    (move-to-block-when-cross-boundrary state e direction)))
+
+(defn keydown-arrow-handler
+  [input direction]
+  (fn [state e]
+    (when (and
+           input
+           (not (gobj/get e "shiftKey"))
+           (not (gobj/get e "ctrlKey"))
+           (not (gobj/get e "metaKey"))
+           (not (in-auto-complete? input)))
+      (on-arrow-move-to-boundray state input e direction))))
+
 (defn keydown-backspace-handler
   [repo input id]
   (fn [state e]

+ 148 - 18
src/main/frontend/handler/export.cljs

@@ -1,6 +1,9 @@
 (ns frontend.handler.export
   (:require [frontend.state :as state]
             [frontend.db :as db]
+            [frontend.format.protocol :as fp]
+            [frontend.format :as f]
+            [datascript.core :as d]
             [frontend.util :as util]
             [cljs-bean.core :as bean]
             [clojure.string :as string]
@@ -44,6 +47,16 @@
         (.setAttribute anchor "download" (str (last (string/split repo #"/")) ".json"))
         (.click anchor)))))
 
+(defn export-repo-as-edn!
+  [repo]
+  (when-let [db (db/get-conn repo)]
+    (let [db-edn (db/db->edn-str db)
+          data-str (str "data:text/edn;charset=utf-8," (js/encodeURIComponent db-edn))]
+      (when-let [anchor (gdom/getElement "download-as-edn")]
+        (.setAttribute anchor "href" data-str)
+        (.setAttribute anchor "download" (str (last (string/split repo #"/")) ".edn"))
+        (.click anchor)))))
+
 (defn download-file!
   [file-path]
   (when-let [repo (state/get-current-repo)]
@@ -59,24 +72,27 @@
 (defn export-repo-as-html!
   [repo]
   (when-let [db (db/get-conn repo)]
-    (let [db (if (state/all-pages-public?)
-               (db/clean-export! db)
-               (db/filter-only-public-pages-and-blocks db))
-          db-str (db/db->string db)
-          state (select-keys @state/state
-                             [:ui/theme :ui/cycle-collapse
-                              :ui/collapsed-blocks
-                              :ui/sidebar-collapsed-blocks
-                              :ui/show-recent?
-                              :config])
-          state (update state :config (fn [config]
-                                        {"local" (get config repo)}))
-          html-str (str "data:text/html;charset=UTF-8,"
-                        (js/encodeURIComponent (html/publishing-html db-str (pr-str state))))]
-      (when-let [anchor (gdom/getElement "download-as-html")]
-        (.setAttribute anchor "href" html-str)
-        (.setAttribute anchor "download" "index.html")
-        (.click anchor)))))
+    (let [db           (if (state/all-pages-public?)
+                         (db/clean-export! db)
+                         (db/filter-only-public-pages-and-blocks db))
+          db-str       (db/db->string db)
+          state        (select-keys @state/state
+                                    [:ui/theme :ui/cycle-collapse
+                                     :ui/collapsed-blocks
+                                     :ui/sidebar-collapsed-blocks
+                                     :ui/show-recent?
+                                     :config])
+          state        (update state :config (fn [config]
+                                               {"local" (get config repo)}))
+          raw-html-str (html/publishing-html db-str (pr-str state))
+          html-str     (str "data:text/html;charset=UTF-8,"
+                            (js/encodeURIComponent raw-html-str))]
+      (if (util/electron?)
+        (js/window.apis.exportPublishAssets raw-html-str)
+        (when-let [anchor (gdom/getElement "download-as-html")]
+          (.setAttribute anchor "href" html-str)
+          (.setAttribute anchor "download" "index.html")
+          (.click anchor))))))
 
 (defn export-repo-as-zip!
   [repo]
@@ -89,3 +105,117 @@
           (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
           (.setAttribute anchor "download" (.-name zipfile))
           (.click anchor))))))
+
+
+
+(defn- get-file-contents-with-suffix
+  [repo]
+  (let [conn (db/get-conn repo)]
+    (->>
+     (filterv (fn [[path _]]
+                (or (string/ends-with? path ".md")))
+              (db/get-file-contents repo))
+     (mapv (fn [[path content]] {:path path :content content
+                                 :names (d/q '[:find [?n ?n2]
+                                               :in $ ?p
+                                               :where [?e :file/path ?p]
+                                               [?e2 :block/file ?e]
+                                               [?e2 :block/name ?n]
+                                               [?e2 :block/original-name ?n2]] conn path)
+                                 :format (f/get-format path)})))))
+
+(defn- get-embed-and-refs-blocks-pages-aux
+  [repo page-or-block is-block? exclude-blocks exclude-pages]
+  (let [[ref-blocks ref-pages]
+        (->> (if is-block?
+               [page-or-block]
+               (db/get-page-blocks
+                repo page-or-block {:use-cache? false
+                                    :pull-keys '[:block/ref-pages :block/ref-blocks]}))
+             (filterv #(or (:block/ref-blocks %) (:block/ref-pages %)))
+             (mapv (fn [b] [(:block/ref-blocks b), (:block/ref-pages b)]))
+             (apply mapv vector)
+             (mapv #(vec (distinct (flatten (remove nil? %))))))
+        ref-block-ids
+        (->> ref-blocks
+             (#(remove (fn [b] (contains? exclude-blocks (:db/id b))) %))
+             (mapv #(:db/id %)))
+        ref-page-ids
+        (->> ref-pages
+             (#(remove (fn [b] (contains? exclude-pages (:db/id b))) %))
+             (mapv #(:db/id %)))
+        ref-blocks
+        (->> ref-block-ids
+             (db/pull-many repo '[*])
+             (flatten))
+        ref-pages
+        (->> ref-page-ids
+             (db/pull-many repo '[*])
+             (flatten))
+        [next-ref-blocks1 next-ref-pages1]
+        (->> ref-blocks
+             (mapv #(get-embed-and-refs-blocks-pages-aux repo % true
+                                                         (set (concat ref-block-ids exclude-blocks)) exclude-pages))
+             (apply mapv vector))
+        [next-ref-blocks2 next-ref-pages2]
+        (->> ref-pages
+             (mapv #(get-embed-and-refs-blocks-pages-aux repo (:block/name %) false
+                                                         exclude-blocks (set (concat ref-page-ids exclude-pages))))
+             (apply mapv vector))]
+    [(->> (concat ref-block-ids next-ref-blocks1 next-ref-blocks2)
+          (flatten)
+          (distinct))
+     (->> (concat ref-page-ids next-ref-pages1 next-ref-pages2)
+          (flatten)
+          (distinct))]))
+
+
+(defn- get-embed-and-refs-blocks-pages
+  [repo page]
+  (let [[block-ids page-ids]
+        (get-embed-and-refs-blocks-pages-aux repo page false #{} #{})
+        blocks
+        (db/pull-many repo '[*] block-ids)
+        pages-name-and-content
+        (->> page-ids
+             (d/q '[:find ?n (pull ?p [:file/path])
+                    :in $ [?e ...]
+                    :where
+                    [?e :block/file ?p]
+                    [?e :block/name ?n]] (db/get-conn repo))
+             (mapv (fn [[page-name file-path]] [page-name (:file/path file-path)]))
+             (d/q '[:find ?n ?c
+                    :in $ [[?n ?p] ...]
+                    :where
+                    [?e :file/path ?p]
+                    [?e :file/content ?c]] @(db/get-files-conn repo)))
+        embed-blocks
+        (mapv (fn [b] [(str (:block/uuid b))
+                       [(apply str
+                               (mapv #(:block/content %)
+                                     (db/get-block-and-children repo (:block/uuid b))))
+                        (:block/title b)]])
+              blocks)]
+    {:embed_blocks embed-blocks
+     :embed_pages pages-name-and-content}))
+
+(defn export-repo-as-markdown!
+  [repo]
+  (when-let [repo (state/get-current-repo)]
+    (when-let [files (get-file-contents-with-suffix repo)]
+      (let [heading-to-list? (state/export-heading-to-list?)
+            files
+            (->> files
+                 (mapv (fn [{:keys [path content names format]}]
+                         (when (first names)
+                           [path (fp/exportMarkdown f/mldoc-record content
+                                                    (f/get-default-config format heading-to-list?)
+                                                    (js/JSON.stringify
+                                                     (clj->js (get-embed-and-refs-blocks-pages repo (first names)))))])))
+                 (remove nil?))
+            zip-file-name (str repo "_markdown_" (quot (util/time-ms) 1000))]
+        (p/let [zipfile (zip/make-zip zip-file-name files)]
+          (when-let [anchor (gdom/getElement "export-as-markdown")]
+            (.setAttribute anchor "href" (js/window.URL.createObjectURL zipfile))
+            (.setAttribute anchor "download" (.-name zipfile))
+            (.click anchor)))))))

+ 27 - 23
src/main/frontend/handler/page.cljs

@@ -36,9 +36,11 @@
 
 (defn- get-file-name
   [journal? title]
-  (if journal?
-    (date/journal-title->default title)
-    (util/page-name-sanity (string/lower-case title))))
+  (when-let [s (if journal?
+            (date/journal-title->default title)
+            (util/page-name-sanity (string/lower-case title)))]
+    ;; Win10 file path has a length limit of 260 chars
+    (util/safe-subs s 0 200)))
 
 (defn create!
   ([title]
@@ -100,7 +102,9 @@
             file (db/entity (:db/id (:block/file page)))
             file-path (:file/path file)
             file-content (db/get-file file-path)
-            after-content (subs file-content (inc (count properties-content)))
+            after-content (if (empty? properties-content)
+                            file-content
+                            (subs file-content (inc (count properties-content))))
             new-properties-content (db/add-properties! page-format properties-content properties)
             full-content (str new-properties-content "\n\n" (string/trim after-content))]
         (file-handler/alter-file (state/get-current-repo)
@@ -430,27 +434,27 @@
 
 (defn get-page-ref-text
   [page]
-  (when-let [edit-block (state/get-edit-block)]
-    (let [page-name (string/lower-case page)
-          edit-block-file-path (-> (:db/id (:block/file edit-block))
-                                   (db/entity)
-                                   :file/path)]
-      (if (and edit-block-file-path
-               (state/org-mode-file-link? (state/get-current-repo)))
-        (if-let [ref-file-path (:file/path (:block/file (db/entity [:block/name page-name])))]
+  (let [edit-block-file-path (some-> (state/get-edit-block)
+                                     (get-in [:block/file :db/id])
+                                     db/entity
+                                     :file/path)
+        page-name (string/lower-case page)]
+    (if (and edit-block-file-path
+             (state/org-mode-file-link? (state/get-current-repo)))
+      (if-let [ref-file-path (:file/path (:file/file (db/entity [:file/name page-name])))]
+        (util/format "[[file:%s][%s]]"
+                     (util/get-relative-path edit-block-file-path ref-file-path)
+                     page)
+        (let [journal? (date/valid-journal-title? page)
+              ref-file-path (str (get-directory journal?)
+                                 "/"
+                                 (get-file-name journal? page)
+                                 ".org")]
+          (create! page {:redirect? false})
           (util/format "[[file:%s][%s]]"
                        (util/get-relative-path edit-block-file-path ref-file-path)
-                       page)
-          (let [journal? (date/valid-journal-title? page)
-                ref-file-path (str (get-directory journal?)
-                                   "/"
-                                   (get-file-name journal? page)
-                                   ".org")]
-            (create! page {:redirect? false})
-            (util/format "[[file:%s][%s]]"
-                         (util/get-relative-path edit-block-file-path ref-file-path)
-                         page)))
-        (util/format "[[%s]]" page)))))
+                       page)))
+      (util/format "[[%s]]" page))))
 
 (defn init-commands!
   []

+ 22 - 15
src/main/frontend/handler/repo.cljs

@@ -111,7 +111,8 @@
 (defn create-today-journal-if-not-exists
   ([repo-url]
    (create-today-journal-if-not-exists repo-url nil))
-  ([repo-url content]
+  ([repo-url {:keys [content write-file?]
+              :or {write-file? true}}]
    (spec/validate :repos/url repo-url)
    (when (state/enable-journals? repo-url)
      (let [repo-dir (config/get-repo-dir repo-url)
@@ -144,22 +145,28 @@
                  file-exists? (fs/file-exists? repo-dir file-path)]
            (when-not file-exists?
              (file-handler/reset-file! repo-url path content)
-             (p/let [_ (fs/create-if-not-exists repo-url repo-dir file-path content)]
+             (if write-file?
+               (p/let [_ (fs/create-if-not-exists repo-url repo-dir file-path content)]
+                 (when-not (state/editing?)
+                   (ui-handler/re-render-root!))
+                 (git-handler/git-add repo-url path))
                (when-not (state/editing?)
                  (ui-handler/re-render-root!))))))))))
 
 (defn create-today-journal!
-  []
-  (state/set-today! (date/today))
-  (when-let [repo (state/get-current-repo)]
-    (when (or (db/cloned? repo)
-              (and (config/local-db? repo)
-                   ;; config file exists
-                   (let [path (config/get-config-path)]
-                     (db/get-file path))))
-      (let [today-page (string/lower-case (date/today))]
-        (when (empty? (db/get-page-blocks-no-cache repo today-page))
-          (create-today-journal-if-not-exists repo))))))
+  ([]
+   (create-today-journal! true))
+  ([write-file?]
+   (state/set-today! (date/today))
+   (when-let [repo (state/get-current-repo)]
+     (when (or (db/cloned? repo)
+               (and (config/local-db? repo)
+                    ;; config file exists
+                    (let [path (config/get-config-path)]
+                      (db/get-file path))))
+       (let [today-page (string/lower-case (date/today))]
+         (when (empty? (db/get-page-blocks-no-cache repo today-page))
+           (create-today-journal-if-not-exists repo {:write-file? write-file?})))))))
 
 (defn create-default-files!
   ([repo-url]
@@ -169,7 +176,7 @@
    (file-handler/create-metadata-file repo-url encrypted?)
    ;; TODO: move to frontend.handler.file
    (create-config-file-if-not-exists repo-url)
-   (create-today-journal-if-not-exists repo-url)
+   (create-today-journal-if-not-exists repo-url {:write-file? false})
    (create-contents-file repo-url)
    (create-custom-theme repo-url)))
 
@@ -611,7 +618,7 @@
              (when-not config/publishing?
                (let [tutorial (get-in dicts/dicts [:en :tutorial/text])
                      tutorial (string/replace-first tutorial "$today" (date/today))]
-                 (create-today-journal-if-not-exists repo tutorial)))
+                 (create-today-journal-if-not-exists repo {:content tutorial})))
              (create-config-file-if-not-exists repo)
              (create-contents-file repo)
              (create-custom-theme repo)

+ 3 - 4
src/main/frontend/handler/route.cljs

@@ -41,7 +41,7 @@
     :all-journals
     "All journals"
     :file
-    (str "File " (util/url-decode (:path path-params)))
+    (str "File " (:path path-params))
     :new-page
     "Create a new page"
     :page
@@ -55,13 +55,12 @@
               (str (subs content 0 48) "...")
               content))
           "Page no longer exists!!")
-        (let [page (util/url-decode name)
-              page (db/pull [:block/name (string/lower-case page)])]
+        (let [page (db/pull [:block/name (string/lower-case name)])]
           (or (:block/original-name page)
               (:block/name page)
               "Logseq"))))
     :tag
-    (str "#" (util/url-decode (:name path-params)))
+    (str "#"  (:name path-params))
     :diff
     "Git diff"
     :draw

+ 1 - 1
src/main/frontend/publishing/html.cljs

@@ -84,4 +84,4 @@
             ;; TODO: should make this configurable
            [:script {:src "/static/js/highlight.min.js"}]
            [:script {:src "/static/js/interact.min.js"}]
-           [:script {:src "/static/js/publishing/main.js"}]]))))
+           [:script {:src "/static/js/main.js"}]]))))

+ 6 - 2
src/main/frontend/search.cljs

@@ -38,8 +38,10 @@
           indice (fuse. blocks
                         (clj->js {:keys ["uuid" "content"]
                                   :shouldSort true
+                                  :tokenize true
                                   :minMatchCharLength 2
-                                  :threshold 0.2}))]
+                                  :distance 1000
+                                  :threshold 0.35}))]
       (swap! indices assoc-in [repo :blocks] indice)
       indice)))
 
@@ -53,8 +55,10 @@
           indice (fuse. pages
                         (clj->js {:keys ["name"]
                                   :shouldSort true
+                                  :tokenize true
                                   :minMatchCharLength 2
-                                  :threshold 0.2
+                                  :distance 1000
+                                  :threshold 0.35
                                   }))]
       (swap! indices assoc-in [repo :pages] indice)
       indice)))

+ 5 - 0
src/main/frontend/state.cljs

@@ -198,6 +198,11 @@
   (not (false? (:feature/enable-journals?
                 (get (sub-config) repo)))))
 
+(defn export-heading-to-list?
+  []
+  (not (false? (:export/heading-to-list?
+                (get (sub-config) (get-current-repo))))))
+
 (defn enable-encryption?
   [repo]
   (:feature/enable-encryption?

+ 4 - 0
src/main/frontend/text.cljs

@@ -249,3 +249,7 @@
   (let [items (map (fn [item] (str "\"" item "\"")) col)]
     (util/format "[%s]"
                  (string/join ", " items))))
+
+(defn image-link?
+  [img-formats s]
+  (some (fn [fmt] (re-find (re-pattern (str "(?i)\\." fmt "(?:\\?([^#]*))?(?:#(.*))?$")) s)) img-formats))

+ 1 - 1
src/main/frontend/ui.css

@@ -32,7 +32,7 @@
 
 .ui__toggle {
   .wrapper {
-    @apply relative inline-block flex-shrink-0
+    @apply relative flex-shrink-0
     h-6 w-11 border-2 border-transparent flex
     rounded-full cursor-pointer focus:outline-none focus:shadow-outline;
   }

+ 14 - 0
src/main/frontend/util.cljc

@@ -1,4 +1,5 @@
 (ns frontend.util
+  #?(:clj (:refer-clojure :exclude [format]))
   (:require
       #?(:cljs [cljs-bean.core :as bean])
       #?(:cljs [cljs-time.coerce :as tc])
@@ -749,6 +750,18 @@
       [input]
       (and input (.-selectionStart input))))
 
+#?(:cljs
+   (defn input-start?
+     [input]
+     (and input (zero? (.-selectionStart input)))))
+
+#?(:cljs
+   (defn input-end?
+     [input]
+     (and input
+          (= (count (.-value input))
+             (.-selectionStart input)))))
+
 #?(:cljs
     (defn get-selected-text
       []
@@ -1025,6 +1038,7 @@
 (defn page-name-sanity
   [page-name]
   (-> page-name
+      (string/replace #"/" ".")
       ;; Windows reserved path characters
       (string/replace #"[\\/:\\*\\?\"<>|]+" "_")))
 

+ 1 - 1
src/main/frontend/version.cljs

@@ -1,3 +1,3 @@
 (ns frontend.version)
 
-(defonce version "0.0.12")
+(defonce version "0.0.13-1")

+ 18 - 0
tailwind.all.css

@@ -0,0 +1,18 @@
+@charset "utf-8";
+@import "tailwindcss/base";
+@import "tailwindcss/components";
+@import "resources/css/inter.css";
+@import "resources/css/reveal.min.css";
+@import "resources/css/reveal_black.min.css";
+@import "resources/css/fonts.css";
+@import "resources/css/excalidraw.min.css";
+@import "resources/css/katex.min.css";
+@import "resources/css/codemirror.min.css";
+@import "resources/css/animation.css";
+@import "resources/css/table.css";
+@import "resources/css/datepicker.css";
+@import "resources/css/highlight.css";
+@import "resources/css/tooltip.css";
+@import "resources/css/common.css";
+@import-glob "src/main/frontend/**/*.css";
+@import "tailwindcss/utilities";

+ 0 - 3
tailwind.css

@@ -1,3 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;

+ 98 - 189
yarn.lock

@@ -204,7 +204,7 @@
   resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.4.2.tgz#2ba0f69f6e66c78e491733d8f8066c35578c2baf"
   integrity sha512-2vX/P9/p+gBvdUsqAbHSiV1yO2sLtG6fqvzWjlWYzRb6AkeNvpe2Wp7UfJOGb+VQmhymBE07be9ACzyA9ib2jw==
 
-"@fullhuman/postcss-purgecss@^3.0.0":
+"@fullhuman/postcss-purgecss@^3.1.3":
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/@fullhuman/postcss-purgecss/-/postcss-purgecss-3.1.3.tgz#47af7b87c9bfb3de4bc94a38f875b928fffdf339"
   integrity sha512-kwOXw8fZ0Lt1QmeOOrd+o4Ibvp4UTEBFQbzvWldjlKv5n+G9sXfIPn1hh63IQIL8K8vbvv1oYMJiIUbuy9bGaA==
@@ -273,6 +273,19 @@
     mini-svg-data-uri "^1.0.3"
     traverse "^0.6.6"
 
+"@tailwindcss/jit@^0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@tailwindcss/jit/-/jit-0.1.1.tgz#d3783b2941fda6d4521c5ce1b8b547e9fc9e130f"
+  integrity sha512-KXpWiRksgs+7voUeFHRDeRwo+EzY9PW/WLeFt8NHl2+hP551PoDC56QjXAjcgDgRniWQg6WoOwFsm6u2ajVWow==
+  dependencies:
+    chokidar "^3.5.1"
+    dlv "^1.1.3"
+    fast-glob "^3.2.5"
+    lodash.topath "^4.5.2"
+    object-hash "^2.1.1"
+    postcss-selector-parser "^6.0.4"
+    quick-lru "^5.1.1"
+
 "@tailwindcss/typography@^0.2.0":
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.2.0.tgz#b597c83502e3c3c6641a8aaabda223cd494ab349"
@@ -445,13 +458,6 @@ ansi-colors@^1.0.1:
   dependencies:
     ansi-wrap "^0.1.0"
 
-ansi-cyan@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/ansi-cyan/-/ansi-cyan-0.1.1.tgz#538ae528af8982f28ae30d86f2f17456d2609873"
-  integrity sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=
-  dependencies:
-    ansi-wrap "0.1.0"
-
 ansi-gray@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251"
@@ -459,13 +465,6 @@ ansi-gray@^0.1.1:
   dependencies:
     ansi-wrap "0.1.0"
 
-ansi-red@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/ansi-red/-/ansi-red-0.1.1.tgz#8c638f9d1080800a353c9c28c8a81ca4705d946c"
-  integrity sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=
-  dependencies:
-    ansi-wrap "0.1.0"
-
 ansi-regex@^2.0.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
@@ -535,14 +534,6 @@ argparse@^1.0.7:
   dependencies:
     sprintf-js "~1.0.2"
 
-arr-diff@^1.0.1:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a"
-  integrity sha1-aHwydYFjWI/vfeezb6vklesaOZo=
-  dependencies:
-    arr-flatten "^1.0.1"
-    array-slice "^0.2.3"
-
 arr-diff@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
@@ -567,11 +558,6 @@ arr-map@^2.0.0, arr-map@^2.0.2:
   dependencies:
     make-iterator "^1.0.0"
 
-arr-union@^2.0.1:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-2.1.0.tgz#20f9eab5ec70f5c7d215b1077b1c39161d292c7d"
-  integrity sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=
-
 arr-union@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
@@ -597,11 +583,6 @@ array-last@^1.1.1:
   dependencies:
     is-number "^4.0.0"
 
-array-slice@^0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5"
-  integrity sha1-3Tz7gO15c6dRF82sabC5nshhhvU=
-
 array-slice@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4"
@@ -1294,6 +1275,11 @@ colorette@^1.2.1:
   resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
   integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
 
+colorette@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
+  integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
+
 commander@^6.0.0:
   version "6.2.1"
   resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
@@ -1319,13 +1305,6 @@ concat-stream@^1.6.0, concat-stream@^1.6.2:
     readable-stream "^2.2.2"
     typedarray "^0.0.6"
 
-concat-with-sourcemaps@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e"
-  integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==
-  dependencies:
-    source-map "^0.6.1"
-
 config-chain@^1.1.11:
   version "1.1.12"
   resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
@@ -1789,6 +1768,11 @@ dir-glob@^3.0.1:
   dependencies:
     path-type "^4.0.0"
 
+dlv@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
+  integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
+
 dom-helpers@^5.0.1:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b"
@@ -2105,13 +2089,6 @@ ext@^1.1.2:
   dependencies:
     type "^2.0.0"
 
-extend-shallow@^1.1.2:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-1.1.4.tgz#19d6bf94dfc09d76ba711f39b872d21ff4dd9071"
-  integrity sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=
-  dependencies:
-    kind-of "^1.1.0"
-
 extend-shallow@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
@@ -2156,7 +2133,7 @@ extract-zip@^1.0.3:
     mkdirp "^0.5.4"
     yauzl "^2.10.0"
 
-fancy-log@^1.3.2, fancy-log@^1.3.3:
+fancy-log@^1.3.2:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.3.tgz#dbc19154f558690150a23953a0adbd035be45fc7"
   integrity sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==
@@ -2171,7 +2148,7 @@ fast-deep-equal@^3.1.1:
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
   integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
 
-fast-glob@^3.1.1, fast-glob@^3.2.5:
+fast-glob@^3.1.1, fast-glob@^3.2.4, fast-glob@^3.2.5:
   version "3.2.5"
   resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661"
   integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==
@@ -2188,6 +2165,11 @@ fast-levenshtein@^1.0.0:
   resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz#e6a754cc8f15e58987aa9cbd27af66fd6f4e5af9"
   integrity sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=
 
+fast-sort@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/fast-sort/-/fast-sort-2.2.0.tgz#20903763531fbcbb41c9df5ab1bf5f2cefc8476a"
+  integrity sha512-W7zqnn2zsYoQA87FKmYtgOsbJohOrh7XrtZrCVHN5XZKqTBTv5UG+rSS3+iWbg/nepRQUOu+wnas8BwtK8kiCg==
+
 fastdom@^1.0.9:
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/fastdom/-/fastdom-1.0.10.tgz#4f2c7c9b24e7e249fc70c63131842b859b92bf09"
@@ -2351,7 +2333,7 @@ fs-extra@^8.1.0:
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
-fs-extra@^9.0.0, fs-extra@^9.0.1:
+fs-extra@^9.0.0, fs-extra@^9.1.0:
   version "9.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
   integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
@@ -2672,34 +2654,6 @@ gulp-cli@^2.2.0:
     v8flags "^3.2.0"
     yargs "^7.1.0"
 
-gulp-concat@^2.6.1:
-  version "2.6.1"
-  resolved "https://registry.yarnpkg.com/gulp-concat/-/gulp-concat-2.6.1.tgz#633d16c95d88504628ad02665663cee5a4793353"
-  integrity sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=
-  dependencies:
-    concat-with-sourcemaps "^1.0.0"
-    through2 "^2.0.0"
-    vinyl "^2.0.0"
-
-gulp-postcss@^9.0.0:
-  version "9.0.0"
-  resolved "https://registry.yarnpkg.com/gulp-postcss/-/gulp-postcss-9.0.0.tgz#2ade18809ab475dae743a88bd6501af0b04ee54e"
-  integrity sha512-5mSQ9CK8salSagrXgrVyILfEMy6I5rUGPRiR9rVjgJV9m/rwdZYUhekMr+XxDlApfc5ZdEJ8gXNZrU/TsgT5dQ==
-  dependencies:
-    fancy-log "^1.3.3"
-    plugin-error "^1.0.1"
-    postcss-load-config "^2.1.1"
-    vinyl-sourcemaps-apply "^0.2.1"
-
-gulp-remember@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/gulp-remember/-/gulp-remember-1.0.1.tgz#cc3aab2a04a623614375571ca464d13e87502bfe"
-  integrity sha1-zDqrKgSmI2FDdVccpGTRPodQK/4=
-  dependencies:
-    fancy-log "^1.3.2"
-    plugin-error "^0.1.2"
-    through2 "^0.5.0"
-
 gulp@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/gulp/-/gulp-4.0.2.tgz#543651070fd0f6ab0a0650c6a3e6ff5a7cb09caa"
@@ -2887,13 +2841,6 @@ immediate@~3.0.5:
   resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
   integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
 
-import-cwd@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
-  integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=
-  dependencies:
-    import-from "^2.1.0"
-
 import-cwd@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-3.0.0.tgz#20845547718015126ea9b3676b7592fb8bd4cf92"
@@ -2917,13 +2864,6 @@ import-fresh@^3.2.1:
     parent-module "^1.0.0"
     resolve-from "^4.0.0"
 
-import-from@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1"
-  integrity sha1-M1238qev/VOqpHHUuAId7ja387E=
-  dependencies:
-    resolve-from "^3.0.0"
-
 import-from@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966"
@@ -3322,11 +3262,6 @@ is-windows@^1.0.1, is-windows@^1.0.2:
   resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
   integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
 
[email protected]:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
-  integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
-
 [email protected], isarray@^1.0.0, isarray@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -3442,11 +3377,6 @@ keyv@^3.0.0:
   dependencies:
     json-buffer "3.0.0"
 
-kind-of@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44"
-  integrity sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=
-
 kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
@@ -3614,6 +3544,11 @@ lodash.toarray@^4.4.0:
   resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
   integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE=
 
+lodash.topath@^4.5.2:
+  version "4.5.2"
+  resolved "https://registry.yarnpkg.com/lodash.topath/-/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009"
+  integrity sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=
+
 lodash.uniq@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
@@ -3911,10 +3846,10 @@ mkdirp@^0.5.4, mkdirp@~0.5.1:
   dependencies:
     minimist "^1.2.5"
 
-mldoc@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-0.5.0.tgz#f29f5fae518219c13772184fbac5c3ebb93fb339"
-  integrity sha512-DRjC0iIciSn+MHTUFj7M1kSf2A+klqA010AfqMqP58x/TQ/tPsYIGwm+kN9bUfD/buGnarOygTXKFqGQCYe3Xg==
+mldoc@0.5.5:
+  version "0.5.5"
+  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-0.5.5.tgz#cb7eea471adc94e1c7858d4ae772ddabe0a75753"
+  integrity sha512-acseZvvwzLNlvp6/fZzqP5rqS80keWOK1XCreem5eXJNxLtJm+xIqzEJVcsmeyFS1Leya1gPT7dMcCUOhEr26g==
   dependencies:
     yargs "^12.0.2"
 
@@ -4141,7 +4076,7 @@ object-copy@^0.1.0:
     define-property "^0.2.5"
     kind-of "^3.0.3"
 
-object-hash@^2.0.3:
+object-hash@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.1.1.tgz#9447d0279b4fcf80cff3259bf66a1dc73afabe09"
   integrity sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ==
@@ -4533,7 +4468,7 @@ pinkie@^2.0.0:
   resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
   integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
 
[email protected], plugin-error@^1.0.1:
[email protected]:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c"
   integrity sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==
@@ -4543,17 +4478,6 @@ [email protected], plugin-error@^1.0.1:
     arr-union "^3.1.0"
     extend-shallow "^3.0.2"
 
-plugin-error@^0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace"
-  integrity sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=
-  dependencies:
-    ansi-cyan "^0.1.1"
-    ansi-red "^0.1.1"
-    arr-diff "^1.0.1"
-    arr-union "^2.0.1"
-    extend-shallow "^1.1.2"
-
 posix-character-classes@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
@@ -4568,10 +4492,10 @@ postcss-calc@^7.0.1:
     postcss-selector-parser "^6.0.2"
     postcss-value-parser "^4.0.2"
 
[email protected].0:
-  version "8.3.0"
-  resolved "https://registry.yarnpkg.com/postcss-cli/-/postcss-cli-8.3.0.tgz#fa06c96cbd0620768c788cae74ba462622a9013c"
-  integrity sha512-GqWohD9VmH+LCe+xsv6VCdcgNylNBmsrbxJlyXUGteGGdcmazj2YxSiJMUmQpg8pE6LRox9idtsTB7JZq5a+rw==
[email protected].1:
+  version "8.3.1"
+  resolved "https://registry.yarnpkg.com/postcss-cli/-/postcss-cli-8.3.1.tgz#865dad08300ac59ae9cecb7066780aa81c767a77"
+  integrity sha512-leHXsQRq89S3JC9zw/tKyiVV2jAhnfQe0J8VI4eQQbUjwIe0XxVqLrR+7UsahF1s9wi4GlqP6SJ8ydf44cgF2Q==
   dependencies:
     chalk "^4.0.0"
     chokidar "^3.3.0"
@@ -4650,6 +4574,24 @@ postcss-html@^0.36.0:
   dependencies:
     htmlparser2 "^3.10.0"
 
+postcss-import-ext-glob@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/postcss-import-ext-glob/-/postcss-import-ext-glob-2.0.1.tgz#9e22f9fc9722f7e7206fde77d594e93f6c57163e"
+  integrity sha512-cCvzsZBPuhLCOAfkPeBnJ31uz5azlAjNb5Aug1f2nlomgZK+WD7Uwfrk+epFU9PI20rsMAineDUK4Ty+jEQHcg==
+  dependencies:
+    fast-glob "^3.2.4"
+    fast-sort "^2.2.0"
+    postcss-value-parser "^4.1.0"
+
+postcss-import@^14.0.0:
+  version "14.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.0.0.tgz#3ed1dadac5a16650bde3f4cdea6633b9c3c78296"
+  integrity sha512-gFDDzXhqr9ELmnLHgCC3TbGfA6Dm/YMb/UN8/f7Uuq4fL7VTk2vOIj6hwINEwbokEmp123bLD7a5m+E+KIetRg==
+  dependencies:
+    postcss-value-parser "^4.0.0"
+    read-cache "^1.0.0"
+    resolve "^1.1.7"
+
 postcss-js@^3.0.3:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-3.0.3.tgz#2f0bd370a2e8599d45439f6970403b5873abda33"
@@ -4665,14 +4607,6 @@ postcss-less@^3.1.4:
   dependencies:
     postcss "^7.0.14"
 
-postcss-load-config@^2.1.1:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a"
-  integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==
-  dependencies:
-    cosmiconfig "^5.0.0"
-    import-cwd "^2.0.0"
-
 postcss-load-config@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.0.0.tgz#850bb066edd65b734329eacf83af0c0764226c87"
@@ -4748,6 +4682,13 @@ postcss-minify-selectors@^4.0.2:
     postcss "^7.0.0"
     postcss-selector-parser "^3.0.0"
 
[email protected]:
+  version "5.0.5"
+  resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.5.tgz#f0a107d33a9fab11d7637205f5321e27223e3603"
+  integrity sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew==
+  dependencies:
+    postcss-selector-parser "^6.0.4"
+
 postcss-nested@^5.0.1:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.3.tgz#2f46d77a06fc98d9c22344fd097396f5431386db"
@@ -4952,26 +4893,17 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0:
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
   integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
 
-postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
+postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
   integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
 
-postcss@7.0.32:
-  version "7.0.32"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d"
-  integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==
+postcss@8.2.8:
+  version "8.2.8"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.8.tgz#0b90f9382efda424c4f0f69a2ead6f6830d08ece"
+  integrity sha512-1F0Xb2T21xET7oQV9eKuctbM9S7BC0fetoHCc4H13z0PT6haiRLP4T0ZY4XWh7iLP0usgqykT6p9B2RtOf4FPw==
   dependencies:
-    chalk "^2.4.2"
-    source-map "^0.6.1"
-    supports-color "^6.1.0"
-
[email protected]:
-  version "8.2.1"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.1.tgz#eabc5557c4558059b9d9e5b15bce7ffa9089c2a8"
-  integrity sha512-RhsqOOAQzTgh1UB/IZdca7F9WDb7SUCR2Vnv1x7DbvuuggQIpoDwjK+q0rzoPffhYvWNKX5JSwS4so4K3UC6vA==
-  dependencies:
-    colorette "^1.2.1"
+    colorette "^1.2.2"
     nanoid "^3.1.20"
     source-map "^0.6.1"
 
@@ -5093,14 +5025,14 @@ punycode@^2.1.0:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
 
-purgecss@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-3.0.0.tgz#039c191871bb999894222a00c4c8b179fccdb043"
-  integrity sha512-t3FGCwyX9XWV3ffvnAXTw6Y3Z9kNlcgm14VImNK66xKi5sdqxSA2I0SFYxtmZbAKuIZVckPdazw5iKL/oY/2TA==
+purgecss@4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-4.0.2.tgz#7281f233c08b4f41e1c5d85c66a2d064e6d7e1b3"
+  integrity sha512-6J1zOEAZJX6VbfcaJHgdQf4uPhxVXvHz7dGgWYXLOI9q7QFZ5feh8NZ2+G3ysii/Sr8OyUe5yhQ5Z/xZ5gIRnQ==
   dependencies:
     commander "^6.0.0"
     glob "^7.0.0"
-    postcss "7.0.32"
+    postcss "^8.2.1"
     postcss-selector-parser "^6.0.2"
 
 purgecss@^3.1.3:
@@ -5133,6 +5065,11 @@ quick-lru@^4.0.1:
   resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
   integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
 
+quick-lru@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
+  integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
+
 randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -5273,16 +5210,6 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
-readable-stream@~1.0.17:
-  version "1.0.34"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
-  integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.1"
-    isarray "0.0.1"
-    string_decoder "~0.10.x"
-
 readdirp@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
@@ -5319,7 +5246,7 @@ redent@^3.0.0:
     indent-string "^4.0.0"
     strip-indent "^3.0.0"
 
-reduce-css-calc@^2.1.6:
+reduce-css-calc@^2.1.8:
   version "2.1.8"
   resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz#7ef8761a28d614980dc0c982f772c93f7a99de03"
   integrity sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==
@@ -5933,11 +5860,6 @@ string_decoder@^1.0.0, string_decoder@^1.1.1:
   dependencies:
     safe-buffer "~5.2.0"
 
-string_decoder@~0.10.x:
-  version "0.10.31"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
-  integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
-
 string_decoder@~1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
@@ -6147,30 +6069,30 @@ table@^6.0.7:
     slice-ansi "^4.0.0"
     string-width "^4.2.0"
 
[email protected].1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.0.1.tgz#8d336917819341d1018208e8b3ed8cbc46e6b643"
-  integrity sha512-57G3jdcVBWTPkHCNSAfDAo1Qp2Nkr4H6WnLD0luNFh1td+KwQp9FOVcqj0SYBH6qwVQJawzT+0/zLxzKmyznGw==
[email protected].3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.0.3.tgz#f8d07797d1f89dc4b171673c26237b58783c2c86"
+  integrity sha512-s8NEqdLBiVbbdL0a5XwTb8jKmIonOuI4RMENEcKLR61jw6SdKvBss7NWZzwCaD+ZIjlgmesv8tmrjXEp7C0eAQ==
   dependencies:
-    "@fullhuman/postcss-purgecss" "^3.0.0"
+    "@fullhuman/postcss-purgecss" "^3.1.3"
     bytes "^3.0.0"
     chalk "^4.1.0"
     color "^3.1.3"
     detective "^5.2.0"
     didyoumean "^1.2.1"
-    fs-extra "^9.0.1"
+    fs-extra "^9.1.0"
     html-tags "^3.1.0"
     lodash "^4.17.20"
     modern-normalize "^1.0.0"
     node-emoji "^1.8.1"
-    object-hash "^2.0.3"
+    object-hash "^2.1.1"
     postcss-functions "^3"
     postcss-js "^3.0.3"
     postcss-nested "^5.0.1"
     postcss-selector-parser "^6.0.4"
     postcss-value-parser "^4.1.0"
     pretty-hrtime "^1.0.3"
-    reduce-css-calc "^2.1.6"
+    reduce-css-calc "^2.1.8"
     resolve "^1.19.0"
 
 through2-filter@^3.0.0:
@@ -6188,14 +6110,6 @@ [email protected]:
   dependencies:
     readable-stream "2 || 3"
 
-through2@^0.5.0:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/through2/-/through2-0.5.1.tgz#dfdd012eb9c700e2323fd334f38ac622ab372da7"
-  integrity sha1-390BLrnHAOIyP9M084rGIqs3Lac=
-  dependencies:
-    readable-stream "~1.0.17"
-    xtend "~3.0.0"
-
 through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
@@ -6644,7 +6558,7 @@ vinyl-sourcemap@^1.1.0:
     remove-bom-buffer "^3.0.0"
     vinyl "^2.0.0"
 
[email protected], vinyl-sourcemaps-apply@^0.2.1:
[email protected]:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705"
   integrity sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=
@@ -6738,11 +6652,6 @@ xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1:
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
   integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
 
-xtend@~3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a"
-  integrity sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=
-
 y18n@^3.2.1:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"