Initial import of LogDarioSeq baseline

Establishes version control for the personal Logseq DB fork before
starting the server-client architecture migration and domain features
described in docs/agent-guide and the approved implementation plan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 15:32:15 +02:00
commit 40631362b3
2016 changed files with 543898 additions and 0 deletions

4
deps/publishing/.carve/config.edn vendored Normal file
View File

@@ -0,0 +1,4 @@
{:paths ["src"]
:api-namespaces [;; for nbb-logseq CLIs
logseq.publishing]
:report {:format :ignore}}

14
deps/publishing/.clj-kondo/config.edn vendored Normal file
View File

@@ -0,0 +1,14 @@
{:linters
{:aliased-namespace-symbol {:level :warning}
:namespace-name-mismatch {:level :warning}
:used-underscored-binding {:level :warning}
:shadowed-var {:level :warning}
:consistent-alias
{:aliases {clojure.string string}}}
:lint-as
{logseq.publishing.test.helper/deftest-async clojure.test/deftest
promesa.core/let clojure.core/let}
:skip-comments true
:output {:progress true}}

3
deps/publishing/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/.clj-kondo/.cache
.nbb
/tmp

52
deps/publishing/README.md vendored Normal file
View File

@@ -0,0 +1,52 @@
## Description
This library handles exporting the `frontend.publishing` single page
application. This library is compatible with ClojureScript and with
node/[nbb-logseq](https://github.com/logseq/nbb-logseq) to respectively provide
frontend and Electron/commandline functionality.
## API
This library is under the parent namespace `logseq.publishing`. This library
provides two namespaces for node/CLI contexts, `logseq.publishing` and
`logseq.publishing.export` and two namespaces for the frontend,
`logseq.publishing.html` and `logseq.publishing.db`.
## Usage
See `script/publishing.cljs` for a CLI example. See the frontend for cljs usage.
## Dev
This follows the practices that [the Logseq frontend
follows](/docs/dev-practices.md). Most of the same linters are used, with
configurations that are specific to this library. See [this library's CI
file](/.github/workflows/deps-publishing.yml) for linting examples.
### Setup
To run linters and tests, you'll want to install pnpm dependencies once:
```
pnpm install
```
This step is not needed if you're just running the frontend application.
### Testing
Testing is done with nbb-logseq and
[nbb-test-runner](https://github.com/nextjournal/nbb-test-runner). Some basic
usage:
```
# Run all tests
$ pnpm test
# List available options
$ pnpm test -H
# Run tests with :focus metadata flag
$ pnpm test -i focus
```
### Managing dependencies
See [standard nbb/cljs library advice in graph-parser](/deps/graph-parser/README.md#managing-dependencies).

30
deps/publishing/bb.edn vendored Normal file
View File

@@ -0,0 +1,30 @@
{:min-bb-version "1.0.168"
:deps
{logseq/bb-tasks
#_{:local/root "../../../bb-tasks"}
{:git/url "https://github.com/logseq/bb-tasks"
:git/sha "7cc218286c03f093996539b8195de56a0ec5df6a"}}
:pods
{clj-kondo/clj-kondo {:version "2026.04.15"}}
:tasks
{test:load-all-namespaces-with-nbb
logseq.bb-tasks.nbb.test/load-all-namespaces
lint:large-vars
logseq.bb-tasks.lint.large-vars/-main
lint:carve
logseq.bb-tasks.lint.carve/-main
lint:ns-docstrings
logseq.bb-tasks.lint.ns-docstrings/-main
lint:minimize-public-vars
logseq.bb-tasks.lint.minimize-public-vars/-main}
:tasks/config
{:large-vars
;; For vars with long running html
{:metadata-exceptions #{:large-vars/html}}}}

7
deps/publishing/deps.edn vendored Normal file
View File

@@ -0,0 +1,7 @@
{:paths ["src"]
:deps
{logseq/db {:local/root "../db"}}
:aliases
{:clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2026.04.15"}}
:main-opts ["-m" "clj-kondo.main"]}}}

6
deps/publishing/nbb.edn vendored Normal file
View File

@@ -0,0 +1,6 @@
{:paths ["src"]
:deps
{logseq/db
{:local/root "../db"}
io.github.nextjournal/nbb-test-runner
{:git/sha "b379325cfa5a3306180649da5de3bf5166414e71"}}}

17
deps/publishing/package.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "@logseq/publishing",
"version": "1.0.0",
"packageManager": "pnpm@10.33.0",
"private": true,
"devDependencies": {
"@logseq/nbb-logseq": "git+https://github.com/logseq/nbb-logseq.git#feat-db-v34",
"mldoc": "^1.5.9"
},
"dependencies": {
"better-sqlite3": "^12.8.0",
"fs-extra": "^11.3.4"
},
"scripts": {
"test": "pnpm exec nbb-logseq -cp test -m nextjournal.test-runner"
}
}

724
deps/publishing/pnpm-lock.yaml generated vendored Normal file
View File

@@ -0,0 +1,724 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
better-sqlite3:
specifier: ^12.8.0
version: 12.8.0
fs-extra:
specifier: ^11.3.4
version: 11.3.4
devDependencies:
'@logseq/nbb-logseq':
specifier: git+https://github.com/logseq/nbb-logseq.git#feat-db-v34
version: https://codeload.github.com/logseq/nbb-logseq/tar.gz/4d9f1382ce2cec6f39bf12df49ba425f1bd5f3fb
mldoc:
specifier: ^1.5.9
version: 1.5.9
packages:
'@logseq/nbb-logseq@https://codeload.github.com/logseq/nbb-logseq/tar.gz/4d9f1382ce2cec6f39bf12df49ba425f1bd5f3fb':
resolution: {tarball: https://codeload.github.com/logseq/nbb-logseq/tar.gz/4d9f1382ce2cec6f39bf12df49ba425f1bd5f3fb}
version: 1.2.173-feat-db-v34
engines: {node: '>=20.10.0'}
hasBin: true
ansi-regex@2.1.1:
resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==}
engines: {node: '>=0.10.0'}
ansi-regex@3.0.1:
resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==}
engines: {node: '>=4'}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
better-sqlite3@12.8.0:
resolution: {integrity: sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==}
engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x}
bindings@1.5.0:
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
cliui@4.1.0:
resolution: {integrity: sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==}
code-point-at@1.1.0:
resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==}
engines: {node: '>=0.10.0'}
cross-spawn@6.0.6:
resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==}
engines: {node: '>=4.8'}
decamelize@1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
decompress-response@6.0.0:
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
engines: {node: '>=10'}
deep-extend@0.6.0:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
engines: {node: '>=4.0.0'}
detect-libc@2.0.3:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
engines: {node: '>=8'}
end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
execa@1.0.0:
resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==}
engines: {node: '>=6'}
expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
file-uri-to-path@1.0.0:
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
find-up@3.0.0:
resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
engines: {node: '>=6'}
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
fs-extra@11.3.4:
resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==}
engines: {node: '>=14.14'}
get-caller-file@1.0.3:
resolution: {integrity: sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==}
get-stream@4.1.0:
resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==}
engines: {node: '>=6'}
github-from-package@0.0.0:
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
import-meta-resolve@4.1.0:
resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==}
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
ini@1.3.8:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
invert-kv@2.0.0:
resolution: {integrity: sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==}
engines: {node: '>=4'}
is-fullwidth-code-point@1.0.0:
resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==}
engines: {node: '>=0.10.0'}
is-fullwidth-code-point@2.0.0:
resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==}
engines: {node: '>=4'}
is-stream@1.1.0:
resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==}
engines: {node: '>=0.10.0'}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
lcid@2.0.0:
resolution: {integrity: sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==}
engines: {node: '>=6'}
locate-path@3.0.0:
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
engines: {node: '>=6'}
map-age-cleaner@0.1.3:
resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==}
engines: {node: '>=6'}
mem@4.3.0:
resolution: {integrity: sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==}
engines: {node: '>=6'}
mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
mldoc@1.5.9:
resolution: {integrity: sha512-87FQ7hseS87tsk+VdpIigpu8LH+GwmbbFgpxgFwvnbH5oOjmIrc47laH4Dyggzqiy8/vMjDHkl7vsId0eXhCDQ==}
hasBin: true
napi-build-utils@1.0.2:
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
nice-try@1.0.5:
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
node-abi@3.71.0:
resolution: {integrity: sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==}
engines: {node: '>=10'}
npm-run-path@2.0.2:
resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==}
engines: {node: '>=4'}
number-is-nan@1.0.1:
resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==}
engines: {node: '>=0.10.0'}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
os-locale@3.1.0:
resolution: {integrity: sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==}
engines: {node: '>=6'}
p-defer@1.0.0:
resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==}
engines: {node: '>=4'}
p-finally@1.0.0:
resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
engines: {node: '>=4'}
p-is-promise@2.1.0:
resolution: {integrity: sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==}
engines: {node: '>=6'}
p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
p-locate@3.0.0:
resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
engines: {node: '>=6'}
p-try@2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
path-exists@3.0.0:
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
engines: {node: '>=4'}
path-key@2.0.1:
resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
engines: {node: '>=4'}
prebuild-install@7.1.2:
resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==}
engines: {node: '>=10'}
deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
hasBin: true
pump@3.0.2:
resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==}
rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
require-main-filename@1.0.1:
resolution: {integrity: sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==}
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
semver@5.7.2:
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
hasBin: true
semver@7.6.3:
resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
engines: {node: '>=10'}
hasBin: true
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
shebang-command@1.2.0:
resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
engines: {node: '>=0.10.0'}
shebang-regex@1.0.0:
resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==}
engines: {node: '>=0.10.0'}
signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
simple-concat@1.0.1:
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
simple-get@4.0.1:
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
string-width@1.0.2:
resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==}
engines: {node: '>=0.10.0'}
string-width@2.1.1:
resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==}
engines: {node: '>=4'}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
strip-ansi@3.0.1:
resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
engines: {node: '>=0.10.0'}
strip-ansi@4.0.0:
resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==}
engines: {node: '>=4'}
strip-eof@1.0.0:
resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==}
engines: {node: '>=0.10.0'}
strip-json-comments@2.0.1:
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
engines: {node: '>=0.10.0'}
tar-fs@2.1.1:
resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==}
tar-stream@2.2.0:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
which-module@2.0.1:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
which@1.3.1:
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
hasBin: true
wrap-ansi@2.1.0:
resolution: {integrity: sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==}
engines: {node: '>=0.10.0'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
yargs-parser@11.1.1:
resolution: {integrity: sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==}
yargs@12.0.5:
resolution: {integrity: sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==}
snapshots:
'@logseq/nbb-logseq@https://codeload.github.com/logseq/nbb-logseq/tar.gz/4d9f1382ce2cec6f39bf12df49ba425f1bd5f3fb':
dependencies:
import-meta-resolve: 4.1.0
ansi-regex@2.1.1: {}
ansi-regex@3.0.1: {}
base64-js@1.5.1: {}
better-sqlite3@12.8.0:
dependencies:
bindings: 1.5.0
prebuild-install: 7.1.2
bindings@1.5.0:
dependencies:
file-uri-to-path: 1.0.0
bl@4.1.0:
dependencies:
buffer: 5.7.1
inherits: 2.0.4
readable-stream: 3.6.2
buffer@5.7.1:
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
camelcase@5.3.1: {}
chownr@1.1.4: {}
cliui@4.1.0:
dependencies:
string-width: 2.1.1
strip-ansi: 4.0.0
wrap-ansi: 2.1.0
code-point-at@1.1.0: {}
cross-spawn@6.0.6:
dependencies:
nice-try: 1.0.5
path-key: 2.0.1
semver: 5.7.2
shebang-command: 1.2.0
which: 1.3.1
decamelize@1.2.0: {}
decompress-response@6.0.0:
dependencies:
mimic-response: 3.1.0
deep-extend@0.6.0: {}
detect-libc@2.0.3: {}
end-of-stream@1.4.4:
dependencies:
once: 1.4.0
execa@1.0.0:
dependencies:
cross-spawn: 6.0.6
get-stream: 4.1.0
is-stream: 1.1.0
npm-run-path: 2.0.2
p-finally: 1.0.0
signal-exit: 3.0.7
strip-eof: 1.0.0
expand-template@2.0.3: {}
file-uri-to-path@1.0.0: {}
find-up@3.0.0:
dependencies:
locate-path: 3.0.0
fs-constants@1.0.0: {}
fs-extra@11.3.4:
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.1.0
universalify: 2.0.1
get-caller-file@1.0.3: {}
get-stream@4.1.0:
dependencies:
pump: 3.0.2
github-from-package@0.0.0: {}
graceful-fs@4.2.11: {}
ieee754@1.2.1: {}
import-meta-resolve@4.1.0: {}
inherits@2.0.4: {}
ini@1.3.8: {}
invert-kv@2.0.0: {}
is-fullwidth-code-point@1.0.0:
dependencies:
number-is-nan: 1.0.1
is-fullwidth-code-point@2.0.0: {}
is-stream@1.1.0: {}
isexe@2.0.0: {}
jsonfile@6.1.0:
dependencies:
universalify: 2.0.1
optionalDependencies:
graceful-fs: 4.2.11
lcid@2.0.0:
dependencies:
invert-kv: 2.0.0
locate-path@3.0.0:
dependencies:
p-locate: 3.0.0
path-exists: 3.0.0
map-age-cleaner@0.1.3:
dependencies:
p-defer: 1.0.0
mem@4.3.0:
dependencies:
map-age-cleaner: 0.1.3
mimic-fn: 2.1.0
p-is-promise: 2.1.0
mimic-fn@2.1.0: {}
mimic-response@3.1.0: {}
minimist@1.2.8: {}
mkdirp-classic@0.5.3: {}
mldoc@1.5.9:
dependencies:
yargs: 12.0.5
napi-build-utils@1.0.2: {}
nice-try@1.0.5: {}
node-abi@3.71.0:
dependencies:
semver: 7.6.3
npm-run-path@2.0.2:
dependencies:
path-key: 2.0.1
number-is-nan@1.0.1: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2
os-locale@3.1.0:
dependencies:
execa: 1.0.0
lcid: 2.0.0
mem: 4.3.0
p-defer@1.0.0: {}
p-finally@1.0.0: {}
p-is-promise@2.1.0: {}
p-limit@2.3.0:
dependencies:
p-try: 2.2.0
p-locate@3.0.0:
dependencies:
p-limit: 2.3.0
p-try@2.2.0: {}
path-exists@3.0.0: {}
path-key@2.0.1: {}
prebuild-install@7.1.2:
dependencies:
detect-libc: 2.0.3
expand-template: 2.0.3
github-from-package: 0.0.0
minimist: 1.2.8
mkdirp-classic: 0.5.3
napi-build-utils: 1.0.2
node-abi: 3.71.0
pump: 3.0.2
rc: 1.2.8
simple-get: 4.0.1
tar-fs: 2.1.1
tunnel-agent: 0.6.0
pump@3.0.2:
dependencies:
end-of-stream: 1.4.4
once: 1.4.0
rc@1.2.8:
dependencies:
deep-extend: 0.6.0
ini: 1.3.8
minimist: 1.2.8
strip-json-comments: 2.0.1
readable-stream@3.6.2:
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
require-directory@2.1.1: {}
require-main-filename@1.0.1: {}
safe-buffer@5.2.1: {}
semver@5.7.2: {}
semver@7.6.3: {}
set-blocking@2.0.0: {}
shebang-command@1.2.0:
dependencies:
shebang-regex: 1.0.0
shebang-regex@1.0.0: {}
signal-exit@3.0.7: {}
simple-concat@1.0.1: {}
simple-get@4.0.1:
dependencies:
decompress-response: 6.0.0
once: 1.4.0
simple-concat: 1.0.1
string-width@1.0.2:
dependencies:
code-point-at: 1.1.0
is-fullwidth-code-point: 1.0.0
strip-ansi: 3.0.1
string-width@2.1.1:
dependencies:
is-fullwidth-code-point: 2.0.0
strip-ansi: 4.0.0
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
strip-ansi@3.0.1:
dependencies:
ansi-regex: 2.1.1
strip-ansi@4.0.0:
dependencies:
ansi-regex: 3.0.1
strip-eof@1.0.0: {}
strip-json-comments@2.0.1: {}
tar-fs@2.1.1:
dependencies:
chownr: 1.1.4
mkdirp-classic: 0.5.3
pump: 3.0.2
tar-stream: 2.2.0
tar-stream@2.2.0:
dependencies:
bl: 4.1.0
end-of-stream: 1.4.4
fs-constants: 1.0.0
inherits: 2.0.4
readable-stream: 3.6.2
tunnel-agent@0.6.0:
dependencies:
safe-buffer: 5.2.1
universalify@2.0.1: {}
util-deprecate@1.0.2: {}
which-module@2.0.1: {}
which@1.3.1:
dependencies:
isexe: 2.0.0
wrap-ansi@2.1.0:
dependencies:
string-width: 1.0.2
strip-ansi: 3.0.1
wrappy@1.0.2: {}
y18n@4.0.3: {}
yargs-parser@11.1.1:
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
yargs@12.0.5:
dependencies:
cliui: 4.1.0
decamelize: 1.2.0
find-up: 3.0.0
get-caller-file: 1.0.3
os-locale: 3.1.0
require-directory: 2.1.1
require-main-filename: 1.0.1
set-blocking: 2.0.0
string-width: 2.1.1
which-module: 2.0.1
y18n: 4.0.3
yargs-parser: 11.1.1

45
deps/publishing/script/publishing.cljs vendored Normal file
View File

@@ -0,0 +1,45 @@
(ns publishing
"Basic script for publishing from CLI"
(:require ["path" :as node-path]
[clojure.edn :as edn]
[datascript.core :as d]
[logseq.db.common.sqlite-cli :as sqlite-cli]
[logseq.db.sqlite.util :as sqlite-util]
[logseq.publishing :as publishing]
[nbb.core :as nbb]))
(defn- publish-db-graph [static-dir graph-dir output-path opts]
(let [db-name (node-path/basename graph-dir)
conn (sqlite-cli/open-db! (node-path/dirname graph-dir) db-name)
repo-config (-> (d/q '[:find ?content
:where [?b :file/path "logseq/config.edn"] [?b :file/content ?content]]
@conn)
ffirst
edn/read-string)]
(publishing/export @conn
static-dir
graph-dir
output-path
(merge opts {:repo (str sqlite-util/db-version-prefix db-name)
:repo-config repo-config
:ui/theme "dark"
:ui/radix-color :cyan}))))
(defn- resolve-path
"If relative path, resolve with $ORIGINAL_PWD"
[path]
(if (node-path/isAbsolute path)
path
(node-path/join (or js/process.env.ORIGINAL_PWD ".") path)))
(defn -main
[args]
(when (< (count args) 3)
(println "Usage: $0 STATIC-DIR GRAPH-DIR OUT-DIR")
(js/process.exit 1))
(let [[static-dir graph-dir output-path] (map resolve-path args)
options {:dev? (contains? (set args) "--dev")}]
(publish-db-graph static-dir graph-dir output-path options)))
(when (= nbb/*file* (nbb/invoked-file))
(-main *command-line-args*))

View File

@@ -0,0 +1,34 @@
(ns ^:node-only logseq.publishing
"This node only ns provides api fns for exporting a publishing app"
(:require [logseq.publishing.export :as publish-export]
[logseq.publishing.html :as publish-html]))
(defn- default-notification-fn [msg]
(if (= "error" (:type msg))
(throw (ex-info (:payload msg) {}))
(js/console.log (:payload msg))))
(defn export
"Exports the given graph-dir and db to the specific output-dir. Most of the graph
configuration is done through logseq/config.edn. There are a few explicit options that
can be passed:
* :ui/theme - Theme mode that can either be 'light' or 'dark'.
* :ui/radix-color - Accent color. See available values in Settings.
* :html-options - A map of values that are inserted into index.html. Map keys
can be icon, name, alias, title, description and url
* :repo - Name of repo
* :repo-config - A graph's configuration
* :default-notification-fn - Configure how errors are reported when creating the export.
Default is to throw an exception when it occurs."
[db static-dir graph-dir output-dir {:keys [notification-fn dev?]
:or {notification-fn default-notification-fn}
:as options}]
(let [options' (cond-> options
(:ui/theme options)
(assoc-in [:app-state :ui/theme] (:ui/theme options))
(:ui/radix-color options)
(assoc-in [:app-state :ui/radix-color] (:ui/radix-color options)))
{:keys [html asset-filenames]} (publish-html/build-html db options')]
(publish-export/create-export html static-dir graph-dir output-dir {:asset-filenames asset-filenames
:notification-fn notification-fn
:dev? dev?})))

View File

@@ -0,0 +1,179 @@
(ns logseq.publishing.db
"Provides db fns and associated util fns for publishing"
(:require [clojure.set :as set]
[datascript.core :as d]
[logseq.db.common.entity-plus :as entity-plus]
[logseq.db.frontend.malli-schema :as db-malli-schema]
[logseq.db.frontend.rules :as rules]))
(defn- get-db-public-pages
"Returns public pages and anything they are directly related to: their tags,
their properties and any property values that are pages. Anything on the
related pages are _not_ included e.g. properties on tag or property pages"
[db]
(let [pages (->> (d/q
'[:find ?p
:in $ %
:where (property ?p :logseq.property/publishing-public? true) [?p :block/name]]
db
(rules/extract-rules rules/db-query-dsl-rules [:property]))
(map first)
set)
page-ents (map #(d/entity db %) pages)
tag-pages* (mapcat #(map :db/id (:block/tags %)) page-ents)
tag-pages (concat tag-pages*
;; built-in property needs to be public to display tags
(when (seq tag-pages*)
(some-> (d/entity db :block/tags) :db/id vector)))
property-pages (mapcat (fn [ent]
(->> (keys (entity-plus/lookup-kv-then-entity ent :block/properties))
(map #(:db/id (d/entity db %)))))
page-ents)]
(concat pages tag-pages property-pages)))
(defn- get-db-public-false-pages
[db]
(->> (d/q
'[:find ?p
:in $ %
:where (property ?p :logseq.property/publishing-public? false) [?p :block/name]]
db
(rules/extract-rules rules/db-query-dsl-rules [:property]))
(map first)
set))
(defn- get-aliases-for-page-ids
[db page-ids]
(->> (d/q '[:find ?e
:in $ ?pages %
:where
[?page :block/name]
[(contains? ?pages ?page)]
(alias ?page ?e)]
db
(set page-ids)
(:alias rules/rules))
(map first)
set))
(defn- get-db-assets
[db]
(->> (d/q '[:find [(pull ?b [:block/uuid :logseq.property.asset/type]) ...]
:where [?b :block/tags :logseq.class/Asset]]
db)
(map #(str (:block/uuid %) "." (:logseq.property.asset/type %)))))
(defn- add-missing-built-in-block-timestamps
[db]
(let [tx-data (->> (d/datoms db :avet :logseq.property/built-in? true)
(map :e)
(map #(d/entity db %))
(mapcat
(fn [entity]
(let [created-at (or (:block/created-at entity)
(:block/updated-at entity)
0)
updated-at (or (:block/updated-at entity)
created-at)]
(cond-> []
(nil? (:block/created-at entity))
(conj [:db/add (:db/id entity) :block/created-at created-at])
(nil? (:block/updated-at entity))
(conj [:db/add (:db/id entity) :block/updated-at updated-at]))))))]
(if (seq tx-data)
(:db-after (d/with db tx-data))
db)))
(defn clean-export!
"Prepares a database assuming all pages are public unless a page has a publishing-public? property set to false"
[db]
(let [remove? #(contains? #{"file"} %)
non-public-datom-ids (get-db-public-false-pages db)
filtered-db (d/filter db
(fn [_db datom]
(let [ns' (namespace (:a datom))]
(and (not (remove? ns'))
(not (contains? non-public-datom-ids (:e datom)))
(not (and (contains? non-public-datom-ids (:v datom))
(= :block/page (:a datom))))))))
datoms (d/datoms filtered-db :eavt)
assets (get-db-assets filtered-db)]
;; (prn :public-counts :datoms (count datoms) :assets (count assets))
[(add-missing-built-in-block-timestamps @(d/conn-from-datoms datoms (:schema db))) assets]))
(defn- db-filter-only-public
[public-ents _db datom]
(contains? public-ents (:e datom)))
(defn- get-properties-on-nodes
[db nodes]
(->> (d/q '[:find ?p
:in $ [?node ...]
:where
[?p :db/ident ?a]
[?node ?a ?v]
[(missing? $ ?a :logseq.property/built-in?)]]
db
nodes)
(map first)
set))
(defn- get-property-values-on-nodes
[db nodes]
(->> (d/q '[:find ?pv
:in $ [?node ...]
:where
[?p :db/ident ?a]
[?p :db/valueType :db.type/ref]
[?node ?a ?pv]
[(missing? $ ?p :logseq.property/built-in?)]]
db
nodes)
(map first)
set))
(defn- get-db-public-ents
[db public-pages]
(let [page-blocks (->> (d/datoms db :avet :block/page)
(keep #(when (contains? public-pages (:v %)) (:e %)))
set)
public-nodes (into public-pages page-blocks)
eavt-datoms (d/datoms db :eavt)
tags (->> eavt-datoms
(keep #(when (and (contains? public-nodes (:e %)) (= :block/tags (:a %)))
(:v %)))
set)
properties (get-properties-on-nodes db public-nodes)
;; This makes nodes from other pages visible on a current public page.
;; BUT clicking on a node will not display the node's page
property-values (get-property-values-on-nodes db public-nodes)
internal-ents (set/union
(->> eavt-datoms
(keep #(when (and (= :db/ident (:a %)) (db-malli-schema/internal-ident? (:v %)))
(:e %)))
set)
(->> (d/datoms db :avet :logseq.property/built-in? true)
(map :e)
set))
ents (set/union internal-ents public-pages page-blocks properties property-values tags)]
#_(prn :public :pages (count public-pages) :page-blocks (count page-blocks)
:properties (count properties) :property-values (count property-values)
:tags (count tags) :internal (count internal-ents))
(when-let [invalid-ents (seq (remove integer? ents))]
(throw (ex-info (str "The following ents are invalid: " (pr-str (vec invalid-ents))) {})))
ents))
(defn filter-only-public-pages-and-blocks
"Prepares a database assuming all pages are private unless a page has a publishing-public? property set to true"
[db]
{:post [(some? %) (sequential? %)]}
(let [public-pages* (seq (get-db-public-pages db))
public-pages (set/union (set public-pages*)
(get-aliases-for-page-ids db public-pages*))
filter-fn (partial db-filter-only-public (get-db-public-ents db public-pages))
filtered-db (d/filter db filter-fn)
datoms (d/datoms filtered-db :eavt)
assets (get-db-assets filtered-db)]
;; (prn :private-counts :internal (count internal-ents) :datoms (count datoms) :assets (count assets))
[(add-missing-built-in-block-timestamps @(d/conn-from-datoms datoms (:schema db))) assets]))

View File

@@ -0,0 +1,125 @@
(ns ^:node-only logseq.publishing.export
"This electron only ns (for the main process) exports files from multiple
locations to provide a complete publishing app"
(:require ["fs" :as fs]
["fs-extra" :as fse]
["path" :as node-path]
[promesa.core :as p]))
(def ^:api js-files
"js files from publishing release build"
["main.js" "code-editor.js"])
(def ^:api static-dirs
"dirs under static dir to copy over"
["css" "icons" "img" "js"])
(defn- remove-js-source-maps
[output-static-dir]
(let [js-dir (node-path/join output-static-dir "js")]
(doseq [file (fs/readdirSync js-dir)]
(when (.endsWith file ".map")
(fs/rmSync (node-path/join js-dir file) #js {:force true})))))
(defn- default-notification
[msg]
(if (= (:type msg) "success")
(js/console.log (:payload msg))
(js/console.error (:payload msg))))
(defn- copy-file!
[from to]
(fs/mkdirSync (node-path/dirname to) #js {:recursive true})
(fs/copyFileSync from to))
(defn- copy-path!
[from to]
(let [stat (fs/statSync from)]
(cond
(.isDirectory stat)
(do
(fs/mkdirSync to #js {:recursive true})
(doseq [entry (fs/readdirSync from)]
(copy-path! (node-path/join from entry)
(node-path/join to entry))))
(.isFile stat)
(copy-file! from to))))
(defn- cleanup-js-dir
"Moves used js files to the correct dir and removes unused js files"
[output-static-dir source-static-dir {:keys [dev?]}]
(let [publishing-dir (node-path/join output-static-dir "js" "publishing")]
(p/let [_ (when-not dev? (remove-js-source-maps output-static-dir))
_ (p/all (map (fn [file]
(fs/rmSync (node-path/join output-static-dir "js" file) #js {:force true}))
js-files))
_ (when dev?
(fse/remove (node-path/join output-static-dir "js" "cljs-runtime")))
_ (p/all (map (fn [file]
(if dev?
(fs/symlinkSync
(node-path/join source-static-dir "js" "publishing" file)
(node-path/join output-static-dir "js" file))
(fs/renameSync
(node-path/join publishing-dir file)
(node-path/join output-static-dir "js" file))))
js-files))
_ (when dev?
(fs/symlinkSync (node-path/join source-static-dir "js" "publishing" "cljs-runtime")
(node-path/join output-static-dir "js" "cljs-runtime")))
;; remove publishing-dir
_ (when-not dev? (fse/remove publishing-dir))])))
(defn- copy-static-files-and-assets
[static-dir repo-path output-dir {:keys [log-error-fn asset-filenames]
:or {asset-filenames []
log-error-fn js/console.error}}]
(let [assets-from-dir (node-path/join repo-path "assets")
assets-to-dir (node-path/join output-dir "assets")
output-static-dir (node-path/join output-dir "static")]
(p/let [_ (fs/mkdirSync assets-to-dir #js {:recursive true})
_ (copy-path! (node-path/join static-dir "404.html")
(node-path/join output-dir "404.html"))
_ (doseq [part static-dirs]
(copy-path! (node-path/join static-dir part)
(node-path/join output-static-dir part)))
_ (p/all
(map
(fn [filename]
(-> (fse/copy (node-path/join assets-from-dir filename) (node-path/join assets-to-dir filename))
(p/catch
(fn [e]
(log-error-fn "Failed to copy"
(str {:from (node-path/join assets-from-dir filename)
:to (node-path/join assets-to-dir filename)})
e)))))
asset-filenames))])))
(defn create-export
"Given a graph's directory, the generated html and the directory containing
html/static assets, creates the export at the specified output-dir and
includes the index.html with supporting assets"
[html static-dir repo-path output-dir {:keys [notification-fn]
:or {notification-fn default-notification}
:as options}]
(let [custom-css-path (node-path/join repo-path "logseq" "custom.css")
export-css-path (node-path/join repo-path "logseq" "export.css")
custom-js-path (node-path/join repo-path "logseq" "custom.js")
output-static-dir (node-path/join output-dir "static")
index-html-path (node-path/join output-dir "index.html")]
(-> (p/let [_ (fs/mkdirSync output-static-dir #js {:recursive true})
_ (fs/writeFileSync index-html-path html)
_ (copy-static-files-and-assets static-dir repo-path output-dir options)
export-css (if (fs/existsSync export-css-path) (str (fs/readFileSync export-css-path)) "")
_ (fs/writeFileSync (node-path/join output-static-dir "css" "export.css") export-css)
custom-css (if (fs/existsSync custom-css-path) (str (fs/readFileSync custom-css-path)) "")
_ (fs/writeFileSync (node-path/join output-static-dir "css" "custom.css") custom-css)
custom-js (if (fs/existsSync custom-js-path) (str (fs/readFileSync custom-js-path)) "")
_ (fs/writeFileSync (node-path/join output-static-dir "js" "custom.js") custom-js)
_ (cleanup-js-dir output-static-dir static-dir options)]
(notification-fn {:type "success"
:payload (str "Export public pages and publish assets to " output-dir " successfully 🎉")}))
(p/catch (fn [error]
(notification-fn {:type "error"
:payload (str "Export public pages unexpectedly failed with: " error)}))))))

View File

@@ -0,0 +1,159 @@
(ns logseq.publishing.html
"This frontend only ns builds the publishing html including doing all the
necessary db filtering"
(:require [clojure.string :as string]
[datascript.core :as d]
[datascript.transit :as dt]
[goog.string :as gstring]
[goog.string.format]
[logseq.publishing.db :as db]))
;; Copied from hiccup but tweaked for publish usage
;; Any changes here should also be made in frontend.publishing/unescape-html
(defn- escape-html
"Change special characters into HTML character entities."
[text]
(-> text
(string/replace "&" "logseq____&amp;")
(string/replace "<" "logseq____&lt;")
(string/replace ">" "logseq____&gt;")
(string/replace "\"" "logseq____&quot;")
(string/replace "'" "logseq____&apos;")))
;; Copied from https://github.com/babashka/babashka/blob/8c1077af00c818ade9e646dfe1297bbe24b17f4d/examples/notes.clj#L21
(defn- html [v]
(cond (vector? v)
(let [tag (first v)
attrs (second v)
attrs (when (map? attrs) attrs)
elts (if attrs (nnext v) (next v))
tag-name (name tag)]
(gstring/format "<%s%s>%s</%s>\n" tag-name (html attrs) (html elts) tag-name))
(map? v)
(string/join ""
(keep (fn [[k v]]
;; Skip nil values because some html tags haven't been
;; given values through html-options
(when (some? v)
(gstring/format " %s=\"%s\"" (name k) v))) v))
(seq? v)
(string/join " " (map html v))
:else (str v)))
(defn- ^:large-vars/html publishing-html
[transit-db app-state options]
(let [{name' :name :keys [icon alias title description url]} options
icon (or icon "static/img/logo.png")
project (or alias name')]
(str "<!DOCTYPE html>\n"
(html
(list
[:head
[:meta {:charset "utf-8"}]
[:meta
{:content
"minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no",
:name "viewport"}]
[:link {:type "text/css", :href "static/css/style.css", :rel "stylesheet"}]
[:link {:type "text/css", :href "static/css/custom.css", :rel "stylesheet"}]
[:link {:type "text/css", :href "static/css/export.css", :rel "stylesheet"}]
[:link
{:href icon
:type "image/png",
:rel "shortcut icon"}]
[:link
{:href icon
:sizes "192x192",
:rel "shortcut icon"}]
[:link
{:href icon
:rel "apple-touch-icon"}]
[:meta {:name "apple-mobile-web-app-title" :content project}]
[:meta {:name "apple-mobile-web-app-capable" :content "yes"}]
[:meta {:name "apple-touch-fullscreen" :content "yes"}]
[:meta {:name "apple-mobile-web-app-status-bar-style" :content "black-translucent"}]
[:meta {:name "mobile-web-app-capable" :content "yes"}]
[:meta {:content title, :property "og:title"}]
[:meta {:content "site", :property "og:type"}]
(when url [:meta {:content url, :property "og:url"}])
[:meta
{:content icon
:property "og:image"}]
[:meta
{:content description
:property "og:description"}]
[:title title]
[:meta {:content project, :property "og:site_name"}]
[:meta
{:description description}]]
[:body
[:div {:id "root"}]
[:script (gstring/format "window.logseq_db=%s" (js/JSON.stringify (escape-html transit-db)))]
[:script (str "window.logseq_state=" (js/JSON.stringify (pr-str app-state)))]
[:script {:type "text/javascript"}
"// Single Page Apps for GitHub Pages
// https://github.com/rafgraph/spa-github-pages
// Copyright (c) 2016 Rafael Pedicini, licensed under the MIT License
// ----------------------------------------------------------------------
// This script checks to see if a redirect is present in the query string
// and converts it back into the correct url and adds it to the
// browser's history using window.history.replaceState(...),
// which won't cause the browser to attempt to load the new url.
// When the single page app is loaded further down in this file,
// the correct url will be waiting in the browser's history for
// the single page app to route accordingly.
(function(l) {
if (l.search) {
var q = {};
l.search.slice(1).split('&').forEach(function(v) {
var a = v.split('=');
q[a[0]] = a.slice(1).join('=').replace(/~and~/g, '&');
});
if (q.p !== undefined) {
window.history.replaceState(null, null,
l.pathname.slice(0, -1) + (q.p || '') +
(q.q ? ('?' + q.q) : '') +
l.hash
);
}
}
}(window.location))"]
;; TODO: should make this configurable
[:script {:src "static/js/react.production.min.js"}]
[:script {:src "static/js/react-dom.production.min.js"}]
[:script {:src "static/js/main.js"}]
;; Deferring scripts above results in errors
[:script {:defer true :src "static/js/interact.min.js"}]
[:script {:defer true :src "static/js/highlight.min.js"}]
[:script {:defer true :src "static/js/katex.min.js"}]
[:script {:defer true :type "module" :src "static/js/pdfjs/pdf.mjs"}]
[:script {:defer true :type "module" :src "static/js/pdf_viewer3.mjs"}]
[:script {:defer true :src "static/js/html2canvas.min.js"}]
[:script {:defer true :src "static/js/code-editor.js"}]
[:script {:defer true :src "static/js/custom.js"}]])))))
(defn build-html
"Given the graph's db, filters the db using the given options and returns the
generated index.html string and assets used by the html"
[db* {:keys [repo app-state repo-config html-options dev?]}]
(let [all-pages-public? (if-let [value (:publishing/all-pages-public? repo-config)]
value
(:all-pages-public? repo-config))
[db asset-filenames'] (if all-pages-public?
(db/clean-export! db*)
(db/filter-only-public-pages-and-blocks db*))
_ (when dev?
(println "Exporting" (count (d/datoms db :eavt)) "of" (count (d/datoms db* :eavt)) "datoms and"
(count asset-filenames') "asset(s)..."))
asset-filenames (remove nil? asset-filenames')
db-str (dt/write-transit-str db)
;; The repo-name is used by the client and thus determines whether
;; it's a db graph or not
state (assoc app-state
:config {repo repo-config})
raw-html-str (publishing-html db-str state html-options)]
{:html raw-html-str
:asset-filenames asset-filenames}))

View File

@@ -0,0 +1,114 @@
(ns logseq.publishing.db-test
(:require [cljs.test :refer [deftest is]]
[clojure.set :as set]
[datascript.core :as d]
[logseq.common.config :as common-config]
[logseq.db :as ldb]
[logseq.db.frontend.validate :as db-validate]
[logseq.db.test.helper :as db-test]
[logseq.publishing.db :as publish-db]))
(deftest clean-export!
(let [conn (db-test/create-conn-with-blocks
[{:page {:block/title "page1"
:build/properties {:logseq.property/publishing-public? false}}
:blocks [{:block/title "b11"}
{:block/title "b12"}
{:block/title "![awesome.png](../assets/awesome_1648822509908_0.png"}]}
{:page {:block/title "page2"}
:blocks [{:block/title "b21"}
{:block/title "![thumb-on-fire.PNG](../assets/thumb-on-fire_1648822523866_0.PNG)"}]}
{:page {:block/title "page3"}
:blocks [{:block/title "b31"}]}])
[filtered-db _assets] (publish-db/clean-export! @conn)
exported-pages (->> (d/q '[:find (pull ?b [*])
:where [?b :block/name]]
filtered-db)
(map (comp :block/name first))
set)
exported-blocks (->> (d/q '[:find (pull ?p [*])
:where
[?b :block/title]
[?b :block/page ?p]
[(missing? $ ?p :logseq.property/built-in?)]]
filtered-db)
(map (comp :block/name first))
set)]
(is (set/subset? #{"page2" "page3"} exported-pages)
"Contains all pages that haven't been marked private")
(is (not (contains? exported-pages "page1"))
"Doesn't contain private page")
(is (= #{"page2" "page3"} exported-blocks)
"Only exports blocks from public pages")
;; TODO: Create assets to test this
#_(is (= ["thumb-on-fire_1648822523866_0.PNG"] assets)
"Only exports assets from public pages")))
(deftest filter-only-public-pages-and-blocks-adds-missing-built-in-page-timestamps
(let [conn (db-test/create-conn-with-blocks
[{:page {:block/title "page1"
:build/properties {:logseq.property/publishing-public? true}}
:blocks [{:block/title "b1"}]}
{:page {:block/title common-config/views-page-name
:build/properties {:logseq.property/built-in? true
:logseq.property/hide? true}}
:blocks []}])
views-page (ldb/get-page @conn common-config/views-page-name)
_ (d/transact! conn
[[:db/retract (:db/id views-page) :block/created-at (:block/created-at views-page)]
[:db/retract (:db/id views-page) :block/updated-at (:block/updated-at views-page)]])
[filtered-db _assets] (publish-db/filter-only-public-pages-and-blocks @conn)
exported-views-page (ldb/get-page filtered-db common-config/views-page-name)]
(is (int? (:block/created-at exported-views-page))
"Missing created-at is added to exported built-in pages")
(is (int? (:block/updated-at exported-views-page))
"Missing updated-at is added to exported built-in pages")
(is (empty? (:errors (db-validate/validate-db filtered-db)))
"Publishing DB remains valid after keeping hidden built-in pages")))
(deftest filter-only-public-pages-and-blocks
(let [conn (db-test/create-conn-with-blocks
[{:page {:block/title "page1"
:build/properties {:logseq.property/publishing-public? false}}
:blocks [{:block/title "b11"}
{:block/title "b12"}
{:block/title "![awesome.png](../assets/awesome_1648822509908_0.png"}]}
{:page {:block/title "page2"
:build/properties {:logseq.property/publishing-public? true
:block/alias #{[:build/page {:block/title "page2-alias"}]}}}
:blocks [{:block/title "b21"}
{:block/title "![thumb-on-fire.PNG](../assets/thumb-on-fire_1648822523866_0.PNG)"}]}
{:page {:block/title "page3"
:build/properties {:logseq.property/publishing-public? true}}
:blocks [{:block/title "b31"}]}])
[filtered-db _assets] (publish-db/filter-only-public-pages-and-blocks @conn)
exported-pages (->> (d/q '[:find (pull ?b [*])
:where [?b :block/name]]
filtered-db)
(map (comp :block/name first))
set)
exported-block-pages (->> (d/q '[:find (pull ?p [*])
:where
[?b :block/title]
[?b :block/page ?p]
[(missing? $ ?p :logseq.property/built-in?)]]
filtered-db)
(map (comp :block/name first))
set)
exported-page2-children (->> (:block/_parent (ldb/get-page filtered-db "page2"))
(map :block/title)
set)]
(is (set/subset? #{"page2" "page3"} exported-pages)
"Contains all pages that have been marked public")
(is (not (contains? exported-pages "page1"))
"Doesn't contain private page")
(is (seq (ldb/get-page filtered-db "page2-alias"))
"Alias of public page is exported")
(is (= #{"page2" "page3"} exported-block-pages)
"Only exports blocks from public pages")
(is (contains? exported-page2-children "b21")
"Public page children are still available through block parent refs")
;; TODO: Create assets to test this
#_(is (= ["thumb-on-fire_1648822523866_0.PNG"] assets)
"Only exports assets from public pages")))

View File

@@ -0,0 +1,142 @@
(ns logseq.publishing.export-test
(:require [cljs.test :as t :refer [is use-fixtures async]]
[logseq.publishing.test.helper :as test-helper :include-macros true :refer [deftest-async]]
[logseq.publishing.export :as publish-export]
[promesa.core :as p]
[clojure.set :as set]
["fs" :as fs]
["path" :as path]))
(use-fixtures
:each
;; Cleaning tmp/ before leaves last tmp/ after a test run for dev and debugging
{:before
#(async done
(if (fs/existsSync "tmp")
(fs/rm "tmp" #js {:recursive true} (fn [err]
(when err (js/console.log err))
(done)))
(done)))})
(defn get-dirs [path]
(->> (fs/readdirSync path)
(map #(path/join path %))
(filter #(.isDirectory (fs/statSync %)))))
(defn get-files [path]
(->> (fs/readdirSync path)
(map #(path/join path %))
(filter #(.isFile (fs/statSync %)))))
(defn get-files-recursively [dir]
(let [dirs (get-dirs dir)]
(->> dirs
(map get-files-recursively)
(reduce concat)
(concat (get-files dir)))))
(defn- js-map-files []
(map #(str % ".map") publish-export/js-files))
(defn- create-export
[static-dir graph-dir output-dir {:keys [html assets]
:or {html "<!DOCTYPE html>"
assets []}}]
(publish-export/create-export
html
static-dir
graph-dir
output-dir
{:asset-filenames assets
:notification-fn (fn [msg]
(if (= "error" (:type msg))
(throw (ex-info (:payload msg) {}))
(js/console.log (:payload msg))))}))
(defn- create-static-dir [dir]
(fs/mkdirSync (path/join dir) #js {:recursive true})
(mapv #(fs/mkdirSync (path/join dir %)) publish-export/static-dirs)
(fs/mkdirSync (path/join dir "css" "fonts") #js {:recursive true})
(fs/writeFileSync (path/join dir "css" "style.css") "style")
(fs/writeFileSync (path/join dir "css" "fonts" "font.woff2") "font")
(fs/mkdirSync (path/join dir "js" "publishing"))
(mapv #(fs/writeFileSync (path/join dir "js" "publishing" %) %)
(conj publish-export/js-files "manifest.edn"))
(doseq [file (conj (vec (js-map-files)) "db-worker.js.map")]
(fs/writeFileSync (path/join dir "js" file) file))
(fs/writeFileSync (path/join dir "404.html") ""))
(defn- create-logseq-graph
"Creates a minimal graph to test publishing"
[dir]
(fs/mkdirSync (path/join dir "logseq") #js {:recursive true})
(fs/writeFileSync (path/join dir "logseq" "config.edn") "{}")
(fs/mkdirSync (path/join dir "assets")))
(deftest-async create-export-with-basic-graph
(create-static-dir "tmp/static")
(create-logseq-graph "tmp/test-graph")
(p/let [_ (create-export "tmp/static" "tmp/test-graph" "tmp/published-graph" {:html "<div>WOOT</div>"})]
(let [original-paths (map path/basename (get-files-recursively "tmp/static"))
copied-paths (map path/basename (get-files-recursively "tmp/published-graph"))
new-files (set/difference (set copied-paths) (set original-paths))]
(is (= #{"index.html" "custom.css" "export.css" "custom.js"}
new-files)
"A published graph has the correct new files")
(is (= "<div>WOOT</div>"
(str (fs/readFileSync "tmp/published-graph/index.html")))
"index.html is copied correctly")
(is (= "main.js"
(str (fs/readFileSync "tmp/published-graph/static/js/main.js")))
"cljs frontend compiled as main.js is copied correctly")
(is (= "style"
(str (fs/readFileSync "tmp/published-graph/static/css/style.css")))
"static files are copied recursively")
(is (= "font"
(str (fs/readFileSync "tmp/published-graph/static/css/fonts/font.woff2")))
"nested static files are copied recursively")
(is (empty? (filter #(.endsWith (path/basename %) ".map")
(get-files "tmp/published-graph/static/js")))
"source maps are not copied"))))
(deftest-async create-export-with-css-files
(create-static-dir "tmp/static")
(create-logseq-graph "tmp/test-graph")
(fs/writeFileSync "tmp/test-graph/logseq/custom.css" ".foo {background-color: blue}")
(fs/writeFileSync "tmp/test-graph/logseq/export.css" ".foo {background-color: red}")
(p/let [_ (create-export "tmp/static" "tmp/test-graph" "tmp/published-graph" {})]
(is (= ".foo {background-color: blue}"
(str (fs/readFileSync "tmp/published-graph/static/css/custom.css")))
"custom.css is copied correctly")
(is (= ".foo {background-color: red}"
(str (fs/readFileSync "tmp/published-graph/static/css/export.css")))
"export.css is copied correctly")))
(deftest-async create-export-with-js-files
(create-static-dir "tmp/static")
(create-logseq-graph "tmp/test-graph")
(fs/writeFileSync "tmp/test-graph/logseq/custom.js" "// foo")
(p/let [_ (create-export "tmp/static" "tmp/test-graph" "tmp/published-graph" {})]
(is (= "// foo"
(str (fs/readFileSync "tmp/published-graph/static/js/custom.js")))
"custom.js is copied correctly")))
(deftest-async create-export-with-assets
(create-static-dir "tmp/static")
(create-logseq-graph "tmp/test-graph")
(fs/writeFileSync "tmp/test-graph/assets/foo.jpg" "foo")
(fs/writeFileSync "tmp/test-graph/assets/bar.png" "bar")
(p/let [_ (create-export "tmp/static"
"tmp/test-graph"
"tmp/published-graph"
{:assets ["foo.jpg" "bar.png"]})]
(is (= "foo"
(str (fs/readFileSync "tmp/published-graph/assets/foo.jpg")))
"first asset is copied correctly")
(is (= "bar"
(str (fs/readFileSync "tmp/published-graph/assets/bar.png")))
"second asset is copied correctly")))

View File

@@ -0,0 +1,25 @@
(ns logseq.publishing.test.helper)
;; Copied from https://github.com/babashka/nbb/blob/e5d84b0fac59774f5d7a4a9e807240cce04bf252/test/nbb/test_macros.clj
(defmacro deftest-async
"A wrapper around deftest that handles async and done in all cases.
Importantly, it prevents unexpected failures in an async test from abruptly
ending a test suite"
[name' opts & body]
(let [[opts body]
(if (map? opts)
[opts body]
[nil (cons opts body)])]
`(cljs.test/deftest ~name'
~@(when-let [pre (:before opts)]
[pre])
(cljs.test/async
~'done
(-> (do ~@body)
(.catch (fn [err#]
(cljs.test/is (= 1 0) (str err# (.-stack err#)))))
(.finally
(fn []
~@(when-let [post (:after opts)]
[post])
(~'done))))))))