diff --git a/.github/workflows/build-desktop-release.yml b/.github/workflows/build-desktop-release.yml index c30c90b43a..4572af78e0 100644 --- a/.github/workflows/build-desktop-release.yml +++ b/.github/workflows/build-desktop-release.yml @@ -9,23 +9,23 @@ on: description: 'Build Target ("nightly"/"beta")' type: string required: true - default: "nightly" + default: "beta" git-ref: - description: "Release Git Ref" + description: "Release Git Ref(master)" required: true default: "master" is-draft: - description: 'Draft Release? ' + description: 'Draft Release? (Beta only, Nightly will always be a non-draft)' type: boolean required: true default: true is-pre-release: - description: 'Pre Release?' + description: 'Pre Release? (labeled as "PreRelease")' type: boolean required: true default: true - # schedule: # Every workday at the noon (UTC) we run a scheduled nightly build - # - cron: '0 12 * * MON-FRI' + schedule: # Every workday at the 2 P.M. (UTC) we run a scheduled nightly build + - cron: '0 14 * * MON-FRI' env: CLOJURE_VERSION: '1.10.1.763' @@ -213,7 +213,7 @@ jobs: name: logseq-win64-builds path: builds - build-macos: + build-macos-x64: needs: [ compile-cljs ] runs-on: macos-11 @@ -277,8 +277,61 @@ jobs: mv static/out/make/Logseq.dmg ./builds/Logseq-darwin-x64-${{ steps.ref.outputs.version }}.dmg mv static/out/make/zip/darwin/x64/*.zip ./builds/Logseq-darwin-x64-${{ steps.ref.outputs.version }}.zip + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: logseq-darwin-x64-builds + path: builds + + build-macos-arm64: + needs: [ compile-cljs ] + runs-on: macos-11 + + steps: + - name: Download The Static Asset + uses: actions/download-artifact@v2 + with: + name: static + path: static + + - name: Retrieve tag version + id: ref + run: | + pkgver=$(cat ./static/VERSION) + echo ::set-output name=version::$pkgver + + - name: Install Node.js, NPM and Yarn + uses: actions/setup-node@v2 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: Cache yarn cache directory + uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-arm64-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-arm64-yarn- + + - name: Signing By Apple Developer ID + uses: apple-actions/import-codesign-certs@v1 + with: + p12-file-base64: ${{ secrets.APPLE_CERTIFICATES_P12 }} + p12-password: ${{ secrets.APPLE_CERTIFICATES_P12_PASSWORD }} + + # - name: Cache Node Modules + # uses: actions/cache@v2 + # with: + # path: | + # **/node_modules + # key: ${{ runner.os }}-node-modules + - name: Build/Release Electron App for arm64 - run: yarn electron:make-macos-arm64 + run: yarn install && yarn electron:make-macos-arm64 working-directory: ./static env: APPLE_ID: ${{ secrets.APPLE_ID_EMAIL }} @@ -287,25 +340,82 @@ jobs: - name: Save arm64 artifacts run: | + mkdir -p builds mv static/out/make/Logseq.dmg ./builds/Logseq-darwin-arm64-${{ steps.ref.outputs.version }}.dmg mv static/out/make/zip/darwin/arm64/*.zip ./builds/Logseq-darwin-arm64-${{ steps.ref.outputs.version }}.zip - name: Upload Artifact uses: actions/upload-artifact@v2 with: - name: logseq-darwin-builds + name: logseq-darwin-arm64-builds path: builds + nightly-release: + if: ${{ github.event_name == 'schedule' || github.event.inputs.build-target == 'nightly' }} + needs: [ build-macos-x64, build-macos-arm64, build-linux, build-windows ] + runs-on: ubuntu-18.04 + steps: + - name: Download MacOS x64 Artifacts + uses: actions/download-artifact@v2 + with: + name: logseq-darwin-x64-builds + path: ./ + + - name: Download MacOS arm64 Artifacts + uses: actions/download-artifact@v2 + with: + name: logseq-darwin-arm64-builds + path: ./ + + - name: Download The Linux Artifacts + uses: actions/download-artifact@v2 + with: + name: logseq-linux-builds + path: ./ + + - name: Download The Windows Artifact + uses: actions/download-artifact@v2 + with: + name: logseq-win64-builds + path: ./ + + - name: List files + run: ls -rl + + - name: Update Nightly Release + uses: andelf/nightly-release@main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: nightly + name: 'Desktop App Nightly Relase $$' + draft: false + prerelease: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.is-pre-release) || (github.event_name == 'schedule')}} + body: "This is a nightly release of the Logseq desktop app." + files: | + ./VERSION + ./SHA256SUMS.txt + ./*.zip + ./*.dmg + ./*.exe + ./*.AppImage + release: # NOTE: For now, we only have beta channel to be released on Github - if: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.build-target == 'beta') }} - needs: [ build-macos, build-linux, build-windows ] + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.build-target != 'nightly' }} + needs: [ build-macos-x64, build-macos-arm64, build-linux, build-windows ] runs-on: ubuntu-18.04 steps: - - name: Download MacOS Artifacts + - name: Download MacOS x64 Artifacts uses: actions/download-artifact@v2 with: - name: logseq-darwin-builds + name: logseq-darwin-x64-builds + path: ./ + + - name: Download MacOS arm64 Artifacts + uses: actions/download-artifact@v2 + with: + name: logseq-darwin-arm64-builds path: ./ - name: Download The Linux Artifacts @@ -344,8 +454,8 @@ jobs: tag_name: ${{ steps.ref.outputs.version }} name: Desktop APP ${{ steps.ref.outputs.version }} (Beta Testing) body: "TODO: Fill this changelog. Sorry for the inconvenience!" - draft: ${{ github.event.inputs.is-draft == true || github.event_name == 'schedule' }} - prerelease: ${{ github.event.inputs.is-pre-release == true || github.event_name == 'schedule' }} + draft: ${{ github.event.inputs.is-draft }} + prerelease: ${{ github.event.inputs.is-pre-release }} files: | ./VERSION ./SHA256SUMS.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3189a8f9ba..43676d6155 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,7 +83,6 @@ jobs: node static/tests.js - name: Run Playwright test - if: github.event_name == 'pull_request' run: | yarn release (cd static && yarn install && yarn rebuild:better-sqlite3) diff --git a/deps.edn b/deps.edn index a0b2220fbf..75b633eb65 100755 --- a/deps.edn +++ b/deps.edn @@ -37,7 +37,8 @@ camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.2"} instaparse/instaparse {:mvn/version "1.4.10"} nubank/workspaces {:mvn/version "1.1.1"} - frankiesardo/linked {:mvn/version "1.3.0"}} + frankiesardo/linked {:mvn/version "1.3.0"} + org.clojars.mmb90/cljs-cache {:mvn/version "0.1.4"}} :aliases {:cljs {:extra-paths ["src/dev-cljs/" "src/test/" "src/electron/"] :extra-deps {org.clojure/clojurescript {:mvn/version "1.10.879"} diff --git a/e2e-tests/basic.spec.ts b/e2e-tests/basic.spec.ts index 0ef95ac26f..4799c1d949 100644 --- a/e2e-tests/basic.spec.ts +++ b/e2e-tests/basic.spec.ts @@ -1,6 +1,6 @@ import { expect } from '@playwright/test' import { test } from './fixtures' -import { randomString, createRandomPage, openSidebar, newBlock, lastBlock } from './utils' +import { randomString, createRandomPage, newBlock } from './utils' test('render app', async ({ page }) => { @@ -12,11 +12,25 @@ test('render app', async ({ page }) => { expect(await page.title()).toMatch(/^Logseq.*?/) }) -test('open sidebar', async ({ page }) => { - await openSidebar(page) +test('toggle sidebar', async ({ page }) => { + let sidebar = page.locator('#left-sidebar') + // Left sidebar is toggled by `is-open` class + if (/is-open/.test(await sidebar.getAttribute('class'))) { + await page.click('#left-menu.button') + expect(await sidebar.getAttribute('class')).not.toMatch(/is-open/) + } else { + await page.click('#left-menu.button') + expect(await sidebar.getAttribute('class')).toMatch(/is-open/) + await page.click('#left-menu.button') + expect(await sidebar.getAttribute('class')).not.toMatch(/is-open/) + } + + await page.click('#left-menu.button') + + expect(await sidebar.getAttribute('class')).toMatch(/is-open/) + await page.waitForSelector('#left-sidebar .left-sidebar-inner', { state: 'visible' }) await page.waitForSelector('#left-sidebar a:has-text("New page")', { state: 'visible' }) - await page.waitForSelector('#left-sidebar >> text=Journals', { state: 'visible' }) }) test('search', async ({ page }) => { diff --git a/e2e-tests/code-editing.spec.ts b/e2e-tests/code-editing.spec.ts new file mode 100644 index 0000000000..29b306040e --- /dev/null +++ b/e2e-tests/code-editing.spec.ts @@ -0,0 +1,179 @@ +import { expect } from '@playwright/test' +import { test } from './fixtures' +import { createRandomPage, escapeToCodeEditor, escapeToBlockEditor } from './utils' + +/** + * NOTE: CodeMirror is a complex library that requires a lot of setup to work. + * This test suite is designed to test the basic functionality of the editor. + * It is not intended to test the full functionality of CodeMirror. + * For more information, see: https://codemirror.net/doc/manual.html + */ + +test('switch code editing mode', async ({ page }) => { + await createRandomPage(page) + + // NOTE: ` will trigger auto-pairing in Logseq + // NOTE: ( will trigger auto-pairing in CodeMirror + // NOTE: waitForTimeout is needed to ensure that the hotkey handler is finished (shift+enter) + // NOTE: waitForTimeout is needed to ensure that the CodeMirror editor is fully loaded and unloaded + // NOTE: multiple textarea elements are existed in the editor, be careful to select the right one + + // code block with 0 line + await page.type(':nth-match(textarea, 1)', '```clojure\n') + // line number: 1 + await page.waitForSelector('.CodeMirror pre', { state: 'visible' }) + expect(await page.locator('.CodeMirror-gutter-wrapper .CodeMirror-linenumber').innerText()).toBe('1') + // lang label: clojure + expect(await page.innerText('.block-body .extensions__code-lang')).toBe('clojure') + + await page.press('.CodeMirror textarea', 'Escape') + await page.waitForSelector('.CodeMirror pre', { state: 'hidden' }) + expect(await page.inputValue(':nth-match(textarea, 1)')).toBe('```clojure\n```') + + await page.waitForTimeout(500) + await page.press(':nth-match(textarea, 1)', 'Escape') + await page.waitForSelector('.CodeMirror pre', { state: 'visible' }) + + // NOTE: must wait here, await loading of CodeMirror editor + await page.waitForTimeout(500) + await page.click('.CodeMirror pre') + await page.waitForTimeout(500) + + await page.type('.CodeMirror textarea', '(+ 1 1') + await page.press('.CodeMirror textarea', 'Escape') + await page.waitForSelector('.CodeMirror pre', { state: 'hidden' }) + expect(await page.inputValue('.block-editor textarea')).toBe('```clojure\n(+ 1 1)\n```') + + await page.waitForTimeout(500) // editor unloading + await page.press('.block-editor textarea', 'Escape') + await page.waitForTimeout(500) // editor loading + // click position is estimated to be at the begining of the first line + await page.click('.CodeMirror pre', { position: { x: 1, y: 5 } }) + await page.waitForTimeout(500) + + await page.type('.CodeMirror textarea', ';; comment\n\n \n') + + await page.press('.CodeMirror textarea', 'Escape') + await page.waitForSelector('.CodeMirror pre', { state: 'hidden' }) + expect(await page.inputValue('.block-editor textarea')).toBe('```clojure\n;; comment\n\n \n(+ 1 1)\n```') + + await page.waitForTimeout(500) +}) + + +test('convert from block content to code', async ({ page }) => { + await createRandomPage(page) + + await page.type('.block-editor textarea', '```') + await page.press('.block-editor textarea', 'Shift+Enter') + await page.waitForTimeout(100) // wait for hotkey handler + await page.press('.block-editor textarea', 'Escape') + await page.waitForSelector('.CodeMirror pre', { state: 'visible' }) + + await page.waitForTimeout(500) + await page.click('.CodeMirror pre') + await page.waitForTimeout(500) + expect(await page.locator('.CodeMirror-gutter-wrapper .CodeMirror-linenumber >> nth=-1').innerText()).toBe('1') + + await page.press('.CodeMirror textarea', 'Escape') + await page.waitForTimeout(500) + + expect(await page.inputValue('.block-editor textarea')).toBe('```\n```') + + // reset block, code block with 1 line + await page.fill('.block-editor textarea', '```\n\n```') + await page.waitForTimeout(500) // wait for fill + await escapeToCodeEditor(page) + expect(await page.locator('.CodeMirror-gutter-wrapper .CodeMirror-linenumber >> nth=-1').innerText()).toBe('1') + await escapeToBlockEditor(page) + expect(await page.inputValue('.block-editor textarea')).toBe('```\n\n```') + + // reset block, code block with 2 line + await page.fill('.block-editor textarea', '```\n\n\n```') + await page.waitForTimeout(500) + await escapeToCodeEditor(page) + expect(await page.locator('.CodeMirror-gutter-wrapper .CodeMirror-linenumber >> nth=-1').innerText()).toBe('2') + await escapeToBlockEditor(page) + expect(await page.inputValue('.block-editor textarea')).toBe('```\n\n\n```') + + await page.fill('.block-editor textarea', '```\n indented\nsecond line\n\n```') + await page.waitForTimeout(500) + await escapeToCodeEditor(page) + await escapeToBlockEditor(page) + expect(await page.inputValue('.block-editor textarea')).toBe('```\n indented\nsecond line\n\n```') + + await page.fill('.block-editor textarea', '```\n indented\n indented\n```') + await page.waitForTimeout(500) + await escapeToCodeEditor(page) + await escapeToBlockEditor(page) + expect(await page.inputValue('.block-editor textarea')).toBe('```\n indented\n indented\n```') +}) + +test('code block mixed input source', async ({ page }) => { + await createRandomPage(page) + + await page.fill('.block-editor textarea', '```\n ABC\n```') + await page.waitForTimeout(500) // wait for fill + await escapeToCodeEditor(page) + await page.type('.CodeMirror textarea', ' DEF\nGHI') + + await page.waitForTimeout(500) + await page.press('.CodeMirror textarea', 'Escape') + await page.waitForTimeout(500) + // NOTE: auto-indent is on + expect(await page.inputValue('.block-editor textarea')).toBe('```\n ABC DEF\n GHI\n```') +}) + +test('code block with text around', async ({ page }) => { + await createRandomPage(page) + + await page.fill('.block-editor textarea', 'Heading\n```\n```\nFooter') + await page.waitForTimeout(500) + await escapeToCodeEditor(page) + await page.type('.CodeMirror textarea', 'first\n second') + + await page.waitForTimeout(500) + await page.press('.CodeMirror textarea', 'Escape') + await page.waitForTimeout(500) + expect(await page.inputValue('.block-editor textarea')).toBe('Heading\n```\nfirst\n second\n```\nFooter') +}) + +test('multiple code block', async ({ page }) => { + await createRandomPage(page) + + // NOTE: the two code blocks are of the same content + await page.fill('.block-editor textarea', 'δΈ­ζ–‡ Heading\n```clojure\n```\nMiddle πŸš€\n```clojure\n```\nFooter') + await page.waitForTimeout(500) + + await page.press('.block-editor textarea', 'Escape') + await page.waitForSelector('.CodeMirror pre', { state: 'visible' }) + + // first + await page.waitForTimeout(500) + await page.click('.CodeMirror pre >> nth=0') + await page.waitForTimeout(500) + + await page.type('.CodeMirror textarea >> nth=0', ':key-test\n', { strict: true }) + await page.waitForTimeout(500) + + await page.press('.CodeMirror textarea >> nth=0', 'Escape') + await page.waitForTimeout(500) + expect(await page.inputValue('.block-editor textarea')) + .toBe('δΈ­ζ–‡ Heading\n```clojure\n:key-test\n\n```\nMiddle πŸš€\n```clojure\n```\nFooter') + + // second + await page.press('.block-editor textarea', 'Escape') + await page.waitForSelector('.CodeMirror pre', { state: 'visible' }) + + await page.waitForTimeout(500) + await page.click('.CodeMirror pre >> nth=1') + await page.waitForTimeout(500) + + await page.type('.CodeMirror textarea >> nth=1', '\n :key-test ζ—₯本θͺž\n', { strict: true }) + await page.waitForTimeout(500) + + await page.press('.CodeMirror textarea >> nth=1', 'Escape') + await page.waitForTimeout(500) + expect(await page.inputValue('.block-editor textarea')) + .toBe('δΈ­ζ–‡ Heading\n```clojure\n:key-test\n\n```\nMiddle πŸš€\n```clojure\n\n :key-test ζ—₯本θͺž\n\n```\nFooter') +}) diff --git a/e2e-tests/hotkey.spec.ts b/e2e-tests/hotkey.spec.ts index bd9013abaa..a9af3df407 100644 --- a/e2e-tests/hotkey.spec.ts +++ b/e2e-tests/hotkey.spec.ts @@ -1,6 +1,6 @@ import { expect } from '@playwright/test' import { test } from './fixtures' -import { createRandomPage, newBlock, lastBlock, appFirstLoaded, IsMac, IsLinux } from './utils' +import { createRandomPage, newBlock, lastBlock, IsMac, IsLinux } from './utils' test('open search dialog', async ({ page }) => { if (IsMac) { diff --git a/e2e-tests/utils.ts b/e2e-tests/utils.ts index 890ef8e09a..d8311d694c 100644 --- a/e2e-tests/utils.ts +++ b/e2e-tests/utils.ts @@ -22,18 +22,10 @@ export async function appFirstLoaded(page: Page) { await page.waitForSelector('text=This is a demo graph, changes will not be saved until you open a local folder') } -export async function openSidebar(page: Page) { - let sidebarVisible = await page.isVisible('#left-sidebar .left-sidebar-inner') - if (!sidebarVisible) { - await page.click('#left-menu.button') - } - await page.waitForSelector('#left-sidebar .left-sidebar-inner', { state: 'visible' }) -} - export async function createRandomPage(page: Page) { const randomTitle = randomString(20) - // Click #left-sidebar a:has-text("New page") + // Click #search-button await page.click('#search-button') // Fill [placeholder="Search or create page"] await page.fill('[placeholder="Search or create page"]', randomTitle) @@ -60,3 +52,23 @@ export async function newBlock(page: Page): Promise { return page.locator(':nth-match(textarea, 1)') } + +export async function escapeToCodeEditor(page: Page): Promise { + await page.press('.block-editor textarea', 'Escape') + await page.waitForSelector('.CodeMirror pre', { state: 'visible' }) + + await page.waitForTimeout(500) + await page.click('.CodeMirror pre') + await page.waitForTimeout(500) + + await page.waitForSelector('.CodeMirror textarea', { state: 'visible' }) +} + +export async function escapeToBlockEditor(page: Page): Promise { + await page.waitForTimeout(500) + await page.click('.CodeMirror pre') + await page.waitForTimeout(500) + + await page.press('.CodeMirror textarea', 'Escape') + await page.waitForTimeout(500) +} diff --git a/externs.js b/externs.js index c049ac223d..36017644f2 100644 --- a/externs.js +++ b/externs.js @@ -118,6 +118,15 @@ dummy.convertToViewportRectangle = function() {}; dummy.init = function() {}; dummy.commit = function() {}; dummy.raw = function() {}; +dummy.onHeadersReceived = function() {}; +dummy.responseHeaders = function() {}; +dummy.velocityDecay = function() {}; +dummy.velocityDecay = function() {}; +dummy.updatePosition = function() {}; +dummy.getNodesObjects = function() {}; +dummy.getEdgesObjects = function() {}; +dummy.alphaTarget = function() {}; +dummy.restart = function() {}; /** * @typedef {{ diff --git a/package.json b/package.json index 7a4101d71b..83f4ccaa26 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "d3-force": "3.0.0", "diff": "5.0.0", "diff-match-patch": "1.0.5", - "electron": "16.0.4", + "electron": "15.1.2", "fs": "0.0.1-security", "fs-extra": "9.1.0", "fuse.js": "6.4.6", @@ -92,9 +92,9 @@ "ignore": "5.1.8", "is-svg": "4.2.2", "jszip": "3.5.0", - "mldoc": "1.2.4", + "mldoc": "1.2.5", "path": "0.12.7", - "pixi-graph-fork": "0.1.6", + "pixi-graph-fork": "0.2.0", "pixi.js": "6.2.0", "posthog-js": "1.10.2", "react": "17.0.2", diff --git a/resources/css/common.css b/resources/css/common.css index 6da0b224e6..1d55a0dc1c 100644 --- a/resources/css/common.css +++ b/resources/css/common.css @@ -1123,13 +1123,6 @@ a.tooltip-priority { background: var(--ls-tertiary-background-color); } -.references-blocks .block-control { - margin-left: -22px; - @screen sm { - margin-left: 2px; - } -} - #head .fade-link { font-weight: 600; font-size: 13px; diff --git a/resources/package.json b/resources/package.json index 2a926e3889..423d1c7db0 100644 --- a/resources/package.json +++ b/resources/package.json @@ -1,6 +1,6 @@ { "name": "Logseq", - "version": "0.5.2", + "version": "0.5.4", "main": "electron.js", "author": "Logseq", "description": "A privacy-first, open-source platform for knowledge management and collaboration.", @@ -11,7 +11,7 @@ "electron:make": "electron-forge make", "electron:make-macos-arm64": "electron-forge make --platform=darwin --arch=arm64", "electron:publish:github": "electron-forge publish", - "rebuild:better-sqlite3": "electron-rebuild -v 16.0.4 -f -w better-sqlite3", + "rebuild:better-sqlite3": "electron-rebuild -v 15.1.2 -f -w better-sqlite3", "postinstall": "install-app-deps" }, "config": { diff --git a/src/electron/electron/core.cljs b/src/electron/electron/core.cljs index 2b137c75df..b5439e502e 100644 --- a/src/electron/electron/core.cljs +++ b/src/electron/electron/core.cljs @@ -15,7 +15,8 @@ ["electron-window-state" :as windowStateKeeper] [clojure.core.async :as async] [electron.state :as state] - [electron.git :as git])) + [electron.git :as git] + ["/electron/utils" :as utils])) (defonce LSP_SCHEME "lsp") (defonce LSP_PROTOCOL (str LSP_SCHEME "://")) @@ -295,6 +296,8 @@ *quitting? (atom false)] (.. logger (info (str "Logseq App(" (.getVersion app) ") Starting... "))) + (utils/disableXFrameOptions win) + (when (search/version-changed?) (search/rm-search-dir!)) diff --git a/src/electron/electron/fs_watcher.cljs b/src/electron/electron/fs_watcher.cljs index 38467975a4..a4a3bcdc11 100644 --- a/src/electron/electron/fs_watcher.cljs +++ b/src/electron/electron/fs_watcher.cljs @@ -10,7 +10,7 @@ ;; TODO: explore different solutions for different platforms ;; 1. https://github.com/Axosoft/nsfw -(defonce polling-interval 5000) +(defonce polling-interval 10000) (defonce file-watcher (atom nil)) (defonce file-watcher-chan "file-watcher") @@ -26,8 +26,7 @@ (clj->js {:ignored (fn [path] (utils/ignored-path? dir path)) - ;; :ignoreInitial false - :ignoreInitial true + :ignoreInitial false :ignorePermissionErrors true :interval polling-interval :binaryInterval polling-interval diff --git a/src/electron/electron/utils.js b/src/electron/electron/utils.js new file mode 100644 index 0000000000..4782124f0d --- /dev/null +++ b/src/electron/electron/utils.js @@ -0,0 +1,18 @@ +// workaround from https://github.com/electron/electron/issues/426#issuecomment-658901422 +// We set an intercept on incoming requests to disable x-frame-options +// headers. + +export const disableXFrameOptions = (win) => { + win.webContents.session.webRequest.onHeadersReceived({ urls: [ "*://*/*" ] }, + (d, c)=>{ + if(d.responseHeaders['X-Frame-Options']){ + delete d.responseHeaders['X-Frame-Options']; + } else if(d.responseHeaders['x-frame-options']) { + delete d.responseHeaders['x-frame-options']; + } + + c({cancel: false, responseHeaders: d.responseHeaders}); + } + ); + +}; diff --git a/src/main/frontend/components/block.cljs b/src/main/frontend/components/block.cljs index ff2aa84785..ab09f23768 100644 --- a/src/main/frontend/components/block.cljs +++ b/src/main/frontend/components/block.cljs @@ -1785,7 +1785,7 @@ (defn clock-summary-cp [block body] - [:span.text-right {:style {:max-width 100}} + [:div {:style {:max-width 100}} (when (and (state/enable-timetracking?) (or (= (:block/marker block) "DONE") (contains? #{"TODO" "LATER"} (:block/marker block)))) @@ -1812,7 +1812,7 @@ (rum/defc block-content < rum/reactive [config {:block/keys [uuid content children properties scheduled deadline format pre-block?] :as block} edit-input-id block-id slide?] (let [{:block/keys [title body] :as block} (if (:block/title block) block - (merge block (block/parse-title-and-body format pre-block? content))) + (merge block (block/parse-title-and-body uuid format pre-block? content))) collapsed? (get properties :collapsed) block-ref? (:block-ref? config) block-ref-with-title? (and block-ref? (seq title)) @@ -1841,51 +1841,48 @@ (merge attrs)) [:span - ;; .flex.relative {:style {:width "100%"}} - [:span - ;; .flex-1.flex-col.relative.block-content - [:span.flex.flex-row.justify-between - [:span - (cond - (seq title) - (build-block-title config block) + [:div.flex.flex-row.justify-between + [:div.flex-1 + (cond + (seq title) + (build-block-title config block) - :else - nil)] + :else + nil)] - (clock-summary-cp block body)] + (clock-summary-cp block body)] - (when (seq children) - (dnd-separator-wrapper block block-id slide? false true)) + (when (seq children) + (dnd-separator-wrapper block block-id slide? false true)) - (when deadline - (when-let [deadline-ast (block-handler/get-deadline-ast block)] - (timestamp-cp block "DEADLINE" deadline-ast))) + (when deadline + (when-let [deadline-ast (block-handler/get-deadline-ast block)] + (timestamp-cp block "DEADLINE" deadline-ast))) - (when scheduled - (when-let [scheduled-ast (block-handler/get-scheduled-ast block)] - (timestamp-cp block "SCHEDULED" scheduled-ast))) + (when scheduled + (when-let [scheduled-ast (block-handler/get-scheduled-ast block)] + (timestamp-cp block "SCHEDULED" scheduled-ast))) - (when (and (seq properties) - (let [hidden? (property/properties-built-in? properties)] - (not hidden?)) - (not block-ref?) - (not (:slide? config))) - (properties-cp config block)) + (when (and (seq properties) + (let [hidden? (property/properties-built-in? properties)] + (not hidden?)) + (not block-ref?) + (not (:slide? config))) + (properties-cp config block)) - (when (and (not block-ref-with-title?) (seq body)) - [:div.block-body {:style {:display (if (and collapsed? (seq title)) "none" "")}} - ;; TODO: consistent id instead of the idx (since it could be changed later) - (let [body (block/trim-break-lines! (:block/body block))] - (for [[idx child] (medley/indexed body)] - (when-let [block (markup-element-cp config child)] - (rum/with-key (block-child block) - (str uuid "-" idx)))))]) + (when (and (not block-ref-with-title?) (seq body)) + [:div.block-body {:style {:display (if (and collapsed? (seq title)) "none" "")}} + ;; TODO: consistent id instead of the idx (since it could be changed later) + (let [body (block/trim-break-lines! (:block/body block))] + (for [[idx child] (medley/indexed body)] + (when-let [block (markup-element-cp config child)] + (rum/with-key (block-child block) + (str uuid "-" idx)))))]) - (case (:block/warning block) - :multiple-blocks - [:p.warning.text-sm "Full content is not displayed, Logseq doesn't support multiple unordered lists or headings in a block."] - nil)]]])) + (case (:block/warning block) + :multiple-blocks + [:p.warning.text-sm "Full content is not displayed, Logseq doesn't support multiple unordered lists or headings in a block."] + nil)]])) (rum/defc block-refs-count < rum/reactive [block] @@ -2000,7 +1997,11 @@ parents-props (doall (for [{:block/keys [uuid name content] :as block} parents] (when-not name ; not page - (let [{:block/keys [title body]} (block/parse-title-and-body (:block/format block) (:block/pre-block? block) content)] + (let [{:block/keys [title body]} (block/parse-title-and-body + uuid + (:block/format block) + (:block/pre-block? block) + content)] [block (if (seq title) (->elem :span (map-inline config title)) @@ -2160,7 +2161,7 @@ (not= (select-keys (first (:rum/args old-state)) config-compare-keys) (select-keys (first (:rum/args new-state)) config-compare-keys)))))} [state config {:block/keys [uuid repo children pre-block? top? properties refs heading-level level type format content] :as block}] - (let [block (merge block (block/parse-title-and-body format pre-block? content)) + (let [block (merge block (block/parse-title-and-body uuid format pre-block? content)) body (:block/body block) blocks-container-id (:blocks-container-id config) config (update config :block merge block) @@ -2638,7 +2639,9 @@ [:div.opacity-50.font-medium (util/format ":%s:" (string/upper-case name))] [:div.opacity-50.font-medium - (logbook-cp lines) + (if (= name "logbook") + (logbook-cp lines) + (apply str lines)) [:div ":END:"]] {:default-collapsed? true :title-trigger? true})]]]) diff --git a/src/main/frontend/components/editor.cljs b/src/main/frontend/components/editor.cljs index e43200dbf6..307f945cc4 100644 --- a/src/main/frontend/components/editor.cljs +++ b/src/main/frontend/components/editor.cljs @@ -74,7 +74,9 @@ (not (contains? #{"Date picker" "Template" "Deadline" "Scheduled" "Upload an image"} command))))] (editor-handler/insert-command! id command-steps format - {:restore? restore-slash?})))) + {:restore? restore-slash?}) + (state/pub-event! [:instrument {:type :editor/command-triggered + :payload {:command command}}])))) :class "black"})))) diff --git a/src/main/frontend/components/header.css b/src/main/frontend/components/header.css index 2993dc7da5..0694f26702 100644 --- a/src/main/frontend/components/header.css +++ b/src/main/frontend/components/header.css @@ -191,4 +191,8 @@ html.is-ios.is-safari { html.is-native-ios { --ls-headbar-inner-top-padding: 36px; + + .cp__header > .r { + display: flex; + } } \ No newline at end of file diff --git a/src/main/frontend/components/lazy_editor.cljs b/src/main/frontend/components/lazy_editor.cljs index 0b3106c66b..98789e1cfb 100644 --- a/src/main/frontend/components/lazy_editor.cljs +++ b/src/main/frontend/components/lazy_editor.cljs @@ -1,9 +1,9 @@ (ns frontend.components.lazy-editor - (:require [rum.core :as rum] + (:require [clojure.string :as string] + [rum.core :as rum] [shadow.lazy :as lazy] [frontend.ui :as ui] - [frontend.state :as state] - [frontend.text :as text])) + [frontend.state :as state])) (def lazy-editor (lazy/loadable frontend.extensions.code/editor)) @@ -18,7 +18,8 @@ [config id attr code options] (let [loaded? (rum/react loaded?) theme (state/sub :ui/theme) - code (when code (text/remove-indentations code))] + code (or code "") + code (string/replace-first code #"\n$" "")] ;; See-also: #3410 (if loaded? (@lazy-editor config id attr code theme options) (ui/loading "CodeMirror")))) diff --git a/src/main/frontend/components/page.cljs b/src/main/frontend/components/page.cljs index 5d0bc18450..ff9b5d0aef 100644 --- a/src/main/frontend/components/page.cljs +++ b/src/main/frontend/components/page.cljs @@ -582,7 +582,7 @@ (global-graph-inner graph settings theme))) (rum/defc page-graph-inner < rum/static - [graph dark?] + [page graph dark?] [:div.sidebar-item.flex-col (graph/graph-2d {:nodes (:nodes graph) :links (:links graph) @@ -605,7 +605,7 @@ (graph-handler/build-block-graph (uuid page) theme) (graph-handler/build-page-graph page theme))] (when (seq (:nodes graph)) - (page-graph-inner graph dark?)))) + (page-graph-inner page graph dark?)))) (defn- sort-pages-by [by-item desc? pages] diff --git a/src/main/frontend/components/query_table.cljs b/src/main/frontend/components/query_table.cljs index 7bb36e925d..0b6453dd79 100644 --- a/src/main/frontend/components/query_table.cljs +++ b/src/main/frontend/components/query_table.cljs @@ -128,7 +128,11 @@ :block ; block title (let [content (:block/content item) - {:block/keys [title]} (block/parse-title-and-body (:block/format item) (:block/pre-block? item) content)] + {:block/keys [title]} (block/parse-title-and-body + (:block/uuid item) + (:block/format item) + (:block/pre-block? item) + content)] (if (seq title) [:element (->elem :div (map-inline config title))] [:string content])) diff --git a/src/main/frontend/components/repo.cljs b/src/main/frontend/components/repo.cljs index 8b7fa42a6f..f56ebe96fd 100644 --- a/src/main/frontend/components/repo.cljs +++ b/src/main/frontend/components/repo.cljs @@ -76,11 +76,11 @@ (let [local? (config/local-db? url)] [:div.flex.justify-between.mb-4 {:key id} (if local? - [:a - {:title url ;; show full path on hover - :on-click #(open-repo-url url)} - (some-> (config/get-local-dir url) - (text/get-graph-name-from-path))] + (let [local-dir (config/get-local-dir url) + graph-name (text/get-graph-name-from-path local-dir)] + [:a {:title local-dir + :on-click #(open-repo-url url)} + graph-name]) [:a {:target "_blank" :href url} (db/get-repo-path url)]) diff --git a/src/main/frontend/components/search.cljs b/src/main/frontend/components/search.cljs index 4c7ee1f9fb..15a6977ec8 100644 --- a/src/main/frontend/components/search.cljs +++ b/src/main/frontend/components/search.cljs @@ -33,38 +33,39 @@ [content q] (if (or (string/blank? content) (string/blank? q)) content - (let [q-words (string/split q #" ") - lc-content (string/lower-case content) - lc-q (string/lower-case q)] - (if (and (string/includes? lc-content lc-q) - (not (util/safe-re-find #" " q))) - (let [i (string/index-of lc-content lc-q) - [before after] [(subs content 0 i) (subs content (+ i (count q)))]] - [:div - (when-not (string/blank? before) - [:span before]) - [:mark {:class "p-0 rounded-none"} (subs content i (+ i (count q)))] - (when-not (string/blank? after) - [:span after])]) - (let [elements (loop [words q-words - content content - result []] - (if (and (seq words) content) - (let [word (first words) - lc-word (string/lower-case word) - lc-content (string/lower-case content)] - (if-let [i (string/index-of lc-content lc-word)] - (recur (rest words) - (subs content (+ i (count word))) - (vec - (concat result - [[:span (subs content 0 i)] - [:mark (subs content i (+ i (count word)))]]))) - (recur nil - content - result))) - (conj result [:span content])))] - [:p {:class "m-0"} elements]))))) + (when (and content q) + (let [q-words (string/split q #" ") + lc-content (string/lower-case content) + lc-q (string/lower-case q)] + (if (and (string/includes? lc-content lc-q) + (not (util/safe-re-find #" " q))) + (let [i (string/index-of lc-content lc-q) + [before after] [(subs content 0 i) (subs content (+ i (count q)))]] + [:div + (when-not (string/blank? before) + [:span before]) + [:mark {:class "p-0 rounded-none"} (subs content i (+ i (count q)))] + (when-not (string/blank? after) + [:span after])]) + (let [elements (loop [words q-words + content content + result []] + (if (and (seq words) content) + (let [word (first words) + lc-word (string/lower-case word) + lc-content (string/lower-case content)] + (if-let [i (string/index-of lc-content lc-word)] + (recur (rest words) + (subs content (+ i (count word))) + (vec + (concat result + [[:span (subs content 0 i)] + [:mark (subs content i (+ i (count word)))]]))) + (recur nil + content + result))) + (conj result [:span content])))] + [:p {:class "m-0"} elements])))))) (rum/defc search-result-item [type content] @@ -148,16 +149,18 @@ (cond-> {:type :page :data page} - (not= (string/lower-case page) - (string/lower-case alias)) - (assoc :alias alias)))) pages)) + (and alias + (not= (string/lower-case page) + (string/lower-case alias))) + (assoc :alias alias)))) + (remove nil? pages))) files (when-not all? (map (fn [file] {:type :file :data file}) files)) blocks (map (fn [block] {:type :block :data block}) blocks) search-mode (state/sub :search/mode) new-page (if (or (and (seq pages) - (= (string/lower-case search-q) - (string/lower-case (:data (first pages))))) + (= (util/safe-lower-case search-q) + (util/safe-lower-case (:data (first pages))))) (nil? result) all?) [] @@ -205,12 +208,13 @@ (case type :page (let [data (or alias data) - page (db/entity [:block/name (string/lower-case data)])] - (state/sidebar-add-block! - (state/get-current-repo) - (:db/id page) - :page - {:page page})) + page (when data (db/entity [:block/name (string/lower-case data)]))] + (when page + (state/sidebar-add-block! + (state/get-current-repo) + (:db/id page) + :page + {:page page}))) :block (let [block-uuid (uuid (:block/uuid data)) @@ -297,6 +301,7 @@ (let [recent-search (mapv (fn [q] {:type :search :data q}) (db/get-key-value :recent/search)) pages (->> (db/get-key-value :recent/pages) (remove nil?) + (filter string?) (remove #(= (string/lower-case %) "contents")) (mapv (fn [page] {:type :page :data page}))) result (concat (take 5 recent-search) pages)] @@ -324,12 +329,13 @@ (case type :page (let [page data] - (when-let [page (db/pull [:block/name (string/lower-case page)])] - (state/sidebar-add-block! - (state/get-current-repo) - (:db/id page) - :page - {:page page}))) + (when (string? page) + (when-let [page (db/pull [:block/name (string/lower-case page)])] + (state/sidebar-add-block! + (state/get-current-repo) + (:db/id page) + :page + {:page page})))) nil)) :item-render (fn [{:keys [type data]}] @@ -386,8 +392,8 @@ (search-handler/clear-search! false) (let [search-mode (state/get-search-mode) opts (if (= :page search-mode) - (let [current-page (or (state/get-current-page) - (date/today))] + (when-let [current-page (or (state/get-current-page) + (date/today))] {:page-db-id (:db/id (db/entity [:block/name (string/lower-case current-page)]))}) {})] (state/set-q! value) diff --git a/src/main/frontend/components/settings.cljs b/src/main/frontend/components/settings.cljs index 1f5af43592..6c79163031 100644 --- a/src/main/frontend/components/settings.cljs +++ b/src/main/frontend/components/settings.cljs @@ -219,9 +219,7 @@ (rum/defcs switch-spell-check-row < rum/reactive [state t] - (let [enabled? (state/sub [:electron/user-cfgs :spell-check]) - enabled? (if (nil? enabled?) true enabled?)] - + (let [enabled? (state/sub [:electron/user-cfgs :spell-check])] [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start [:label.block.text-sm.font-medium.leading-5.opacity-70 (t :settings-page/spell-checker)] diff --git a/src/main/frontend/components/sidebar.cljs b/src/main/frontend/components/sidebar.cljs index 263b9d8504..6a94b08654 100644 --- a/src/main/frontend/components/sidebar.cljs +++ b/src/main/frontend/components/sidebar.cljs @@ -336,8 +336,8 @@ (ui/loading (t :loading))]] :else - [:div.pb-24 {:class (if global-graph-pages? "" (util/hiccup->class "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.pb-24")) + :style {:margin-bottom (if global-graph-pages? 0 120)}} main-content])]]]))) (rum/defc footer diff --git a/src/main/frontend/components/sidebar.css b/src/main/frontend/components/sidebar.css index ee0fe22ba1..a05ed07f0a 100644 --- a/src/main/frontend/components/sidebar.css +++ b/src/main/frontend/components/sidebar.css @@ -188,7 +188,7 @@ width: 0; height: 100vh; position: fixed; - top: 0; + top: var(--ls-headbar-inner-top-padding); left: 0; z-index: 9; transition: width 1.2s; @@ -224,11 +224,11 @@ height: 3rem; width: calc(var(--ls-left-sidebar-sm-width) - var(--ls-scrollbar-width)); transition: width .3s; - background-color: transparent; + background-color: var(--ls-primary-background-color); position: fixed; left: 0; top: 0; - opacity: .8; + opacity: 1; z-index: 5; } diff --git a/src/main/frontend/components/theme.css b/src/main/frontend/components/theme.css index c146f6c7aa..654a58ddeb 100644 --- a/src/main/frontend/components/theme.css +++ b/src/main/frontend/components/theme.css @@ -36,6 +36,12 @@ html { } } +@media (prefers-color-scheme: dark) { + .preboot-loading { + color: lightgray; + } +} + .form-checkbox { color: var(--ls-page-checkbox-color, #6093a0); background-color: var(--ls-page-checkbox-color, #6093a0); diff --git a/src/main/frontend/db.cljs b/src/main/frontend/db.cljs index 9c7b4f3468..7e280c5363 100644 --- a/src/main/frontend/db.cljs +++ b/src/main/frontend/db.cljs @@ -34,7 +34,7 @@ [frontend.db.model block-and-children-transform blocks-count blocks-count-cache clean-export! cloned? delete-blocks get-pre-block - delete-file! delete-file-blocks! delete-file-pages! delete-file-tx delete-files delete-pages-by-files + delete-file! delete-file-blocks! delete-page-blocks delete-file-pages! delete-file-tx delete-files delete-pages-by-files filter-only-public-pages-and-blocks get-all-block-contents get-all-tagged-pages get-all-templates get-block-and-children get-block-by-uuid get-block-children sort-by-left get-block-parent get-block-parents parents-collapsed? get-block-referenced-blocks diff --git a/src/main/frontend/db/model.cljs b/src/main/frontend/db/model.cljs index 41a6779f4c..5dda9440d8 100644 --- a/src/main/frontend/db/model.cljs +++ b/src/main/frontend/db/model.cljs @@ -473,23 +473,24 @@ ([repo-url page {:keys [use-cache? pull-keys] :or {use-cache? true pull-keys '[*]}}] - (let [page (string/lower-case (string/trim page)) - page-entity (or (db-utils/entity repo-url [:block/name page]) - (db-utils/entity repo-url [:block/original-name page])) - page-id (:db/id page-entity) - db (conn/get-conn repo-url)] - (when page-id - (some-> - (react/q repo-url [:page/blocks page-id] - {:use-cache? use-cache? - :transform-fn #(page-blocks-transform repo-url %) - :query-fn (fn [db] - (let [datoms (d/datoms db :avet :block/page page-id) - block-eids (mapv :e datoms)] - (db-utils/pull-many repo-url pull-keys block-eids)))} - nil) - react - (flatten-blocks-sort-by-left page-entity)))))) + (when page + (let [page (string/lower-case (string/trim page)) + page-entity (or (db-utils/entity repo-url [:block/name page]) + (db-utils/entity repo-url [:block/original-name page])) + page-id (:db/id page-entity) + db (conn/get-conn repo-url)] + (when page-id + (some-> + (react/q repo-url [:page/blocks page-id] + {:use-cache? use-cache? + :transform-fn #(page-blocks-transform repo-url %) + :query-fn (fn [db] + (let [datoms (d/datoms db :avet :block/page page-id) + block-eids (mapv :e datoms)] + (db-utils/pull-many repo-url pull-keys block-eids)))} + nil) + react + (flatten-blocks-sort-by-left page-entity))))))) (defn get-page-blocks-no-cache ([page] @@ -498,15 +499,16 @@ (get-page-blocks-no-cache repo-url page nil)) ([repo-url page {:keys [pull-keys] :or {pull-keys '[*]}}] - (let [page (string/lower-case page) - page-id (or (:db/id (db-utils/entity repo-url [:block/name page])) - (:db/id (db-utils/entity repo-url [:block/original-name page]))) - db (conn/get-conn repo-url)] - (when page-id - (let [datoms (d/datoms db :avet :block/page page-id) - block-eids (mapv :e datoms)] - (some->> (db-utils/pull-many repo-url pull-keys block-eids) - (page-blocks-transform repo-url))))))) + (when page + (let [page (string/lower-case page) + page-id (or (:db/id (db-utils/entity repo-url [:block/name page])) + (:db/id (db-utils/entity repo-url [:block/original-name page]))) + db (conn/get-conn repo-url)] + (when page-id + (let [datoms (d/datoms db :avet :block/page page-id) + block-eids (mapv :e datoms)] + (some->> (db-utils/pull-many repo-url pull-keys block-eids) + (page-blocks-transform repo-url)))))))) (defn get-page-blocks-count [repo page-id] @@ -697,10 +699,12 @@ first))))) (defn get-page-file - [page-name] - (some-> (or (db-utils/entity [:block/name page-name]) - (db-utils/entity [:block/original-name page-name])) - :block/file)) + ([page-name] + (get-page-file (state/get-current-repo) page-name)) + ([repo page-name] + (some-> (or (db-utils/entity repo [:block/name page-name]) + (db-utils/entity repo [:block/original-name page-name])) + :block/file))) (defn get-block-file-path [block] @@ -1345,6 +1349,16 @@ (let [blocks (get-file-blocks repo-url path)] (mapv (fn [eid] [:db.fn/retractEntity eid]) blocks))) +(defn delete-page-blocks + [repo-url page] + (when page + (let [db (conn/get-conn repo-url) + page (db-utils/pull [:block/name (string/lower-case page)])] + (when page + (let [datoms (d/datoms db :avet :block/page (:db/id page)) + block-eids (mapv :e datoms)] + (mapv (fn [eid] [:db.fn/retractEntity eid]) block-eids)))))) + (defn delete-file-pages! [repo-url path] (let [pages (get-file-pages repo-url path)] diff --git a/src/main/frontend/db/query_dsl.cljs b/src/main/frontend/db/query_dsl.cljs index 7783df7d45..19d1008dcf 100644 --- a/src/main/frontend/db/query_dsl.cljs +++ b/src/main/frontend/db/query_dsl.cljs @@ -417,7 +417,8 @@ (not (string/blank? s))) (let [counter (atom 0)] (try - (let [form (some-> s + (let [s (if (= \# (first s)) (util/format "[[%s]]" (subs s 1)) s) + form (some-> s (pre-transform) (reader/read-string))] (if (symbol? form) @@ -460,8 +461,10 @@ (let [query-string (template/resolve-dynamic-template! query-string)] (when-not (string/blank? query-string) (let [{:keys [query sort-by blocks? sample] :as result} (parse repo query-string) - query (if (string? query) (string/trim query) query)] - (if (and (string? result) (not (string/includes? result " "))) + query (if (string? query) (string/trim query) query) + full-text-query? (and (string? result) + (not (string/includes? result " ")))] + (if full-text-query? (if (= "\"" (first result) (last result)) (subs result 1 (dec (count result))) result) diff --git a/src/main/frontend/extensions/code.cljs b/src/main/frontend/extensions/code.cljs index 8421c8916f..faf5f18807 100644 --- a/src/main/frontend/extensions/code.cljs +++ b/src/main/frontend/extensions/code.cljs @@ -45,8 +45,8 @@ [frontend.handler.editor :as editor-handler] [frontend.handler.file :as file-handler] [frontend.state :as state] + [frontend.utf8 :as utf8] [frontend.util :as util] - [frontend.text :as text] [goog.dom :as gdom] [goog.object :as gobj] [rum.core :as rum])) @@ -68,29 +68,15 @@ (cond (:block/uuid config) (let [block (db/pull [:block/uuid (:block/uuid config)]) - format (:block/format block) content (:block/content block) - {:keys [lines]} (last (:rum/args state)) - full-content (:full_content (last (:rum/args state))) - value (text/remove-indentations value)] - (when full-content - (let [lines (string/split-lines full-content) - fl (first lines) - ll (last lines)] - (when (and fl ll) - (let [src (->> (subvec (vec lines) 1 (dec (count lines))) - (string/join "\n")) - src (text/remove-indentations src) - full-content (str (string/trim fl) - (if (seq src) - (str "\n" src "\n") - "\n") - (string/trim ll))] - (when (string/includes? content full-content) - (let [value' (str (string/trim fl) "\n" value "\n" (string/trim ll)) - ;; FIXME: What if there're multiple code blocks with the same value? - content' (string/replace-first content full-content value')] - (editor-handler/save-block-if-changed! block content')))))))) + {:keys [start_pos end_pos]} (:pos_meta (last (:rum/args state))) + raw-content (utf8/encode content) ;; NOTE: :pos_meta is based on byte position + prefix (utf8/decode (.slice raw-content 0 (- start_pos 2))) + surfix (utf8/decode (.slice raw-content (- end_pos 2))) + new-content (if (string/blank? value) + (str prefix surfix) + (str prefix value "\n" surfix))] + (editor-handler/save-block-if-changed! block new-content)) (:file-path config) (let [path (:file-path config) @@ -137,9 +123,8 @@ (get-in config [:block :block/uuid]))) _ (state/set-state! :editor/code-mode? false) original-mode (get attr :data-lang) - mode original-mode - clojure? (contains? #{"clojure" "clj" "text/x-clojure" "cljs" "cljc"} mode) - mode (if clojure? "clojure" (text->cm-mode mode)) + clojure? (contains? #{"clojure" "clj" "text/x-clojure" "cljs" "cljc"} original-mode) + mode (if clojure? "clojure" (text->cm-mode original-mode)) lisp? (or clojure? (contains? #{"scheme" "racket" "lisp"} mode)) textarea (gdom/getElement id) @@ -152,14 +137,12 @@ :lineNumbers true :styleActiveLine true :extraKeys #js {"Esc" (fn [cm] + (reset! esc-pressed? true) (save-file-or-block-when-blur-or-esc! cm textarea config state) (when-let [block-id (:block/uuid config)] - (let [block (db/pull [:block/uuid block-id]) - value (.getValue cm) - textarea-value (gobj/get textarea "value")] + (let [block (db/pull [:block/uuid block-id])] (editor-handler/edit-block! block :max block-id))) ;; TODO: return "handled" or false doesn't always prevent event bubbles - (reset! esc-pressed? true) (js/setTimeout #(reset! esc-pressed? false) 10))}}))] (when editor (let [textarea-ref (rum/ref-node state textarea-ref-name)] diff --git a/src/main/frontend/extensions/code.css b/src/main/frontend/extensions/code.css index 2e703fce43..fceedab934 100644 --- a/src/main/frontend/extensions/code.css +++ b/src/main/frontend/extensions/code.css @@ -1,12 +1,17 @@ .extensions__code { @apply relative; z-index: 0; - + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; &-lang { - @apply absolute right-0 p-1 text-sm; - top: 3px; - z-index: 1; + @apply p-1 text-sm; + margin-top: 3px; background: var(--ls-secondary-background-color); + word-break: keep-all; + height:2rem; + order:3; } &-calc { @@ -31,6 +36,8 @@ } > .CodeMirror { + order: 1; + width: 100%; z-index: 0; height: auto; margin-top: 4px; diff --git a/src/main/frontend/extensions/graph.cljs b/src/main/frontend/extensions/graph.cljs index a40bd887d5..b1629b5502 100644 --- a/src/main/frontend/extensions/graph.cljs +++ b/src/main/frontend/extensions/graph.cljs @@ -6,13 +6,6 @@ [goog.object :as gobj] [rum.core :as rum])) -(defn- highlight-node! - [^js graph node] - (.resetNodeStyle graph node - (bean/->js {:color "#6366F1" - :border {:width 2 - :color "#6366F1"}}))) - (defn- highlight-neighbours! [^js graph node focus-nodes dark?] (.forEachNeighbor @@ -61,14 +54,16 @@ (rum/defcs graph-2d < (rum/local nil :ref) {:did-update pixi/render! + :should-update (fn [old-state new-state] + (not= (select-keys (first (:rum/args old-state)) + [:nodes :links :dark?]) + (select-keys (first (:rum/args new-state)) + [:nodes :links :dark?]))) :will-unmount (fn [state] - (when-let [graph (:graph state)] - (.destroy graph)) (reset! pixi/*graph-instance nil) state)} [state opts] - [:div.graph {:style {:height "100vh"} - :ref (fn [value] + [:div.graph {:ref (fn [value] (let [ref (get state :ref)] (when (and ref value) (reset! ref value))))}]) diff --git a/src/main/frontend/extensions/graph.css b/src/main/frontend/extensions/graph.css index c6be0d04ba..a0a1635759 100644 --- a/src/main/frontend/extensions/graph.css +++ b/src/main/frontend/extensions/graph.css @@ -7,3 +7,7 @@ position: relative; z-index: 4; } + +.graph { + height: calc(100vh - 100px) !important; +} diff --git a/src/main/frontend/extensions/graph/pixi.cljs b/src/main/frontend/extensions/graph/pixi.cljs index 4046c5b1b6..78eb7e0ea3 100644 --- a/src/main/frontend/extensions/graph/pixi.cljs +++ b/src/main/frontend/extensions/graph/pixi.cljs @@ -7,6 +7,9 @@ ["graphology" :as graphology] ["pixi-graph-fork" :as Pixi-Graph])) +(defonce *graph-instance (atom nil)) +(defonce *simulation (atom nil)) + (def Graph (gobj/get graphology "Graph")) (defonce colors @@ -38,7 +41,6 @@ :type (.-TEXT (.-TextType Pixi-Graph)) :fontSize 12 :color (if dark? "rgba(255, 255, 255, 0.8)" "rgba(0, 0, 0, 0.8)") - ; :backgroundColor "rgba(255, 255, 255, 0.5)" :padding 4}} :edge {:width 1 :color (if dark? "#094b5a" "#cccccc")}}) @@ -46,15 +48,14 @@ (defn default-hover-style [dark?] {:node {:color "#6366F1" - :border {:width 2 - :color "#6366F1"} :label {:backgroundColor "rgba(238, 238, 238, 1)" :color "#333333"}} :edge {:color "#A5B4FC"}}) (defn layout! [nodes links] - (let [simulation (forceSimulation nodes)] + (let [nodes-count (count nodes) + simulation (forceSimulation nodes)] (-> simulation (.force "link" (-> (forceLink) @@ -63,19 +64,19 @@ (.links links))) (.force "charge" (-> (forceManyBody) - (.distanceMax 4000) + (.distanceMax (if (> nodes-count 500) 4000 600)) (.theta 0.5) (.strength -600))) (.force "collision" (-> (forceCollide) - (.radius (+ 8 18)))) + (.radius (+ 8 18)) + (.iterations 2))) (.force "x" (-> (forceX 0) (.strength 0.02))) (.force "y" (-> (forceY 0) (.strength 0.02))) (.force "center" (forceCenter)) - (.tick 3) - (.stop)))) - -(defonce *graph-instance (atom nil)) + (.velocityDecay 0.8)) + (reset! *simulation simulation) + simulation)) (defn- clear-nodes! [graph] @@ -83,63 +84,118 @@ (fn [node] (.dropNode graph node)))) +;; (defn- clear-edges! +;; [graph] +;; (.forEachEdge graph +;; (fn [edge] +;; (.dropEdge graph edge)))) + (defn destroy-instance! [] (when-let [instance (:pixi @*graph-instance)] (.destroy instance) - (reset! *graph-instance nil))) + (reset! *graph-instance nil) + (reset! *simulation nil))) (defonce *dark? (atom nil)) +(defn- update-position! + [node obj] + (.updatePosition node #js {:x (.-x obj) + :y (.-y obj)})) + +(defn- tick! + [pixi graph nodes-js links-js] + (fn [] + (let [nodes-objects (.getNodesObjects pixi) + edges-objects (.getEdgesObjects pixi)] + (doseq [node nodes-js] + (when-let [node-object (.get nodes-objects (.-id node))] + (update-position! node-object node))) + (doseq [edge links-js] + (when-let [edge-object (.get edges-objects (str (.-index edge)))] + (.updatePosition edge-object + #js {:x (.-x (.-source edge)) + :y (.-y (.-source edge))} + #js {:x (.-x (.-target edge)) + :y (.-y (.-target edge))})))))) + +(defn- set-up-listeners! + [pixi-graph] + (when pixi-graph + ;; drag start + (let [*dragging? (atom false) + nodes (.getNodesObjects pixi-graph) + on-drag-end (fn [node event] + (.stopPropagation event) + (when-let [s @*simulation] + (when-not (.-active event) + (.alphaTarget s 0))) + (reset! *dragging? false))] + (.on pixi-graph "nodeMousedown" + (fn [event node-key] + (when-let [node (.get nodes node-key)] + (when-let [s @*simulation] + (when-not (.-active event) + (-> (.alphaTarget s 0.3) + (.restart)) + (js/setTimeout #(.alphaTarget s 0) 2000)) + (reset! *dragging? true))))) + + (.on pixi-graph "nodeMouseup" + (fn [event node-key] + (when-let [node (.get nodes node-key)] + (on-drag-end node event)))) + + (.on pixi-graph "nodeMousemove" + (fn [event node-key] + (when-let [node (.get nodes node-key)] + (when @*dragging? + (update-position! node event)))))))) + (defn render! [state] - (let [dark? (:dark? (first (:rum/args state)))] - (when (and (some? @*dark?) (not= @*dark? dark?)) - (destroy-instance!)) - (reset! *dark? dark?)) - (try - (let [old-instance @*graph-instance - {:keys [graph pixi]} old-instance] - (when (and graph pixi) - (clear-nodes! graph)) - (let [{:keys [nodes links style hover-style height register-handlers-fn dark?]} (first (:rum/args state)) - style (or style (default-style dark?)) - hover-style (or hover-style (default-hover-style dark?)) - graph (or graph (Graph.)) - nodes-set (set (map :id nodes)) - links (->> - (filter - (fn [link] - (and (nodes-set (:source link)) (nodes-set (:target link)))) - links) - (distinct)) - nodes (remove nil? nodes) - links (remove (fn [{:keys [source target]}] (or (nil? source) (nil? target))) links) - nodes-js (bean/->js nodes) - links-js (bean/->js links)] - (layout! nodes-js links-js) + (when @*graph-instance + (clear-nodes! (:graph @*graph-instance)) + (destroy-instance!)) + (let [{:keys [nodes links style hover-style height register-handlers-fn dark?]} (first (:rum/args state)) + style (or style (default-style dark?)) + hover-style (or hover-style (default-hover-style dark?)) + graph (Graph.) + nodes-set (set (map :id nodes)) + links (->> + (filter + (fn [link] + (and (nodes-set (:source link)) (nodes-set (:target link)))) + links) + (distinct)) + nodes (remove nil? nodes) + links (remove (fn [{:keys [source target]}] (or (nil? source) (nil? target))) links) + nodes-js (bean/->js nodes) + links-js (bean/->js links)] + (let [simulation (layout! nodes-js links-js)] (doseq [node nodes-js] (.addNode graph (.-id node) node)) (doseq [link links-js] (let [source (.-id (.-source link)) target (.-id (.-target link))] (.addEdge graph source target link))) - (if pixi - (.resetView pixi) - (when-let [container-ref (:ref state)] - (let [pixi-graph (new (.-PixiGraph Pixi-Graph) - (bean/->js - {:container @container-ref - :graph graph - :style style - :hoverStyle hover-style - :height height}))] - (reset! *graph-instance - {:graph graph - :pixi pixi-graph}) - (when register-handlers-fn - (register-handlers-fn pixi-graph))))))) + (when-let [container-ref (:ref state)] + (let [pixi-graph (new (.-PixiGraph Pixi-Graph) + (bean/->js + {:container @container-ref + :graph graph + :style style + :hoverStyle hover-style + :height height}))] + (reset! *graph-instance + {:graph graph + :pixi pixi-graph}) + (when register-handlers-fn + (register-handlers-fn pixi-graph)) + (set-up-listeners! pixi-graph) + (.on simulation "tick" (tick! pixi-graph graph nodes-js links-js)))))) (catch js/Error e (js/console.error e))) state) diff --git a/src/main/frontend/format/block.cljs b/src/main/frontend/format/block.cljs index 8bc3b851f3..862bf89725 100644 --- a/src/main/frontend/format/block.cljs +++ b/src/main/frontend/format/block.cljs @@ -12,7 +12,8 @@ [frontend.util :as util] [frontend.util.property :as property] [lambdaisland.glogi :as log] - [medley.core :as medley])) + [medley.core :as medley] + [frontend.format.mldoc :as mldoc])) (defn heading-block? [block] @@ -20,6 +21,12 @@ (vector? block) (= "Heading" (first block)))) +(defn properties-block? + [block] + (and + (vector? block) + (= "Properties" (first block)))) + (defn get-tag [block] (when-let [tag-value (and (vector? block) @@ -724,21 +731,29 @@ ([block] (when (map? block) (merge block - (parse-title-and-body (:block/format block) + (parse-title-and-body (:block/uuid block) + (:block/format block) (:block/pre-block? block) (:block/content block))))) - ([format pre-block? content] - (let [content (if pre-block? content - (str (config/get-block-pattern format) " " (string/triml content))) - content (property/remove-properties format content) - ast (->> (format/to-edn content format nil) - (map first)) - title (when (heading-block? (first ast)) - (:title (second (first ast))))] - (cond-> - {:block/body (vec (if title (rest ast) ast))} - title - (assoc :block/title title))))) + ([block-uuid format pre-block? content] + (when-not (string/blank? content) + (let [content (if pre-block? content + (str (config/get-block-pattern format) " " (string/triml content))) + content (property/remove-properties format content)] + (if-let [result (state/get-block-ast block-uuid content)] + result + (let [ast (->> (format/to-edn content format (mldoc/default-config format)) + (map first)) + title (when (heading-block? (first ast)) + (:title (second (first ast)))) + body (vec (if title (rest ast) ast)) + body (drop-while properties-block? body) + result (cond-> + (if (seq body) {:block/body body} {}) + title + (assoc :block/title title))] + (state/add-block-ast-cache! block-uuid content result) + result)))))) (defn macro-subs [macro-content arguments] diff --git a/src/main/frontend/fs/capacitor_fs.cljs b/src/main/frontend/fs/capacitor_fs.cljs index 9994ef193c..acc1c3f626 100644 --- a/src/main/frontend/fs/capacitor_fs.cljs +++ b/src/main/frontend/fs/capacitor_fs.cljs @@ -47,6 +47,7 @@ files (->> files (remove (fn [file] (or (string/starts-with? file ".") + (string/starts-with? file "#") (= file "bak"))))) files (->> files (map (fn [file] diff --git a/src/main/frontend/handler.cljs b/src/main/frontend/handler.cljs index 595e3506a8..bb9f51aa86 100644 --- a/src/main/frontend/handler.cljs +++ b/src/main/frontend/handler.cljs @@ -55,17 +55,19 @@ (let [repo (state/get-current-repo)] (when-not (state/nfs-refreshing?) ;; Don't create the journal file until user writes something - (page-handler/create-today-journal!)) - - (when (and (state/input-idle? repo) - (> (- (util/time-ms) @cards-last-check-time) - (* 60 1000))) - (let [total (srs/get-srs-cards-total)] - (state/set-state! :srs/cards-due-count total) - (reset! cards-last-check-time (util/time-ms))))))] + (page-handler/create-today-journal!))))] (f) (js/setInterval f 5000))) +(defn- instrument! + [] + (let [total (srs/get-srs-cards-total)] + (state/set-state! :srs/cards-due-count total) + (state/pub-event! [:instrument {:type :flashcards/count + :payload {:total (or total 0)}}]) + (state/pub-event! [:instrument {:type :blocks/count + :payload {:total (db/blocks-count)}}]))) + (defn store-schema! [] (storage/set :db-schema (assoc db-schema/schema @@ -235,7 +237,8 @@ (enable-datalog-console)) (when (util/electron?) (el/listen!)) - (mobile/init!))) + (mobile/init!) + (js/setTimeout instrument! (* 60 1000)))) (defn stop! [] (prn "stop!")) diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs index bd8c02e3cb..1c3cc263fd 100644 --- a/src/main/frontend/handler/editor.cljs +++ b/src/main/frontend/handler/editor.cljs @@ -348,9 +348,9 @@ (defn wrap-parse-block [{:block/keys [content format left page uuid level pre-block?] :as block}] - (let [block (merge - (or (and (:db/id block) (db/pull (:db/id block))) block) - (block/parse-title-and-body format pre-block? content)) + (let [block (or (and (:db/id block) (db/pull (:db/id block))) block) + block (merge block + (block/parse-title-and-body uuid format pre-block? (:block/content block))) properties (:block/properties block) real-content (:block/content block) content (if (and (seq properties) real-content (not= real-content content)) @@ -707,8 +707,13 @@ (assoc :block/uuid (or custom-uuid (db/new-block-id)))) [block-m sibling?] (cond before? - (let [block (db/pull (:db/id (:block/left block))) - sibling? (if (:block/name block) false sibling?)] + (let [first-child? (->> [:block/parent :block/left] + (map #(:db/id (get block %))) + (apply =)) + block (db/pull (:db/id (:block/left block))) + sibling? (if (or first-child? ;; insert as first child + (:block/name block)) + false sibling?)] [block sibling?]) sibling? @@ -1113,18 +1118,19 @@ (state/exit-editing-and-set-selected-blocks! [block]))) (defn- blocks-with-level + "Should be sorted already." [blocks] - (let [level-blocks (mapv #(assoc % :level 1) blocks) - level-blocks-map (into {} (mapv (fn [b] [(:db/id b) b]) level-blocks)) - [level-blocks-map _] - (reduce (fn [[r state] [id block]] - (if-let [parent-level (get-in state [(:db/id (:block/parent block)) :level])] - [(conj r [id (assoc block :level (inc parent-level))]) - (assoc-in state [(:db/id block) :level] (inc parent-level))] - [(conj r [id block]) - state])) - [{} level-blocks-map] level-blocks-map)] - level-blocks-map)) + (let [root (assoc (first blocks) :level 1)] + (loop [m [[(:db/id root) root]] + blocks (rest blocks)] + (if (empty? blocks) + m + (let [block (first blocks) + parent-id (:db/id (:block/parent block)) + parent-level (:level (second (first (filter (fn [x] (= (first x) parent-id)) m)))) + block (assoc block :level (inc parent-level)) + m' (vec (conj m [(:db/id block) block]))] + (recur m' (rest blocks))))))) (defn- blocks-vec->tree [blocks] @@ -1160,8 +1166,8 @@ (vec (tree/sort-blocks (db/get-block-children repo (:block/uuid b)) b)) [b])) blocks)) block-ids* (mapv :block/uuid blocks*) - level-blocks-map (blocks-with-level blocks*) - level-blocks-uuid-map (into {} (mapv (fn [b] [(:block/uuid b) b]) (vals level-blocks-map))) + level-blocks (blocks-with-level blocks*) + level-blocks-uuid-map (into {} (mapv (fn [b] [(:block/uuid b) b]) (map second level-blocks))) level-blocks (mapv (fn [uuid] (get level-blocks-uuid-map uuid)) block-ids*) tree (blocks-vec->tree level-blocks) top-level-block-uuids (mapv :block/uuid (filterv #(not (vector? %)) tree)) @@ -1254,8 +1260,8 @@ [b])) ) (flatten)) block-ids* (mapv :block/uuid blocks*) - level-blocks-map (blocks-with-level blocks*) - level-blocks-uuid-map (into {} (mapv (fn [b] [(:block/uuid b) b]) (vals level-blocks-map))) + level-blocks (blocks-with-level blocks*) + level-blocks-uuid-map (into {} (mapv (fn [b] [(:block/uuid b) b]) (map second level-blocks))) level-blocks (mapv (fn [uuid] (get level-blocks-uuid-map uuid)) block-ids*) tree (blocks-vec->tree level-blocks) top-level-block-uuids (mapv :block/uuid (filterv #(not (vector? %)) tree))] @@ -1372,8 +1378,11 @@ (defn cut-block! [block-id] (when-let [block (db/pull [:block/uuid block-id])] - (let [content (:block/content block)] - (common-handler/copy-to-clipboard-without-id-property! (:block/format block) content) + (let [repo (state/get-current-repo) + content (:block/content block) + ;; TODO: support org mode + [md-content _tree] (compose-copied-blocks-contents-&-block-tree repo [block-id])] + (common-handler/copy-to-clipboard-without-id-property! (:block/format block) md-content) (delete-block-aux! block true)))) (defn clear-last-selected-block! @@ -1869,11 +1878,6 @@ (util/stop event) (state/append-current-edit-content! doc-text)))))) -(defn- block-and-children-content - [block-children] - (-> (map :block/content block-children) - string/join)) - (defn- reorder-selected-blocks [blocks] (let [repo (state/get-current-repo) @@ -2298,12 +2302,15 @@ block-uuid (:block/uuid block) template-including-parent? (not (false? (:template-including-parent (:block/properties block)))) blocks (if template-including-parent? (db/get-block-and-children repo block-uuid) (db/get-block-children repo block-uuid)) - level-blocks (vals (blocks-with-level blocks)) - grouped-blocks (group-by #(= db-id (:db/id %)) level-blocks) - root-block (or (first (get grouped-blocks true)) (assoc (db/pull db-id) :level 1)) - blocks-exclude-root (get grouped-blocks false) + root-block (db/pull db-id) + blocks-exclude-root (remove (fn [b] (= (:db/id b) db-id)) blocks) sorted-blocks (tree/sort-blocks blocks-exclude-root root-block) - result-blocks (if template-including-parent? sorted-blocks (drop 1 sorted-blocks)) + sorted-blocks (->> (blocks-with-level sorted-blocks) + (map second)) + result-blocks (if template-including-parent? + sorted-blocks + (->> (drop 1 sorted-blocks) + (map (fn [block] (update block :level dec))))) tree (blocks-vec->tree result-blocks)] (when element-id (insert-command! element-id "" format {})) @@ -2608,7 +2615,7 @@ (cursor/move-cursor-forward input)))))) (defn- delete-and-update [^js input start end] - (.setRangeText input "" start end) + (util/safe-set-range-text! input "" start end) (state/set-edit-content! (state/get-edit-input-id) (.-value input))) (defn- delete-concat [current-block] @@ -3309,7 +3316,7 @@ (map (fn [x] (dissoc x :block/children)))))) (defn collapsable? [block-id] - (if-let [block (db-model/get-block-by-uuid block-id)] + (if-let [block (db-model/query-block-by-uuid block-id)] (let [block (block/parse-title-and-body block)] (and (nil? (-> block :block/properties :collapsed)) diff --git a/src/main/frontend/handler/file.cljs b/src/main/frontend/handler/file.cljs index 1d43d98cbb..ad22d04376 100644 --- a/src/main/frontend/handler/file.cljs +++ b/src/main/frontend/handler/file.cljs @@ -125,9 +125,9 @@ data))) (defn- page-exists-in-another-file - [page file] + [repo-url page file] (when-let [page-name (:block/name page)] - (let [current-file (:file/path (db/get-page-file page-name))] + (let [current-file (:file/path (db/get-page-file repo-url page-name))] (when (not= file current-file) current-file)))) @@ -157,9 +157,14 @@ utf8-content (utf8/encode content) file-content [{:file/path file}]] (p/let [tx (if (contains? config/mldoc-support-formats format) - (p/let [delete-blocks (db/delete-file-blocks! repo-url file) - [pages blocks] (extract-handler/extract-blocks-pages repo-url file content utf8-content) - _ (when-let [current-file (page-exists-in-another-file (first pages) file)] + (p/let [[pages blocks] (extract-handler/extract-blocks-pages repo-url file content utf8-content) + first-page (first pages) + delete-blocks (-> + (concat + (db/delete-file-blocks! repo-url file) + (when first-page (db/delete-page-blocks repo-url (:block/name first-page)))) + (distinct)) + _ (when-let [current-file (page-exists-in-another-file repo-url first-page file)] (when (not= file current-file) (let [error (str "Page already exists with another file: " current-file ", current file: " file)] (state/pub-event! [:notification/show diff --git a/src/main/frontend/handler/plugin.cljs b/src/main/frontend/handler/plugin.cljs index 82f0f4c9b1..988af67750 100644 --- a/src/main/frontend/handler/plugin.cljs +++ b/src/main/frontend/handler/plugin.cljs @@ -349,7 +349,7 @@ [] (let [text (state/sub :plugin/indicator-text)] (if-not (= text "END") - [:div.flex.align-items.justify-center.h-screen.w-full + [:div.flex.align-items.justify-center.h-screen.w-full.preboot-loading [:span.flex.items-center.justify-center.w-60.flex-col [:small.scale-250.opacity-70.mb-10.animate-pulse (svg/logo false)] [:small.block.text-sm.relative.opacity-50 {:style {:right "-8px"}} text]]]))) diff --git a/src/main/frontend/handler/route.cljs b/src/main/frontend/handler/route.cljs index c6495ffc3c..c5e223e7e7 100644 --- a/src/main/frontend/handler/route.cljs +++ b/src/main/frontend/handler/route.cljs @@ -27,6 +27,14 @@ (when pub-event? (state/pub-event! [:redirect-to-home])) (redirect! {:to :home}))) +(defn redirect-to-all-pages! + [] + (redirect! {:to :all-pages})) + +(defn redirect-to-graph-view! + [] + (redirect! {:to :graph})) + (defn redirect-to-page! ([page-name] (recent-handler/add-page-to-recent! (state/get-current-repo) page-name) diff --git a/src/main/frontend/modules/file/core.cljs b/src/main/frontend/modules/file/core.cljs index f04cb751a0..6344aa25b8 100644 --- a/src/main/frontend/modules/file/core.cljs +++ b/src/main/frontend/modules/file/core.cljs @@ -24,8 +24,8 @@ (ffirst body)))) (defn transform-content - [{:block/keys [format pre-block? unordered content heading-level left page scheduled deadline parent] :as block} level {:keys [heading-to-list?]}] - (let [{:block/keys [title body]} (block/parse-title-and-body format pre-block? content) + [{:block/keys [uuid format pre-block? unordered content heading-level left page scheduled deadline parent] :as block} level {:keys [heading-to-list?]}] + (let [{:block/keys [title body]} (block/parse-title-and-body uuid format pre-block? content) content (or content "") heading-with-title? (seq title) allowed-block-as-title? (allowed-block-as-title? title body) diff --git a/src/main/frontend/modules/instrumentation/posthog.cljs b/src/main/frontend/modules/instrumentation/posthog.cljs index d5a2f54f91..a3615bf5bb 100644 --- a/src/main/frontend/modules/instrumentation/posthog.cljs +++ b/src/main/frontend/modules/instrumentation/posthog.cljs @@ -1,6 +1,7 @@ (ns frontend.modules.instrumentation.posthog (:require [frontend.config :as cfg] [frontend.util :as util] + [frontend.mobile.util :as mobile] [frontend.version :refer [version]] ["posthog-js" :as posthog] [cljs-bean.core :as bean])) @@ -11,7 +12,14 @@ (defn register [] (posthog/register (clj->js - {:app_type (if (util/electron?) "electron" "web") + {:app_type (let [platform (mobile/platform)] + (cond + platform + platform + (util/electron?) + "electron" + :else + "web")) :app_env (if cfg/dev? "development" "production") :app_ver version :schema_ver 0 diff --git a/src/main/frontend/modules/shortcut/config.cljs b/src/main/frontend/modules/shortcut/config.cljs index b914a10a6c..30718e9651 100644 --- a/src/main/frontend/modules/shortcut/config.cljs +++ b/src/main/frontend/modules/shortcut/config.cljs @@ -285,8 +285,8 @@ :binding "mod+k" :fn #(route-handler/go-to-search! :global)} - :go/journals {:desc "Jump to journals" - :binding (if mac? "mod+j" "alt+j") + :go/journals {:desc "Go to journals" + :binding "g j" :fn route-handler/go-to-journals!} :go/backward {:desc "Backwards" @@ -325,7 +325,16 @@ :go/home {:desc "Go to home" :binding "g h" - :fn #(route-handler/redirect-to-home!)} + :fn route-handler/redirect-to-home!} + + :go/all-pages {:desc "Go to all pages" + :binding "g a" + :fn route-handler/redirect-to-all-pages!} + + :go/graph-view {:desc "Go to graph view" + :binding "g g" + :fn route-handler/redirect-to-graph-view!} + :go/keyboard-shortcuts {:desc "Go to keyboard shortcuts" :binding "g s" @@ -343,6 +352,13 @@ :binding "g p" :fn journal-handler/go-to-prev-journal!} + :go/flashcards {:desc "Toggle flashcards" + :binding "g f" + :fn (fn [] + (if (state/modal-opened?) + (state/close-modal!) + (state/pub-event! [:modal/show-cards])))} + :ui/toggle-document-mode {:desc "Toggle document mode" :binding "t d" :fn state/toggle-document-mode!} @@ -367,8 +383,8 @@ :binding "t t" :fn state/toggle-theme!} - :ui/toggle-contents {:desc "Toggle Favorites in sidebar" - :binding "t f" + :ui/toggle-contents {:desc "Toggle Contents in sidebar" + :binding "mod+shift+c" :fn ui-handler/toggle-contents!} :command/toggle-favorite {:desc "Add to/remove from favorites" @@ -500,7 +516,6 @@ :ui/toggle-brackets :go/search-in-page :go/search - :go/journals :go/backward :go/forward :search/re-index @@ -517,6 +532,10 @@ (-> (build-category-map [:command/run :go/home + :go/journals + :go/all-pages + :go/flashcards + :go/graph-view :go/keyboard-shortcuts :go/tomorrow :go/next-journal @@ -633,6 +652,9 @@ ^{:doc "Others"} [:go/home :go/journals + :go/all-pages + :go/graph-view + :go/flashcards :go/tomorrow :go/next-journal :go/prev-journal diff --git a/src/main/frontend/page.cljs b/src/main/frontend/page.cljs index cfb765e903..5f43afa3d4 100644 --- a/src/main/frontend/page.cljs +++ b/src/main/frontend/page.cljs @@ -10,6 +10,14 @@ [view route-match] (view route-match)) +(defn- teardown-fn + [] + (try + (ui/setup-active-keystroke!) + (ui/setup-patch-ios-visual-viewport-state!) + (catch js/Error _e + nil))) + (rum/defc current-page < rum/reactive {:did-mount (fn [state] (state/set-root-component! (:rum/react-component state)) @@ -17,10 +25,7 @@ (ui/inject-document-devices-envs!) (ui/inject-dynamic-style-node!) (plugin-handler/host-mounted!) - (let [teardown-fn (comp - (ui/setup-active-keystroke!) - (ui/setup-patch-ios-visual-viewport-state!))] - (assoc state ::teardown teardown-fn))) + (assoc state ::teardown teardown-fn)) :will-unmount (fn [state] (let [teardown (::teardown state)] (when-not (nil? teardown) diff --git a/src/main/frontend/state.cljs b/src/main/frontend/state.cljs index 96900cca9a..dc98f74b63 100644 --- a/src/main/frontend/state.cljs +++ b/src/main/frontend/state.cljs @@ -16,7 +16,8 @@ [promesa.core :as p] [rum.core :as rum] [frontend.mobile.util :as mobile] - [frontend.mobile.util :as mobile-util])) + [frontend.mobile.util :as mobile-util] + [cljs.cache :as cache])) (defonce state (let [document-mode? (or (storage/get :document/mode?) false) @@ -188,6 +189,31 @@ :srs/cards-due-count nil}))) +;; block uuid -> {content(String) -> ast} +(def blocks-ast-cache (atom (cache/lru-cache-factory {} :threshold 5000))) +(defn add-block-ast-cache! + [block-uuid content ast] + (when (and block-uuid content ast) + (let [k block-uuid + add-cache! (fn [] + (reset! blocks-ast-cache (cache/evict @blocks-ast-cache block-uuid)) + (reset! blocks-ast-cache (cache/miss @blocks-ast-cache k {content ast})))] + (if (cache/has? @blocks-ast-cache k) + (let [m (cache/lookup @blocks-ast-cache k)] + (if (and (map? m) (get m content)) + (reset! blocks-ast-cache (cache/hit @blocks-ast-cache k)) + (add-cache!))) + (add-cache!))))) + +(defn get-block-ast + [block-uuid content] + (when (and block-uuid content) + (let [k block-uuid] + (when (cache/has? @blocks-ast-cache k) + (let [m (cache/lookup @blocks-ast-cache k)] + (when-let [result (and (map? m) (get m content))] + (reset! blocks-ast-cache (cache/hit @blocks-ast-cache k)) + result)))))) (defn sub [ks] @@ -1587,4 +1613,4 @@ (defn get-visual-viewport-state [] - (:ui/visual-viewport-state @state)) \ No newline at end of file + (:ui/visual-viewport-state @state)) diff --git a/src/main/frontend/text.cljs b/src/main/frontend/text.cljs index 143d2f6235..687a6845ec 100644 --- a/src/main/frontend/text.cljs +++ b/src/main/frontend/text.cljs @@ -1,9 +1,7 @@ (ns frontend.text (:require [frontend.config :as config] [frontend.util :as util] - [clojure.string :as string] - [clojure.set :as set] - [medley.core :as medley])) + [clojure.string :as string])) (def page-ref-re-0 #"\[\[(.*)\]\]") (def org-page-ref-re #"\[\[(file:.*)\]\[.+?\]\]") @@ -346,14 +344,3 @@ (if (not= (first parts) "0") (string/join "/" parts) (last parts))))) - -(defn remove-indentations - [text] - (when (string? text) - (let [lines (string/split-lines text) - spaces (re-find #"^[\s\t]+" (first lines)) - spaces-count (count spaces)] - (string/join "\n" (map (fn [line] - (let [spaces (re-find #"^[\s\t]+" line) - spaces-count (min (count spaces) spaces-count)] - (util/safe-subs line spaces-count))) lines))))) diff --git a/src/main/frontend/ui.cljs b/src/main/frontend/ui.cljs index 11ce77316f..5e2ff1885a 100644 --- a/src/main/frontend/ui.cljs +++ b/src/main/frontend/ui.cljs @@ -270,7 +270,7 @@ (defn main-node [] - (gdom/getElement "main-content")) + (gdom/getElement "main-container")) (defn get-scroll-top [] (.-scrollTop (main-node))) @@ -369,16 +369,26 @@ (.removeEventListener js/window "blur" clear-all) (.removeEventListener js/window "visibilitychange" clear-all)))) +(defonce last-scroll-top (atom 0)) + +(defn scroll-down? + [] + (let [scroll-top (get-scroll-top)] + (let [down? (> scroll-top @last-scroll-top)] + (reset! last-scroll-top scroll-top) + down?))) + (defn on-scroll [node on-load on-top-reached] (let [full-height (gobj/get node "scrollHeight") scroll-top (gobj/get node "scrollTop") client-height (gobj/get node "clientHeight") bottom-reached? (<= (- full-height scroll-top client-height) 100) - top-reached? (= scroll-top 0)] - (when (and bottom-reached? on-load) + top-reached? (= scroll-top 0) + down? (scroll-down?)] + (when (and down? bottom-reached? on-load) (on-load)) - (when (and top-reached? on-top-reached) + (when (and (not down?) top-reached? on-top-reached) (on-top-reached)))) (defn attach-listeners diff --git a/src/main/frontend/ui.css b/src/main/frontend/ui.css index c632754362..4d392d56a4 100644 --- a/src/main/frontend/ui.css +++ b/src/main/frontend/ui.css @@ -85,13 +85,7 @@ } .ui__modal { - @apply fixed bottom-0 inset-x-0 px-4 pb-4; - - @screen sm { - & { - @apply inset-0 flex items-baseline justify-center top-24; - } - } + @apply fixed px-4 pb-4 inset-0 flex items-baseline justify-center top-24; &-overlay { @apply fixed inset-0 transition-opacity; @@ -123,6 +117,12 @@ } } +html.is-mobile { + .ui__modal { + @apply bottom-0 inset-x-0; + } +} + .ui__confirm-modal { .sublabel { display: flex; diff --git a/src/main/frontend/util.cljc b/src/main/frontend/util.cljc index 9a1f1d7feb..1d5473ffb2 100644 --- a/src/main/frontend/util.cljc +++ b/src/main/frontend/util.cljc @@ -652,6 +652,19 @@ (recur (conj res [(.-index m) (first m)])) res))))) +#?(:cljs + (defn safe-set-range-text! + ([input text start end] + (try + (.setRangeText input "" start end) + (catch js/Error _e + nil))) + ([input text start end select-mode] + (try + (.setRangeText input "" start end select-mode) + (catch js/Error _e + nil))))) + #?(:cljs (defn kill-line-before! [input] @@ -659,7 +672,7 @@ end (.-selectionStart input) n-pos (string/last-index-of val \newline (dec end)) start (if n-pos (inc n-pos) 0)] - (.setRangeText input "" start end)))) + (safe-set-range-text! input "" start end)))) #?(:cljs (defn kill-line-after! @@ -668,14 +681,14 @@ start (.-selectionStart input) end (or (string/index-of val \newline start) (count val))] - (.setRangeText input "" start end)))) + (safe-set-range-text! input "" start end)))) #?(:cljs (defn insert-at-current-position! [input text] (let [start (.-selectionStart input) end (.-selectionEnd input)] - (.setRangeText input text start end "end")))) + (safe-set-range-text! input text start end "end")))) ;; copied from re_com #?(:cljs @@ -1323,7 +1336,7 @@ (recur (dec idx)) idx)) inc))] - (.setRangeText input "" idx current)))) + (safe-set-range-text! input "" idx current)))) #?(:cljs (defn forward-kill-word @@ -1339,7 +1352,7 @@ (remove nil?) (apply min)) (count val))] - (.setRangeText input "" current (inc idx))))) + (safe-set-range-text! input "" current (inc idx))))) #?(:cljs (defn fix-open-external-with-shift! @@ -1476,4 +1489,4 @@ #?(:cljs (defn sm-breakpoint? [] - (< (.-offsetWidth js/document.documentElement) 640))) \ No newline at end of file + (< (.-offsetWidth js/document.documentElement) 640))) diff --git a/src/main/frontend/util/drawer.cljs b/src/main/frontend/util/drawer.cljs index 7eead19142..bbabf5518a 100644 --- a/src/main/frontend/util/drawer.cljs +++ b/src/main/frontend/util/drawer.cljs @@ -61,8 +61,8 @@ :else (let [properties-count (count (second (first (second ast)))) - properties (subvec body-without-timestamps 0 (inc properties-count)) - after (rest body-without-timestamps)] + properties (subvec body-without-timestamps 0 properties-count) + after (subvec body-without-timestamps properties-count)] (string/join "\n" (concat [title] scheduled deadline properties [drawer] after)))) (string/join "\n" (concat [title] scheduled deadline [drawer] body-without-timestamps)))) diff --git a/src/main/frontend/version.cljs b/src/main/frontend/version.cljs index c7f62d1294..786def7bf0 100644 --- a/src/main/frontend/version.cljs +++ b/src/main/frontend/version.cljs @@ -1,3 +1,3 @@ (ns frontend.version) -(defonce version "0.5.2") +(defonce version "0.5.4")