mirror of
https://github.com/logseq/logseq.git
synced 2026-04-24 22:25:01 +00:00
Merge remote-tracking branch 'origin2/master' into fix/priority-style-issues
# Conflicts: # package.json # resources/css/common.css # src/main/frontend/components/block.cljs # src/main/frontend/components/page.cljs # src/main/frontend/components/sidebar.css # yarn.lock
This commit is contained in:
89
README.md
89
README.md
@@ -62,51 +62,12 @@ The following is for developers and designers who want to build and run Logseq l
|
|||||||
|
|
||||||
## Set up development environment
|
## Set up development environment
|
||||||
|
|
||||||
If you are on Windows, use the [Windows setup](#windows-setup) below.
|
|
||||||
|
|
||||||
### 1. Requirements
|
### 1. Requirements
|
||||||
|
|
||||||
|
- [Node.js](https://nodejs.org/en/download/) & [Yarn](https://classic.yarnpkg.com/en/docs/install/)
|
||||||
- [Java & Clojure](https://clojure.org/guides/getting_started)
|
- [Java & Clojure](https://clojure.org/guides/getting_started)
|
||||||
|
|
||||||
- [PostgreSQL](https://www.postgresql.org/download/)
|
### 2. Compile to JavaScript
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org/en/download/) & [Yarn](https://classic.yarnpkg.com/en/docs/install/)
|
|
||||||
|
|
||||||
### 2. Create a GitHub app
|
|
||||||
|
|
||||||
Follow the guide at <https://docs.github.com/en/free-pro-team@latest/developers/apps/creating-a-github-app>, where the user authorization "Callback URL" should be `http://localhost:3000/auth/github`.
|
|
||||||
|
|
||||||
Remember to download the `private-key.pem` which will be used for the next step. Also take note of your `App ID`, `Client ID`, and your newly generated `Client Secret` for use in step 4.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
**Add contents permission**:
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
### 3. Set up PostgreSQL
|
|
||||||
|
|
||||||
Make sure you have PostgreSQL running. You can check if it's running with `pg_ctl -D /usr/local/var/postgres status` and use `pg_ctl -D /usr/local/var/postgres start` to start it up. You'll also need to make a Logseq DB in PostgreSQL. Do that with `createdb logseq`.
|
|
||||||
|
|
||||||
### 4. Add environment variables
|
|
||||||
|
|
||||||
``` bash
|
|
||||||
export ENVIRONMENT="dev"
|
|
||||||
export JWT_SECRET="xxxxxxxxxxxxxxxxxxxx"
|
|
||||||
export COOKIE_SECRET="xxxxxxxxxxxxxxxxxxxx"
|
|
||||||
export DATABASE_URL="postgres://localhost:5432/logseq"
|
|
||||||
export GITHUB_APP2_NAME="logseq-test-your-username-app"
|
|
||||||
export GITHUB_APP2_ID="your id"
|
|
||||||
export GITHUB_APP2_KEY="xxxxxxxxxxxxxxxxxxxx" #Your Github App's Client ID
|
|
||||||
export GITHUB_APP2_SECRET="xxxxxxxxxxxxxxxxxxxx"
|
|
||||||
# Replace your-code-directory and your-app.private-key.pem with yours
|
|
||||||
export GITHUB_APP_PEM="/your-code-directory/your-app.private-key.pem"
|
|
||||||
export LOG_PATH="/tmp/logseq"
|
|
||||||
export PG_USERNAME="xxx"
|
|
||||||
export PG_PASSWORD="xxx"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Compile to JavaScript
|
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
git clone https://github.com/logseq/logseq
|
git clone https://github.com/logseq/logseq
|
||||||
@@ -114,46 +75,12 @@ yarn
|
|||||||
yarn watch
|
yarn watch
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. Start the Clojure server
|
### 3. Open the browser
|
||||||
|
|
||||||
1. Download jar
|
Open <http://localhost:3001>.
|
||||||
|
|
||||||
Go to <https://github.com/logseq/logseq/releases>, download the `logseq.jar` and put it in the `logseq` directory.
|
### 4. Build a release
|
||||||
|
|
||||||
2. Run jar
|
``` bash
|
||||||
|
yarn release
|
||||||
``` bash
|
```
|
||||||
java -Duser.timezone=UTC -jar logseq.jar
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7. Open the browser
|
|
||||||
|
|
||||||
Open <http://localhost:3000>.
|
|
||||||
|
|
||||||
## Windows setup
|
|
||||||
|
|
||||||
### 1. Required software
|
|
||||||
|
|
||||||
Install Clojure through scoop-clojure: <https://github.com/littleli/scoop-clojure>. You can also install [Node.js](https://nodejs.org/en/), [Yarn](https://yarnpkg.com/) and [PostgreSQL](https://www.postgresql.org/download/) through scoop if you want to.
|
|
||||||
|
|
||||||
### 2. Create a GitHub app
|
|
||||||
|
|
||||||
Follow [Step 2](#2-create-a-github-app) above if you want Logseq to connect to GitHub. If not, skip this section. The `GITHUB_APP_PEM` variable in the `run-windows.bat` needs to be set with the correct directory for your system.
|
|
||||||
|
|
||||||
### 3. Set up PostgreSQL
|
|
||||||
|
|
||||||
Make sure you have PostgreSQL running. You can check if it's running with `pg_ctl status` and use `pg_ctl start` to start it up. You'll also need to make a Logseq DB in PostgreSQL. Do that with `createdb logseq`.
|
|
||||||
|
|
||||||
### 4. Download the Clojure server
|
|
||||||
|
|
||||||
Go to <https://github.com/logseq/logseq/releases>, download the `logseq.jar` and move into the root directory of repo.
|
|
||||||
|
|
||||||
### 5. Start Logseq
|
|
||||||
|
|
||||||
Run `start-windows.bat` which is located in the repo. This will open a second terminal that runs Logseq's backend server. To completely stop Logseq, you'll need to also close that second terminal that was opened.
|
|
||||||
|
|
||||||
`start-windows.bat` will try to start PostgreSQL for you if it's not already started.
|
|
||||||
|
|
||||||
## Build errors
|
|
||||||
### 1. The required namespace `devtools.preload` is not available.
|
|
||||||
Upload your clojure to at least version `1.10.1.739`.
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
@echo off
|
|
||||||
cmd-clojure %*
|
|
||||||
14
externs.js
14
externs.js
@@ -33,3 +33,17 @@ dummy.zoomToFit = function() {};
|
|||||||
dummy.folder = function() {};
|
dummy.folder = function() {};
|
||||||
dummy.file = function() {};
|
dummy.file = function() {};
|
||||||
dummy.generateAsync = function() {};
|
dummy.generateAsync = function() {};
|
||||||
|
dummy.showOpenFilePicker = function() {};
|
||||||
|
dummy.showDirectoryPicker = function() {};
|
||||||
|
dummy.getDirectoryHandle = function() {};
|
||||||
|
dummy.getFileHandle = function() {};
|
||||||
|
dummy.removeEntry = function() {};
|
||||||
|
dummy.getFile = function() {};
|
||||||
|
dummy.text = function() {};
|
||||||
|
dummy.requestPermission = function() {};
|
||||||
|
dummy.queryPermission = function() {};
|
||||||
|
dummy.verifyPermission = function() {};
|
||||||
|
dummy.createWritable = function() {};
|
||||||
|
dummy.write = function() {};
|
||||||
|
dummy.close = function() {};
|
||||||
|
dummy.values = function() {};
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"tailwindcss": "2.0.1"
|
"tailwindcss": "2.0.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"watch": "run-p cljs:watch gulp:watch",
|
"watch": "run-p cljs:watch gulp:build gulp:watch",
|
||||||
"release": "run-s cljs:release gulp:build",
|
"release": "run-s cljs:release gulp:build",
|
||||||
"watch-app": "run-p cljs:watch-app gulp:watch",
|
"watch-app": "run-p cljs:watch-app gulp:watch",
|
||||||
"release-app": "run-s cljs:release-app gulp:build",
|
"release-app": "run-s cljs:release-app gulp:build",
|
||||||
@@ -27,8 +27,9 @@
|
|||||||
"dev-release-app": "run-s cljs:dev-release-app gulp:build",
|
"dev-release-app": "run-s cljs:dev-release-app gulp:build",
|
||||||
"clean": "gulp clean",
|
"clean": "gulp clean",
|
||||||
"test": "run-s cljs:test cljs:run-test",
|
"test": "run-s cljs:test cljs:run-test",
|
||||||
|
"report": "run-s cljs:report",
|
||||||
"gulp:watch": "gulp watch",
|
"gulp:watch": "gulp watch",
|
||||||
"gulp:build": "NODE_ENV=production gulp build",
|
"gulp:build": "cross-env NODE_ENV=production gulp build",
|
||||||
"cljs:watch": "clojure -M:cljs watch app publishing",
|
"cljs:watch": "clojure -M:cljs watch app publishing",
|
||||||
"cljs:release": "clojure -M:cljs release app publishing",
|
"cljs:release": "clojure -M:cljs release app publishing",
|
||||||
"cljs:test": "clojure -A:test compile test",
|
"cljs:test": "clojure -A:test compile test",
|
||||||
@@ -44,6 +45,7 @@
|
|||||||
"codemirror": "^5.58.1",
|
"codemirror": "^5.58.1",
|
||||||
"diff": "^4.0.2",
|
"diff": "^4.0.2",
|
||||||
"dropbox": "^5.2.0",
|
"dropbox": "^5.2.0",
|
||||||
|
"ignore": "^5.1.8",
|
||||||
"jszip": "^3.5.0",
|
"jszip": "^3.5.0",
|
||||||
"localforage": "^1.7.3",
|
"localforage": "^1.7.3",
|
||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
|
|||||||
16
public/index.html
Normal file
16
public/index.html
Normal file
File diff suppressed because one or more lines are too long
1
public/static
Symbolic link
1
public/static
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../static
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html><head><meta charset="utf-8"><meta content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no" name="viewport"><meta content="Agp2znmEoRKqxMhzbNL2R3UOCNcagP7+fu0KSM+09O21u7EHdJgqhTrslpfyFC/dSt6jvpaDzNiFf2769fLHMAUAAABoeyJvcmlnaW4iOiJodHRwczovL2xvZ3NlcS5jb206NDQzIiwiZmVhdHVyZSI6Ik5hdGl2ZUZpbGVTeXN0ZW0yIiwiZXhwaXJ5IjoxNTk3Mjg5MzY5LCJpc1N1YmRvbWFpbiI6dHJ1ZX0=" http-equiv="origin-trial"><link href="https://asset.logseq.com/static/style.css" rel="stylesheet" type="text/css"><link href="https://asset.logseq.com/static/img/logo.png" rel="shortcut icon" type="image/png"><link href="https://asset.logseq.com/static/img/logo.png" rel="shortcut icon" sizes="192x192"><link href="https://asset.logseq.com/static/img/logo.png" rel="apple-touch-icon"><meta content="summary" name="twitter:card"><meta content="A local-first notes app which uses Git to store and sync your knowledge." name="twitter:description"><meta content="@logseq" name="twitter:site"><meta content="A local-first notes app." name="twitter:title"><meta content="https://asset.logseq.com/static/img/logo.png" name="twitter:image:src"><meta content="A local-first notes app." name="twitter:image:alt"><meta content="A local-first notes app." property="og:title"><meta content="site" property="og:type"><meta content="https://logseq.com" property="og:url"><meta content="https://asset.logseq.com/static/img/logo.png" property="og:image"><meta content="A local-first notes app which uses Git to store and sync your knowledge." property="og:description"><title>Logseq: A local-first notes app</title><meta content="logseq" property="og:site_name"><meta description="A local-first notes app which uses Git to store and sync your knowledge."><script crossorigin="anonymous" defer onload="if (window.location.host != 'localhost:3000') {
|
|
||||||
Sentry.init({dsn: 'https://636e9174ffa148c98d2b9d3369661683@o416451.ingest.sentry.io/5311485'});
|
|
||||||
};" src="https://asset.logseq.com/static/js/sentry.min.js"></script></head><body><div id="root"></div><script>window.user={"name":"tiensonqin","email":"tiensonqin@gmail.com","avatar":"https://avatars3.githubusercontent.com/u/479169?v=4","repos":[{"id":"bc80efff-1420-4eb7-9e07-9506b8d9bbe0","url":"https://github.com/tiensonqin/notes"}],"preferred_format":"org","encrypt_object_key":"snRsaP8r9VG6KsXxu0IfDA"};</script><script src="https://asset.logseq.com/static/js/mldoc.min.js"></script><script src="/js/magic_portal.js"></script><script>let worker = new Worker("/js/worker.js");
|
|
||||||
const portal = new MagicPortal(worker);
|
|
||||||
;(async () => {
|
|
||||||
const git = await portal.get('git');
|
|
||||||
window.git = git;
|
|
||||||
const fs = await portal.get('fs');
|
|
||||||
window.fs = fs;
|
|
||||||
const pfs = await portal.get('pfs');
|
|
||||||
window.pfs = pfs;
|
|
||||||
const workerThread = await portal.get('workerThread');
|
|
||||||
window.workerThread = workerThread;
|
|
||||||
})();
|
|
||||||
</script><script src="https://asset.logseq.com/static/js/main.js"></script><script>
|
|
||||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
||||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
||||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
||||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
|
||||||
|
|
||||||
ga('create', 'UA-171599883-1', 'logseq.com');
|
|
||||||
ga('send', 'pageview');
|
|
||||||
</script></body></html>
|
|
||||||
8
resources/js/lightning-fs.min.js
vendored
Normal file
8
resources/js/lightning-fs.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
resources/js/magic_portal.js
Normal file
2
resources/js/magic_portal.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.MagicPortal=t()}(this,function(){var e=function(e){var t=this;this.rpc_counter=0,this.channel=e,this.foreign=new Map,this.local=new Map,this.calls=new Map,this.queue=[],this.connectionEstablished=!1,this.channel.addEventListener("message",function(e){var n=e.data;if(n&&"object"==typeof n)switch(n.type){case"MP_INIT":return t.onInit(n);case"MP_SET":return t.onSet(n);case"MP_CALL":return t.onCall(n);case"MP_RETURN":return t.onReturn(n)}}),this.channel.postMessage({type:"MP_INIT",id:1,reply:!0})};e.prototype.onInit=function(e){this.connectionEstablished=!0;var t=this.queue;this.queue=[];for(var n=0,o=t;n<o.length;n+=1){this.channel.postMessage(o[n])}e.reply&&this.channel.postMessage({type:"MP_INIT",reply:!1})},e.prototype.onSet=function(e){for(var t=this,n={},o=e.object,i=function(){var i=r[s],c=!e.void.includes(i);n[i]=function(){for(var e=[],n=arguments.length;n--;)e[n]=arguments[n];return t.rpc_counter=(t.rpc_counter+1)%Number.MAX_SAFE_INTEGER,new Promise(function(n,s){t.postMessage({type:"MP_CALL",object:o,method:i,id:t.rpc_counter,args:e,reply:c}),c?t.calls.set(t.rpc_counter,{resolve:n,reject:s}):n()})}},s=0,r=e.methods;s<r.length;s+=1)i();var c=this.foreign.get(e.object);this.foreign.set(e.object,n),"function"==typeof c&&c(n)},e.prototype.onCall=function(e){var t=this,n=this.local.get(e.object);n&&n[e.method].apply(n,e.args).then(function(n){return e.reply&&t.channel.postMessage({type:"MP_RETURN",id:e.id,result:n})}).catch(function(n){return t.channel.postMessage({type:"MP_RETURN",id:e.id,error:n.message})})},e.prototype.onReturn=function(e){if(this.calls.has(e.id)){var t=this.calls.get(e.id),n=t.resolve,o=t.reject;this.calls.delete(e.id),e.error?o(e.error):n(e.result)}},e.prototype.postMessage=function(e){this.connectionEstablished?this.channel.postMessage(e):this.queue.push(e)},e.prototype.set=function(e,t,n){void 0===n&&(n={}),this.local.set(e,t);var o=Object.entries(t).filter(function(e){return"function"==typeof e[1]}).map(function(e){return e[0]});this.postMessage({type:"MP_SET",object:e,methods:o,void:n.void||[]})},e.prototype.get=function(e){return new Promise(function(t,n){var o=this;return this.foreign.has(e)?t(this.foreign.get(e)):t(new Promise(function(t,n){return o.foreign.set(e,t)}))}.bind(this))};return function(t){var n=new e(t);Object.defineProperties(this,{get:{writable:!1,configurable:!1,value:n.get.bind(n)},set:{writable:!1,configurable:!1,value:n.set.bind(n)}})}});
|
||||||
|
//# sourceMappingURL=index.umd.js.map
|
||||||
303
resources/js/worker.js
Normal file
303
resources/js/worker.js
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
importScripts(
|
||||||
|
// Batched optimization
|
||||||
|
"/static/js/lightning-fs.min.js?v=0.0.2.3",
|
||||||
|
"https://cdn.jsdelivr.net/npm/isomorphic-git@1.7.4/index.umd.min.js",
|
||||||
|
"https://cdn.jsdelivr.net/npm/isomorphic-git@1.7.4/http/web/index.umd.js",
|
||||||
|
// Fixed a bug
|
||||||
|
"/static/js/magic_portal.js"
|
||||||
|
);
|
||||||
|
|
||||||
|
const detect = () => {
|
||||||
|
if (typeof window !== 'undefined' && !self.skipWaiting) {
|
||||||
|
return 'window'
|
||||||
|
} else if (typeof self !== 'undefined' && !self.skipWaiting) {
|
||||||
|
return 'Worker'
|
||||||
|
} else if (typeof self !== 'undefined' && self.skipWaiting) {
|
||||||
|
return 'ServiceWorker'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function basicAuth (username, token) {
|
||||||
|
return "Basic " + btoa(username + ":" + token);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fsName = 'logseq';
|
||||||
|
const createFS = () => new LightningFS(fsName);
|
||||||
|
let fs = createFS();
|
||||||
|
let pfs = fs.promises;
|
||||||
|
|
||||||
|
if (detect() === 'Worker') {
|
||||||
|
const portal = new MagicPortal(self);
|
||||||
|
portal.set('git', git);
|
||||||
|
portal.set('fs', fs);
|
||||||
|
portal.set('pfs', pfs);
|
||||||
|
portal.set('gitHttp', GitHttp);
|
||||||
|
portal.set('workerThread', {
|
||||||
|
setConfig: function (dir, path, value) {
|
||||||
|
return git.setConfig ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
path,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
clone: function (dir, url, corsProxy, depth, branch, username, token) {
|
||||||
|
return git.clone ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
http: GitHttp,
|
||||||
|
url,
|
||||||
|
corsProxy,
|
||||||
|
ref: branch,
|
||||||
|
singleBranch: true,
|
||||||
|
depth,
|
||||||
|
headers: {
|
||||||
|
"Authorization": basicAuth(username, token)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fetch: function (dir, url, corsProxy, depth, branch, username, token) {
|
||||||
|
return git.fetch ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
http: GitHttp,
|
||||||
|
url,
|
||||||
|
corsProxy,
|
||||||
|
ref: branch,
|
||||||
|
singleBranch: true,
|
||||||
|
depth,
|
||||||
|
headers: {
|
||||||
|
"Authorization": basicAuth(username, token)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
pull: function (dir, corsProxy, branch, username, token) {
|
||||||
|
return git.pull ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
http: GitHttp,
|
||||||
|
corsProxy,
|
||||||
|
ref: branch,
|
||||||
|
singleBranch: true,
|
||||||
|
// fast: true,
|
||||||
|
headers: {
|
||||||
|
"Authorization": basicAuth(username, token)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
push: function (dir, corsProxy, branch, force, username, token) {
|
||||||
|
return git.push ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
http: GitHttp,
|
||||||
|
ref: branch,
|
||||||
|
corsProxy,
|
||||||
|
remote: "origin",
|
||||||
|
force,
|
||||||
|
headers: {
|
||||||
|
"Authorization": basicAuth(username, token)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
merge: function (dir, branch) {
|
||||||
|
return git.merge ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
ours: branch,
|
||||||
|
theirs: "remotes/origin/" + branch,
|
||||||
|
// fastForwardOnly: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
checkout: function (dir, branch) {
|
||||||
|
return git.checkout ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
ref: branch,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
log: function (dir, branch, depth) {
|
||||||
|
return git.log ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
ref: branch,
|
||||||
|
depth,
|
||||||
|
singleBranch: true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
add: function (dir, file) {
|
||||||
|
return git.add ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
filepath: file
|
||||||
|
});
|
||||||
|
},
|
||||||
|
remove: function (dir, file) {
|
||||||
|
return git.remove ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
filepath: file
|
||||||
|
});
|
||||||
|
},
|
||||||
|
commit: function (dir, message, name, email, parent) {
|
||||||
|
if (parent) {
|
||||||
|
return git.commit ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
message,
|
||||||
|
author: {name: name,
|
||||||
|
email: email},
|
||||||
|
parent: parent
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return git.commit ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
message,
|
||||||
|
author: {name: name,
|
||||||
|
email: email}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
readCommit: function (dir, oid) {
|
||||||
|
return git.readCommit ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
oid
|
||||||
|
});
|
||||||
|
},
|
||||||
|
readBlob: function (dir, oid, path) {
|
||||||
|
return git.readBlob ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
oid,
|
||||||
|
path
|
||||||
|
});
|
||||||
|
},
|
||||||
|
writeRef: function (dir, branch, oid) {
|
||||||
|
return git.writeRef ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
ref: "refs/heads/" + branch,
|
||||||
|
value: oid,
|
||||||
|
force: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resolveRef: function (dir, ref) {
|
||||||
|
return git.resolveRef ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
ref
|
||||||
|
});
|
||||||
|
},
|
||||||
|
listFiles: function (dir, branch) {
|
||||||
|
return git.listFiles ({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
ref: branch
|
||||||
|
});
|
||||||
|
},
|
||||||
|
rimraf: async function (path) {
|
||||||
|
// try {
|
||||||
|
// // First assume path is itself a file
|
||||||
|
// await pfs.unlink(path)
|
||||||
|
// // if that worked we're done
|
||||||
|
// return
|
||||||
|
// } catch (err) {
|
||||||
|
// // Otherwise, path must be a directory
|
||||||
|
// if (err.code !== 'EISDIR') throw err
|
||||||
|
// }
|
||||||
|
// Knowing path is a directory,
|
||||||
|
// first, assume everything inside path is a file.
|
||||||
|
let files = await pfs.readdir(path);
|
||||||
|
for (let file of files) {
|
||||||
|
let child = path + '/' + file
|
||||||
|
try {
|
||||||
|
await pfs.unlink(child)
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code !== 'EISDIR') throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Assume what's left are directories and recurse.
|
||||||
|
let dirs = await pfs.readdir(path)
|
||||||
|
for (let dir of dirs) {
|
||||||
|
let child = path + '/' + dir
|
||||||
|
await rimraf(child, pfs)
|
||||||
|
}
|
||||||
|
// Finally, delete the empty directory
|
||||||
|
await pfs.rmdir(path)
|
||||||
|
},
|
||||||
|
getFileStateChanges: async function (commitHash1, commitHash2, dir) {
|
||||||
|
return git.walk({
|
||||||
|
fs,
|
||||||
|
dir,
|
||||||
|
trees: [git.TREE({ ref: commitHash1 }), git.TREE({ ref: commitHash2 })],
|
||||||
|
map: async function(filepath, [A, B]) {
|
||||||
|
var type = 'equal';
|
||||||
|
if (A === null) {
|
||||||
|
type = "add";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (B === null) {
|
||||||
|
type = "remove";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore directories
|
||||||
|
if (filepath === '.') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ((A !== null && (await A.type()) === 'tree')
|
||||||
|
||
|
||||||
|
(B !== null && (await B.type()) === 'tree')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate ids
|
||||||
|
const Aoid = A !== null && await A.oid();
|
||||||
|
const Boid = B !== null && await B.oid();
|
||||||
|
|
||||||
|
if (type === "equal") {
|
||||||
|
// determine modification type
|
||||||
|
if (Aoid !== Boid) {
|
||||||
|
type = 'modify'
|
||||||
|
}
|
||||||
|
if (Aoid === undefined) {
|
||||||
|
type = 'add'
|
||||||
|
}
|
||||||
|
if (Boid === undefined) {
|
||||||
|
type = 'remove'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Aoid === undefined && Boid === undefined) {
|
||||||
|
console.log('Something weird happened:')
|
||||||
|
console.log(A)
|
||||||
|
console.log(B)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: `/${filepath}`,
|
||||||
|
type: type,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
statusMatrix: async function (dir) {
|
||||||
|
await git.statusMatrix({ fs, dir });
|
||||||
|
},
|
||||||
|
getChangedFiles: async function (dir) {
|
||||||
|
try {
|
||||||
|
const FILE = 0, HEAD = 1, WORKDIR = 2;
|
||||||
|
|
||||||
|
let filenames = (await git.statusMatrix({ fs, dir }))
|
||||||
|
.filter(row => row[HEAD] !== row[WORKDIR])
|
||||||
|
.map(row => row[FILE]);
|
||||||
|
|
||||||
|
return filenames;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// self.addEventListener("message", ({ data }) => console.log(data));
|
||||||
|
}
|
||||||
@@ -26,6 +26,8 @@
|
|||||||
{:before-load frontend.core/stop
|
{:before-load frontend.core/stop
|
||||||
;; after live-reloading finishes call this function
|
;; after live-reloading finishes call this function
|
||||||
:after-load frontend.core/start
|
:after-load frontend.core/start
|
||||||
|
:http-root "public"
|
||||||
|
:http-port 3001
|
||||||
:preloads [devtools.preload]}}
|
:preloads [devtools.preload]}}
|
||||||
|
|
||||||
:test
|
:test
|
||||||
|
|||||||
@@ -1500,9 +1500,9 @@
|
|||||||
|
|
||||||
(rum/defcs custom-query < rum/reactive
|
(rum/defcs custom-query < rum/reactive
|
||||||
{:will-mount (fn [state]
|
{:will-mount (fn [state]
|
||||||
(let [[config query] (:rum/args state)]
|
(let [[config query] (:rum/args state)
|
||||||
(let [query-atom (db/custom-query query)]
|
query-atom (db/custom-query query)]
|
||||||
(assoc state :query-atom query-atom))))
|
(assoc state :query-atom query-atom)))
|
||||||
:did-mount (fn [state]
|
:did-mount (fn [state]
|
||||||
(when-let [query (last (:rum/args state))]
|
(when-let [query (last (:rum/args state))]
|
||||||
(state/add-custom-query-component! query (:rum/react-component state)))
|
(state/add-custom-query-component! query (:rum/react-component state)))
|
||||||
@@ -1550,11 +1550,13 @@
|
|||||||
:margin-left "0.25rem"}})
|
:margin-left "0.25rem"}})
|
||||||
|
|
||||||
(seq result) ;TODO: table
|
(seq result) ;TODO: table
|
||||||
[:pre
|
(let [result (->>
|
||||||
(for [record result]
|
(for [record result]
|
||||||
(if (map? record)
|
(if (map? record)
|
||||||
(str (util/pp-str record) "\n")
|
(str (util/pp-str record) "\n")
|
||||||
record))]
|
record))
|
||||||
|
(remove nil?))]
|
||||||
|
[:pre result])
|
||||||
|
|
||||||
:else
|
:else
|
||||||
[:div.text-sm.mt-2.ml-2.font-medium.opacity-50 "Empty"])
|
[:div.text-sm.mt-2.ml-2.font-medium.opacity-50 "Empty"])
|
||||||
|
|||||||
@@ -698,6 +698,10 @@
|
|||||||
current-pos (:pos (util/get-caret-pos (gdom/getElement id)))]
|
current-pos (:pos (util/get-caret-pos (gdom/getElement id)))]
|
||||||
(state/set-edit-content! id value)
|
(state/set-edit-content! id value)
|
||||||
(state/set-edit-pos! current-pos)
|
(state/set-edit-pos! current-pos)
|
||||||
|
(when-let [repo (or (:block/repo block)
|
||||||
|
(state/get-current-repo))]
|
||||||
|
(state/set-editor-last-input-time! repo (util/time-ms))
|
||||||
|
(db/clear-repo-persistent-job! repo))
|
||||||
(let [input (gdom/getElement id)
|
(let [input (gdom/getElement id)
|
||||||
native-e (gobj/get e "nativeEvent")
|
native-e (gobj/get e "nativeEvent")
|
||||||
last-input-char (util/nth-safe value (dec current-pos))]
|
last-input-char (util/nth-safe value (dec current-pos))]
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
[frontend.components.svg :as svg]
|
[frontend.components.svg :as svg]
|
||||||
[frontend.components.repo :as repo]
|
[frontend.components.repo :as repo]
|
||||||
[frontend.components.page :as page]
|
[frontend.components.page :as page]
|
||||||
[frontend.components.search :as search]))
|
[frontend.components.search :as search]
|
||||||
|
[frontend.handler.web.nfs :as nfs]))
|
||||||
|
|
||||||
(rum/defc logo < rum/reactive
|
(rum/defc logo < rum/reactive
|
||||||
[{:keys [white?]}]
|
[{:keys [white?]}]
|
||||||
@@ -57,13 +58,8 @@
|
|||||||
{:title (t :graph)
|
{:title (t :graph)
|
||||||
:options {:href (rfe/href :graph)}
|
:options {:href (rfe/href :graph)}
|
||||||
:icon svg/graph-sm})
|
:icon svg/graph-sm})
|
||||||
(when (and logged? current-repo)
|
(when (or logged? (and (nfs/supported?) current-repo))
|
||||||
{:title (t :publishing)
|
{:title (t :all-graphs)
|
||||||
:options {:on-click (fn []
|
|
||||||
(export/export-repo-as-html! current-repo))}
|
|
||||||
:icon nil})
|
|
||||||
(when logged?
|
|
||||||
{:title (t :all-repos)
|
|
||||||
:options {:href (rfe/href :repos)}
|
:options {:href (rfe/href :repos)}
|
||||||
:icon svg/repos-sm})
|
:icon svg/repos-sm})
|
||||||
(when current-repo
|
(when current-repo
|
||||||
@@ -78,12 +74,20 @@
|
|||||||
{:title (t :all-journals)
|
{:title (t :all-journals)
|
||||||
:options {:href (rfe/href :all-journals)}
|
:options {:href (rfe/href :all-journals)}
|
||||||
:icon svg/calendar-sm})
|
:icon svg/calendar-sm})
|
||||||
{:title (t :excalidraw-title)
|
|
||||||
:options {:href (rfe/href :draw)}
|
|
||||||
:icon (svg/excalidraw-logo)}
|
|
||||||
{:title (t :settings)
|
{:title (t :settings)
|
||||||
:options {:href (rfe/href :settings)}
|
:options {:href (rfe/href :settings)}
|
||||||
:icon svg/settings-sm}
|
:icon svg/settings-sm}
|
||||||
|
(when-let [project (and current-repo (state/get-current-project))]
|
||||||
|
(let [link (str config/website "/" project)]
|
||||||
|
{:title (str (t :go-to) "/" project)
|
||||||
|
:options {:href link
|
||||||
|
:target "_blank"}
|
||||||
|
:icon svg/external-link}))
|
||||||
|
(when (and logged? current-repo)
|
||||||
|
{:title (t :export)
|
||||||
|
:options {:on-click (fn []
|
||||||
|
(export/export-repo-as-html! current-repo))}
|
||||||
|
:icon nil})
|
||||||
(when current-repo
|
(when current-repo
|
||||||
{:title (t :import)
|
{:title (t :import)
|
||||||
:options {:href (rfe/href :import)}
|
:options {:href (rfe/href :import)}
|
||||||
@@ -109,56 +113,59 @@
|
|||||||
|
|
||||||
(rum/defc header
|
(rum/defc header
|
||||||
[{:keys [open-fn current-repo white? logged? page? route-match me default-home new-block-mode]}]
|
[{:keys [open-fn current-repo white? logged? page? route-match me default-home new-block-mode]}]
|
||||||
(rum/with-context [[t] i18n/*tongue-context*]
|
(let [local-repo? (= current-repo config/local-repo)
|
||||||
[:div.cp__header#head
|
repos (->> (state/sub [:me :repos])
|
||||||
(left-menu-button {:on-click (fn []
|
(remove #(= (:url %) config/local-repo)))]
|
||||||
(open-fn)
|
(rum/with-context [[t] i18n/*tongue-context*]
|
||||||
(state/set-left-sidebar-open! true))})
|
[:div.cp__header#head
|
||||||
|
(left-menu-button {:on-click (fn []
|
||||||
|
(open-fn)
|
||||||
|
(state/set-left-sidebar-open! true))})
|
||||||
|
|
||||||
(logo {:white? white?})
|
(logo {:white? white?})
|
||||||
|
|
||||||
(if current-repo
|
(if current-repo
|
||||||
(search/search)
|
(search/search)
|
||||||
[:div.flex-1])
|
[:div.flex-1])
|
||||||
|
|
||||||
(new-block-mode)
|
(new-block-mode)
|
||||||
|
|
||||||
(when (and (not logged?)
|
(when (and (not logged?)
|
||||||
(not config/publishing?))
|
(not config/publishing?))
|
||||||
[:a.text-sm.font-medium.login.opacity-70.hover:opacity-100
|
[:a.text-sm.font-medium.login.opacity-70.hover:opacity-100
|
||||||
{:href "/login/github"
|
{:href "/login/github"
|
||||||
:on-click (fn []
|
:on-click (fn []
|
||||||
(storage/remove :git/current-repo))}
|
(storage/remove :git/current-repo))}
|
||||||
(t :login-github)])
|
(t :login-github)])
|
||||||
|
|
||||||
(repo/sync-status)
|
(repo/sync-status)
|
||||||
|
|
||||||
[:div.repos.hidden.md:block
|
[:div.repos.hidden.md:block
|
||||||
(repo/repos-dropdown true)]
|
(repo/repos-dropdown true)]
|
||||||
|
|
||||||
(when-let [project (and current-repo (state/get-current-project))]
|
(when (and (nfs/supported?) (empty? repos))
|
||||||
[:a.opacity-70.hover:opacity-100.ml-4
|
(ui/tooltip
|
||||||
{:title (str (t :go-to) "/" project)
|
"Warning: this is an experimental feature, please only use it for testing purpose."
|
||||||
:href (str config/website "/" project)
|
[:a.text-sm.font-medium.opacity-70.hover:opacity-100.ml-3.block
|
||||||
:target "_blank"}
|
{:on-click (fn []
|
||||||
svg/external-link])
|
(nfs/ls-dir-files))}
|
||||||
|
[:div.flex.flex-row.text-center
|
||||||
|
[:span.inline-block svg/folder-add]
|
||||||
|
(when-not config/mobile?
|
||||||
|
[:span.ml-1 {:style {:margin-top 2}}
|
||||||
|
(t :open)])]]
|
||||||
|
{:label-style {:width 200}}))
|
||||||
|
|
||||||
(when (and page? current-repo (not config/mobile?))
|
(if config/publishing?
|
||||||
(let [page (get-in route-match [:path-params :name])
|
[:a.text-sm.font-medium.ml-3 {:href (rfe/href :graph)}
|
||||||
page (string/lower-case (util/url-decode page))
|
(t :graph)]
|
||||||
page (db/entity [:page/name page])]
|
|
||||||
(page/presentation current-repo page (:journal? page))))
|
|
||||||
|
|
||||||
(if config/publishing?
|
(dropdown-menu {:me me
|
||||||
[:a.text-sm.font-medium.ml-3 {:href (rfe/href :graph)}
|
:t t
|
||||||
(t :graph)]
|
:current-repo current-repo
|
||||||
|
:default-home default-home}))
|
||||||
|
|
||||||
(dropdown-menu {:me me
|
[:a#download-as-html.hidden]
|
||||||
:t t
|
[:a#download-as-zip.hidden]
|
||||||
:current-repo current-repo
|
|
||||||
:default-home default-home}))
|
|
||||||
|
|
||||||
[:a#download-as-html.hidden]
|
(right-menu-button)])))
|
||||||
[:a#download-as-zip.hidden]
|
|
||||||
|
|
||||||
(right-menu-button)]))
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
[frontend.db :as db]
|
[frontend.db :as db]
|
||||||
[frontend.state :as state]
|
[frontend.state :as state]
|
||||||
[frontend.ui :as ui]
|
[frontend.ui :as ui]
|
||||||
|
[frontend.config :as config]
|
||||||
[frontend.components.content :as content]
|
[frontend.components.content :as content]
|
||||||
[frontend.components.block :as block]
|
[frontend.components.block :as block]
|
||||||
[frontend.components.editor :as editor]
|
[frontend.components.editor :as editor]
|
||||||
@@ -68,6 +69,7 @@
|
|||||||
today? (= (string/lower-case title)
|
today? (= (string/lower-case title)
|
||||||
(string/lower-case (date/journal-name)))
|
(string/lower-case (date/journal-name)))
|
||||||
intro? (and (not (state/logged?))
|
intro? (and (not (state/logged?))
|
||||||
|
(not (config/local-db? repo))
|
||||||
(not config/publishing?)
|
(not config/publishing?)
|
||||||
today?)]
|
today?)]
|
||||||
[:div.flex-1.journal.page {:class (if intro? "intro" "")}
|
[:div.flex-1.journal.page {:class (if intro? "intro" "")}
|
||||||
|
|||||||
@@ -87,16 +87,15 @@
|
|||||||
(page-blocks-cp repo contents file-path name original-name name true false false nil format))))
|
(page-blocks-cp repo contents file-path name original-name name true false false nil format))))
|
||||||
|
|
||||||
(defn presentation
|
(defn presentation
|
||||||
[repo page journal?]
|
[repo page]
|
||||||
[:a.opacity-50.hover:opacity-100.ml-4
|
[:a.opacity-50.hover:opacity-100
|
||||||
{:title "Presentation mode (Powered by Reveal.js)"
|
{:title "Presentation mode (Powered by Reveal.js)"
|
||||||
:on-click (fn []
|
:on-click (fn []
|
||||||
(state/sidebar-add-block!
|
(state/sidebar-add-block!
|
||||||
repo
|
repo
|
||||||
(:db/id page)
|
(:db/id page)
|
||||||
:page-presentation
|
:page-presentation
|
||||||
{:page page
|
{:page page}))}
|
||||||
:journal? journal?}))}
|
|
||||||
svg/slideshow])
|
svg/slideshow])
|
||||||
|
|
||||||
(rum/defc today-queries < rum/reactive
|
(rum/defc today-queries < rum/reactive
|
||||||
@@ -354,11 +353,16 @@
|
|||||||
(not block?)
|
(not block?)
|
||||||
(not (state/hide-file?))
|
(not (state/hide-file?))
|
||||||
(not config/publishing?))
|
(not config/publishing?))
|
||||||
[:div.text-sm.ml-1.mb-2.flex-1 {:key "page-file"}
|
[:div.text-sm.ml-1.mb-4.flex-1.inline-flex
|
||||||
[:span.opacity-50 (t :file/file)]
|
{:key "page-file"}
|
||||||
[:a.bg-base-2.p-1.ml-1 {:style {:border-radius 4}
|
[:span.opacity-50 {:style {:margin-top 2}} (t :file/file)]
|
||||||
:href (str "/file/" (util/url-encode file-path))}
|
[:a.bg-base-2.px-1.ml-1.mr-3 {:style {:border-radius 4}
|
||||||
file-path]])]
|
:href (str "/file/" (util/url-encode file-path))}
|
||||||
|
file-path]
|
||||||
|
|
||||||
|
(when (and (not config/mobile?)
|
||||||
|
(not journal?))
|
||||||
|
(presentation repo page))])]
|
||||||
|
|
||||||
(when (and repo (not block?))
|
(when (and repo (not block?))
|
||||||
(let [alias (db/get-page-alias-names repo page-name)]
|
(let [alias (db/get-page-alias-names repo page-name)]
|
||||||
|
|||||||
@@ -69,11 +69,11 @@
|
|||||||
|
|
||||||
(rum/defcs unlinked-references-aux
|
(rum/defcs unlinked-references-aux
|
||||||
< rum/reactive db-mixins/query
|
< rum/reactive db-mixins/query
|
||||||
{:will-mount (fn [state]
|
{:will-mount (fn [state]
|
||||||
(let [[page-name n-ref] (:rum/args state)
|
(let [[page-name n-ref] (:rum/args state)
|
||||||
ref-blocks (db/get-page-unlinked-references page-name)]
|
ref-blocks (db/get-page-unlinked-references page-name)]
|
||||||
(reset! n-ref (count ref-blocks))
|
(reset! n-ref (count ref-blocks))
|
||||||
(assoc state ::ref-blocks ref-blocks)))}
|
(assoc state ::ref-blocks ref-blocks)))}
|
||||||
[state page-name n-ref]
|
[state page-name n-ref]
|
||||||
(let [ref-blocks (::ref-blocks state)]
|
(let [ref-blocks (::ref-blocks state)]
|
||||||
[:div.references-blocks
|
[:div.references-blocks
|
||||||
@@ -100,5 +100,5 @@
|
|||||||
(if @n-ref
|
(if @n-ref
|
||||||
(str @n-ref " Unlinked References")
|
(str @n-ref " Unlinked References")
|
||||||
"Unlinked References")]
|
"Unlinked References")]
|
||||||
(fn [] (unlinked-references-aux page-name n-ref))
|
(fn [] (unlinked-references-aux page-name n-ref))
|
||||||
true)]]))))
|
true)]]))))
|
||||||
|
|||||||
@@ -8,11 +8,13 @@
|
|||||||
[frontend.handler.common :as common-handler]
|
[frontend.handler.common :as common-handler]
|
||||||
[frontend.handler.route :as route-handler]
|
[frontend.handler.route :as route-handler]
|
||||||
[frontend.handler.export :as export-handler]
|
[frontend.handler.export :as export-handler]
|
||||||
|
[frontend.handler.web.nfs :as nfs-handler]
|
||||||
[frontend.util :as util]
|
[frontend.util :as util]
|
||||||
[frontend.config :as config]
|
[frontend.config :as config]
|
||||||
[reitit.frontend.easy :as rfe]
|
[reitit.frontend.easy :as rfe]
|
||||||
[frontend.version :as version]
|
[frontend.version :as version]
|
||||||
[frontend.components.commit :as commit]
|
[frontend.components.commit :as commit]
|
||||||
|
[frontend.components.svg :as svg]
|
||||||
[frontend.context.i18n :as i18n]
|
[frontend.context.i18n :as i18n]
|
||||||
[clojure.string :as string]))
|
[clojure.string :as string]))
|
||||||
|
|
||||||
@@ -22,159 +24,199 @@
|
|||||||
|
|
||||||
(rum/defc repos < rum/reactive
|
(rum/defc repos < rum/reactive
|
||||||
[]
|
[]
|
||||||
(let [{:keys [repos]} (state/sub :me)
|
(let [repos (->> (state/sub [:me :repos])
|
||||||
|
(remove #(= (:url %) config/local-repo)))
|
||||||
repos (util/distinct-by :url repos)]
|
repos (util/distinct-by :url repos)]
|
||||||
(if (seq repos)
|
(rum/with-context [[t] i18n/*tongue-context*]
|
||||||
[:div#repos
|
(if (seq repos)
|
||||||
[:h1.title "All Repos"]
|
[:div#repos
|
||||||
|
[:h1.title "All Graphs"]
|
||||||
|
|
||||||
[:div.pl-1.content
|
[:div.pl-1.content
|
||||||
[:div.flex.my-4 {:key "add-button"}
|
[:div.flex.flex-row.my-4
|
||||||
(ui/button
|
(when (state/logged?)
|
||||||
"Add another repo"
|
[:div.mr-8
|
||||||
:href (rfe/href :repo-add))]
|
(ui/button
|
||||||
|
"Add another git repo"
|
||||||
|
:href (rfe/href :repo-add))])
|
||||||
|
(when (nfs-handler/supported?)
|
||||||
|
[:div.flex.flex-col
|
||||||
|
[:div (ui/button
|
||||||
|
(t :open-a-directory)
|
||||||
|
:on-click nfs-handler/ls-dir-files)]
|
||||||
|
[:span.warning.mt-2.text-sm "Warning: this is an experimental feature,"
|
||||||
|
[:br]
|
||||||
|
"please only use it for testing purpose."]])]
|
||||||
|
(for [{:keys [id url] :as repo} repos]
|
||||||
|
(let [local? (config/local-db? url)]
|
||||||
|
[:div.flex.justify-between.mb-1 {:key id}
|
||||||
|
(if local?
|
||||||
|
[:a
|
||||||
|
(config/get-local-dir url)]
|
||||||
|
[:a {:target "_blank"
|
||||||
|
:href url}
|
||||||
|
(db/get-repo-path url)])
|
||||||
|
[:div.controls
|
||||||
|
[:a.control {:title (if local?
|
||||||
|
"Sync with the local directory"
|
||||||
|
"Clone again and re-index the db")
|
||||||
|
:on-click (fn []
|
||||||
|
(if local?
|
||||||
|
(nfs-handler/refresh! url)
|
||||||
|
(repo-handler/rebuild-index! url))
|
||||||
|
(js/setTimeout
|
||||||
|
(fn []
|
||||||
|
(route-handler/redirect! {:to :home}))
|
||||||
|
500))}
|
||||||
|
"Re-index"]
|
||||||
|
[:a.control.ml-4 {:title "Clone again and re-index the db"
|
||||||
|
:on-click (fn []
|
||||||
|
(export-handler/export-repo-as-json! (:url repo)))}
|
||||||
|
"Export as JSON"]
|
||||||
|
[:a.text-gray-400.ml-4 {:on-click (fn []
|
||||||
|
(repo-handler/remove-repo! repo))}
|
||||||
|
"Unlink"]]]))]
|
||||||
|
|
||||||
(for [{:keys [id url] :as repo} repos]
|
[:a#download-as-json.hidden]]
|
||||||
[:div.flex.justify-between.mb-1 {:key id}
|
(widgets/add-repo)))))
|
||||||
[:a {:target "_blank"
|
|
||||||
:href url}
|
|
||||||
(db/get-repo-path url)]
|
|
||||||
[:div.controls
|
|
||||||
[:a.control {:title "Clone again and re-index the db"
|
|
||||||
:on-click (fn []
|
|
||||||
(repo-handler/rebuild-index! repo)
|
|
||||||
(js/setTimeout
|
|
||||||
(fn []
|
|
||||||
(route-handler/redirect! {:to :home}))
|
|
||||||
500))}
|
|
||||||
"Re-index"]
|
|
||||||
[:a.control.ml-4 {:title "Clone again and re-index the db"
|
|
||||||
:on-click (fn []
|
|
||||||
(export-handler/export-repo-as-json! (:url repo)))}
|
|
||||||
"Export as JSON"]
|
|
||||||
[:a.text-gray-400.ml-4 {:on-click (fn []
|
|
||||||
(repo-handler/remove-repo! repo))}
|
|
||||||
"Unlink"]]])]
|
|
||||||
|
|
||||||
[:a#download-as-json.hidden]]
|
|
||||||
(widgets/add-repo))))
|
|
||||||
|
|
||||||
(rum/defc sync-status < rum/reactive
|
(rum/defc sync-status < rum/reactive
|
||||||
{:did-mount (fn [state]
|
{:did-mount (fn [state]
|
||||||
(js/setTimeout common-handler/check-changed-files-status 1000)
|
(js/setTimeout common-handler/check-changed-files-status 1000)
|
||||||
state)}
|
state)}
|
||||||
[]
|
[]
|
||||||
(let [repo (state/get-current-repo)]
|
(when-let [repo (state/get-current-repo)]
|
||||||
(when-not (= repo config/local-repo)
|
(let [nfs-repo? (config/local-db? repo)]
|
||||||
(let [changed-files (state/sub [:repo/changed-files repo])
|
(when-not (= repo config/local-repo)
|
||||||
should-push? (seq changed-files)
|
(if (and nfs-repo? (nfs-handler/supported?))
|
||||||
git-status (state/sub [:git/status repo])
|
(let [syncing? (state/sub :graph/syncing?)]
|
||||||
pushing? (= :pushing git-status)
|
[:div.ml-2.mr-1.opacity-70.hover:opacity-100 {:class (if syncing? "loader" "initial")}
|
||||||
pulling? (= :pulling git-status)
|
[:a
|
||||||
push-failed? (= :push-failed git-status)
|
{:on-click #(nfs-handler/refresh! repo)
|
||||||
last-pulled-at (db/sub-key-value repo :git/last-pulled-at)
|
:title (str "Sync files with the local directory: " (config/get-local-dir repo))}
|
||||||
editing? (seq (state/sub :editor/editing?))]
|
svg/refresh]])
|
||||||
[:div.flex-row.flex.items-center
|
(let [changed-files (state/sub [:repo/changed-files repo])
|
||||||
(when pushing?
|
should-push? (seq changed-files)
|
||||||
[:span.lds-dual-ring.mt-1])
|
git-status (state/sub [:git/status repo])
|
||||||
(ui/dropdown
|
pushing? (= :pushing git-status)
|
||||||
(fn [{:keys [toggle-fn]}]
|
pulling? (= :pulling git-status)
|
||||||
[:div.cursor.w-2.h-2.sync-status.mr-2
|
push-failed? (= :push-failed git-status)
|
||||||
{:class (cond
|
last-pulled-at (db/sub-key-value repo :git/last-pulled-at)
|
||||||
|
;; db-persisted? (state/sub [:db/persisted? repo])
|
||||||
|
editing? (seq (state/sub :editor/editing?))]
|
||||||
|
[:div.flex-row.flex.items-center
|
||||||
|
(when pushing?
|
||||||
|
[:span.lds-dual-ring.mt-1])
|
||||||
|
(ui/dropdown
|
||||||
|
(fn [{:keys [toggle-fn]}]
|
||||||
|
[:div.cursor.w-2.h-2.sync-status.mr-2
|
||||||
|
{:class (cond
|
||||||
|
push-failed?
|
||||||
|
"bg-red-500"
|
||||||
|
(or
|
||||||
|
;; (not db-persisted?)
|
||||||
|
editing?
|
||||||
|
should-push? pushing?)
|
||||||
|
"bg-orange-400"
|
||||||
|
:else
|
||||||
|
"bg-green-600")
|
||||||
|
:style {:border-radius "50%"
|
||||||
|
:margin-top 2}
|
||||||
|
:on-mouse-over
|
||||||
|
(fn [e]
|
||||||
|
(toggle-fn)
|
||||||
|
(js/setTimeout common-handler/check-changed-files-status 0))}])
|
||||||
|
(fn [{:keys [toggle-fn]}]
|
||||||
|
(rum/with-context [[t] i18n/*tongue-context*]
|
||||||
|
[:div.p-2.rounded-md.shadow-xs.bg-base-3.flex.flex-col.sync-content
|
||||||
|
{:on-mouse-leave toggle-fn}
|
||||||
|
[:div
|
||||||
|
[:div
|
||||||
|
(cond
|
||||||
push-failed?
|
push-failed?
|
||||||
"bg-red-500"
|
[:p (t :git/push-failed)]
|
||||||
(or editing? should-push? pushing?)
|
(and should-push? (seq changed-files))
|
||||||
"bg-orange-400"
|
[:div.changes
|
||||||
|
[:ul.overflow-y-scroll {:style {:max-height 250}}
|
||||||
|
(for [file changed-files]
|
||||||
|
[:li {:key (str "sync-" file)}
|
||||||
|
[:div.flex.flex-row.justify-between.align-items
|
||||||
|
[:a {:href (rfe/href :file {:path file})}
|
||||||
|
file]
|
||||||
|
[:a.ml-4.text-sm.mt-1
|
||||||
|
{:on-click (fn [e]
|
||||||
|
(export-handler/download-file! file))}
|
||||||
|
[:span (t :download)]]]])]]
|
||||||
:else
|
:else
|
||||||
"bg-green-600")
|
[:p (t :git/local-changes-synced)])]
|
||||||
:style {:border-radius "50%"
|
;; [:a.text-sm.font-bold {:href "/diff"} "Check diff"]
|
||||||
:margin-top 2}
|
[:div.flex.flex-row.justify-between.align-items.mt-2
|
||||||
:on-mouse-over
|
(ui/button (t :git/push)
|
||||||
(fn [e]
|
:on-click (fn [] (state/set-modal! commit/add-commit-message)))
|
||||||
(toggle-fn)
|
(if pushing?
|
||||||
(js/setTimeout common-handler/check-changed-files-status 0))}])
|
[:span.lds-dual-ring.mt-1])]]
|
||||||
(fn [{:keys [toggle-fn]}]
|
[:hr]
|
||||||
(rum/with-context [[t] i18n/*tongue-context*]
|
[:div
|
||||||
[:div.p-2.rounded-md.shadow-xs.bg-base-3.flex.flex-col.sync-content
|
(when-not (string/blank? last-pulled-at)
|
||||||
{:on-mouse-leave toggle-fn}
|
[:p {:style {:font-size 12}} (t :git/last-pull)
|
||||||
[:div
|
(str ": " last-pulled-at)])
|
||||||
[:div
|
[:div.flex.flex-row.justify-between.align-items
|
||||||
(cond
|
(ui/button (t :git/pull)
|
||||||
push-failed?
|
:on-click (fn [] (repo-handler/pull-current-repo)))
|
||||||
[:p (t :git/push-failed)]
|
(if pulling?
|
||||||
(and should-push? (seq changed-files))
|
[:span.lds-dual-ring.mt-1])]
|
||||||
[:div.changes
|
[:a.mt-5.text-sm.opacity-50.block
|
||||||
[:ul
|
{:on-click (fn []
|
||||||
(for [file changed-files]
|
(export-handler/export-repo-as-zip! repo))}
|
||||||
[:li {:key (str "sync-" file)}
|
(t :repo/download-zip)]
|
||||||
[:div.flex.flex-row.justify-between.align-items
|
[:p.pt-2.text-sm.opacity-50
|
||||||
[:a {:href (rfe/href :file {:path file})}
|
(t :git/version) (str " " version/version)]]])))]))))))
|
||||||
file]
|
|
||||||
[:a.ml-4.text-sm.mt-1
|
|
||||||
{:on-click (fn [e]
|
|
||||||
(export-handler/download-file! file))}
|
|
||||||
[:span (t :download)]]]])]]
|
|
||||||
:else
|
|
||||||
[:p (t :git/local-changes-synced)])]
|
|
||||||
;; [:a.text-sm.font-bold {:href "/diff"} "Check diff"]
|
|
||||||
[:div.flex.flex-row.justify-between.align-items.mt-2
|
|
||||||
(ui/button (t :git/push)
|
|
||||||
:on-click (fn [] (state/set-modal! commit/add-commit-message)))
|
|
||||||
(if pushing?
|
|
||||||
[:span.lds-dual-ring.mt-1])]]
|
|
||||||
[:hr]
|
|
||||||
[:div
|
|
||||||
(when-not (string/blank? last-pulled-at)
|
|
||||||
[:p {:style {:font-size 12}} (t :git/last-pull)
|
|
||||||
(str ": " last-pulled-at)])
|
|
||||||
[:div.flex.flex-row.justify-between.align-items
|
|
||||||
(ui/button (t :git/pull)
|
|
||||||
:on-click (fn [] (repo-handler/pull-current-repo)))
|
|
||||||
(if pulling?
|
|
||||||
[:span.lds-dual-ring.mt-1])]
|
|
||||||
[:a.mt-5.text-sm.opacity-50.block
|
|
||||||
{:on-click (fn []
|
|
||||||
(export-handler/export-repo-as-zip! repo))}
|
|
||||||
(t :repo/download-zip)]
|
|
||||||
[:p.pt-2.text-sm.opacity-50
|
|
||||||
(t :git/version) (str " " version/version)]]])))]))))
|
|
||||||
|
|
||||||
(rum/defc repos-dropdown < rum/reactive
|
(rum/defc repos-dropdown < rum/reactive
|
||||||
[head? on-click]
|
[head? on-click]
|
||||||
(let [current-repo (state/sub :git/current-repo)
|
(when-let [current-repo (state/sub :git/current-repo)]
|
||||||
logged? (state/logged?)
|
(let [logged? (state/logged?)
|
||||||
local-repo? (= current-repo config/local-repo)
|
local-repo? (= current-repo config/local-repo)
|
||||||
get-repo-name (fn [repo]
|
get-repo-name (fn [repo]
|
||||||
(if head?
|
(if (config/local-db? repo)
|
||||||
(db/get-repo-path repo)
|
(config/get-local-dir repo)
|
||||||
(util/take-at-most (db/get-repo-name repo) 20)))]
|
(if head?
|
||||||
(when logged?
|
(db/get-repo-path repo)
|
||||||
(if current-repo
|
(util/take-at-most (db/get-repo-name repo) 20))))]
|
||||||
(let [repos (state/sub [:me :repos])]
|
(let [repos (->> (state/sub [:me :repos])
|
||||||
(if (> (count repos) 1)
|
(remove (fn [r] (= config/local-repo (:url r)))))]
|
||||||
(ui/dropdown-with-links
|
(cond
|
||||||
(fn [{:keys [toggle-fn]}]
|
(> (count repos) 1)
|
||||||
[:a#repo-switch {:on-click toggle-fn}
|
(ui/dropdown-with-links
|
||||||
[:span (get-repo-name current-repo)]
|
(fn [{:keys [toggle-fn]}]
|
||||||
[:span.dropdown-caret.ml-1 {:style {:border-top-color "#6b7280"}}]])
|
[:a#repo-switch {:on-click toggle-fn}
|
||||||
(mapv
|
|
||||||
(fn [{:keys [id url]}]
|
|
||||||
{:title (get-repo-name url)
|
|
||||||
:options {:on-click (fn []
|
|
||||||
(repo-handler/push-if-auto-enabled! (state/get-current-repo))
|
|
||||||
(state/set-current-repo! url)
|
|
||||||
(when-not (= :draw (state/get-current-route))
|
|
||||||
(route-handler/redirect-to-home!))
|
|
||||||
(when on-click
|
|
||||||
(on-click url)))}})
|
|
||||||
(remove (fn [repo]
|
|
||||||
(= current-repo (:url repo)))
|
|
||||||
repos))
|
|
||||||
{:modal-class (util/hiccup->class
|
|
||||||
"origin-top-right.absolute.left-0.mt-2.w-48.rounded-md.shadow-lg ")})
|
|
||||||
(if local-repo?
|
|
||||||
[:span (get-repo-name current-repo)]
|
[:span (get-repo-name current-repo)]
|
||||||
|
[:span.dropdown-caret.ml-1 {:style {:border-top-color "#6b7280"}}]])
|
||||||
|
(mapv
|
||||||
|
(fn [{:keys [id url]}]
|
||||||
|
{:title (get-repo-name url)
|
||||||
|
:options {:on-click (fn []
|
||||||
|
(repo-handler/push-if-auto-enabled! (state/get-current-repo))
|
||||||
|
|
||||||
|
(state/set-current-repo! url)
|
||||||
|
(when-not (= :draw (state/get-current-route))
|
||||||
|
(route-handler/redirect-to-home!))
|
||||||
|
(when on-click
|
||||||
|
(on-click url)))}})
|
||||||
|
(remove (fn [repo]
|
||||||
|
(= current-repo (:url repo)))
|
||||||
|
repos))
|
||||||
|
{:modal-class (util/hiccup->class
|
||||||
|
"origin-top-right.absolute.left-0.mt-2.w-48.rounded-md.shadow-lg ")})
|
||||||
|
|
||||||
|
(and current-repo (not local-repo?))
|
||||||
|
(let [repo-name (get-repo-name current-repo)]
|
||||||
|
(if (config/local-db? current-repo)
|
||||||
|
repo-name
|
||||||
[:a
|
[:a
|
||||||
{:href current-repo
|
{:href current-repo
|
||||||
:target "_blank"}
|
:target "_blank"}
|
||||||
(get-repo-name current-repo)])))))))
|
repo-name]))
|
||||||
|
|
||||||
|
:else
|
||||||
|
nil)))))
|
||||||
|
|||||||
@@ -118,8 +118,6 @@
|
|||||||
[:div.cp__sidebar-main-content
|
[:div.cp__sidebar-main-content
|
||||||
{:data-is-global-graph-pages global-graph-pages?
|
{:data-is-global-graph-pages global-graph-pages?
|
||||||
:data-is-full-width (or global-graph-pages?
|
:data-is-full-width (or global-graph-pages?
|
||||||
(and (not logged?)
|
|
||||||
home?)
|
|
||||||
(contains? #{:all-files :all-pages} route-name))}
|
(contains? #{:all-files :all-pages} route-name))}
|
||||||
(cond
|
(cond
|
||||||
(not indexeddb-support?)
|
(not indexeddb-support?)
|
||||||
@@ -174,22 +172,22 @@
|
|||||||
current-repo (state/sub :git/current-repo)
|
current-repo (state/sub :git/current-repo)
|
||||||
latest-journals (db/get-latest-journals (state/get-current-repo) journals-length)
|
latest-journals (db/get-latest-journals (state/get-current-repo) journals-length)
|
||||||
preferred-format (state/sub [:me :preferred_format])
|
preferred-format (state/sub [:me :preferred_format])
|
||||||
logged? (:name me)
|
logged? (:name me)]
|
||||||
token (state/sub :encrypt/token)
|
|
||||||
;; TODO: remove this
|
|
||||||
daily-migrating? (state/sub [:daily/migrating?])]
|
|
||||||
(rum/with-context [[t] i18n/*tongue-context*]
|
(rum/with-context [[t] i18n/*tongue-context*]
|
||||||
[:div.max-w-7xl.mx-auto
|
[:div.max-w-7xl.mx-auto
|
||||||
(cond
|
(cond
|
||||||
daily-migrating?
|
|
||||||
(ui/loading "Migrating to daily notes")
|
|
||||||
|
|
||||||
(and default-home
|
(and default-home
|
||||||
(= :home (state/get-current-route))
|
(= :home (state/get-current-route))
|
||||||
(not (state/route-has-p?)))
|
(not (state/route-has-p?)))
|
||||||
(route-handler/redirect! {:to :page
|
(route-handler/redirect! {:to :page
|
||||||
:path-params {:name (:page default-home)}})
|
:path-params {:name (:page default-home)}})
|
||||||
|
|
||||||
|
importing-to-db?
|
||||||
|
(ui/loading (t :parsing-files))
|
||||||
|
|
||||||
|
loading-files?
|
||||||
|
(ui/loading (t :loading-files))
|
||||||
|
|
||||||
(and (not logged?) (seq latest-journals))
|
(and (not logged?) (seq latest-journals))
|
||||||
(journal/journals latest-journals)
|
(journal/journals latest-journals)
|
||||||
|
|
||||||
@@ -206,12 +204,6 @@
|
|||||||
(seq latest-journals)
|
(seq latest-journals)
|
||||||
(journal/journals latest-journals)
|
(journal/journals latest-journals)
|
||||||
|
|
||||||
importing-to-db?
|
|
||||||
(ui/loading (t :parsing-files))
|
|
||||||
|
|
||||||
loading-files?
|
|
||||||
(ui/loading (t :loading-files))
|
|
||||||
|
|
||||||
(and logged? (empty? (:repos me)))
|
(and logged? (empty? (:repos me)))
|
||||||
(widgets/add-repo)
|
(widgets/add-repo)
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,10 @@
|
|||||||
:stroke "currentColor"
|
:stroke "currentColor"
|
||||||
:d d}]]))
|
:d d}]]))
|
||||||
|
|
||||||
|
(def refresh
|
||||||
|
(hero-icon "M4 4V9H4.58152M19.9381 11C19.446 7.05369 16.0796 4 12 4C8.64262 4 5.76829 6.06817 4.58152 9M4.58152 9H9M20 20V15H19.4185M19.4185 15C18.2317 17.9318 15.3574 20 12 20C7.92038 20 4.55399 16.9463 4.06189 13M19.4185 15H15"
|
||||||
|
{:fill "none"}))
|
||||||
|
|
||||||
(def user
|
(def user
|
||||||
[:svg
|
[:svg
|
||||||
{:stroke-linejoin "round"
|
{:stroke-linejoin "round"
|
||||||
@@ -122,6 +126,17 @@
|
|||||||
:x1 "10.5"}]])
|
:x1 "10.5"}]])
|
||||||
|
|
||||||
(def graph-sm [:div {:style {:transform "rotate(90deg)"}} (hero-icon "M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" {:height "16" :width "16"})])
|
(def graph-sm [:div {:style {:transform "rotate(90deg)"}} (hero-icon "M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" {:height "16" :width "16"})])
|
||||||
|
|
||||||
|
(def folder-add
|
||||||
|
[:svg
|
||||||
|
{:stroke "currentColor", :view-box "0 0 24 24", :fill "none" :width 24 :height 24 :display "inline-block"}
|
||||||
|
[:path
|
||||||
|
{:d
|
||||||
|
"M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z",
|
||||||
|
:stroke-width "2",
|
||||||
|
:stroke-linejoin "round",
|
||||||
|
:stroke-linecap "round"}]])
|
||||||
|
|
||||||
(def folder (hero-icon "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"))
|
(def folder (hero-icon "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"))
|
||||||
(def folder-sm (hero-icon "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" {:height "16" :width "16"}))
|
(def folder-sm (hero-icon "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" {:height "16" :width "16"}))
|
||||||
(def pages-sm [:svg {:viewbox "0 0 20 20", :fill "currentColor", :height "16", :width "16"}
|
(def pages-sm [:svg {:viewbox "0 0 20 20", :fill "currentColor", :height "16", :width "16"}
|
||||||
@@ -327,10 +342,9 @@
|
|||||||
(def slideshow
|
(def slideshow
|
||||||
[:svg
|
[:svg
|
||||||
{:view-box "0 0 24 24"
|
{:view-box "0 0 24 24"
|
||||||
:height 23
|
:height 24
|
||||||
:width 23
|
:width 24
|
||||||
:fill "currentColor"
|
:fill "currentColor"}
|
||||||
:display "inline-block"}
|
|
||||||
[:path
|
[:path
|
||||||
{:d "M10 8v8l5-4-5-4zm9-5H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"}]])
|
{:d "M10 8v8l5-4-5-4zm9-5H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"}]])
|
||||||
|
|
||||||
@@ -395,3 +409,6 @@
|
|||||||
:stroke-width "2"
|
:stroke-width "2"
|
||||||
:stroke-linejoin "round"
|
:stroke-linejoin "round"
|
||||||
:stroke-linecap "round"}]])
|
:stroke-linecap "round"}]])
|
||||||
|
|
||||||
|
(def online
|
||||||
|
(hero-icon "M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0"))
|
||||||
|
|||||||
@@ -226,6 +226,15 @@
|
|||||||
"md"
|
"md"
|
||||||
(name format)))
|
(name format)))
|
||||||
|
|
||||||
|
(defn get-file-format
|
||||||
|
[extension]
|
||||||
|
(case (keyword extension)
|
||||||
|
:markdown
|
||||||
|
:markdown
|
||||||
|
:md
|
||||||
|
:markdown
|
||||||
|
(keyword extension)))
|
||||||
|
|
||||||
(defn default-empty-block
|
(defn default-empty-block
|
||||||
([format]
|
([format]
|
||||||
(default-empty-block format 2))
|
(default-empty-block format 2))
|
||||||
@@ -269,3 +278,15 @@
|
|||||||
(def markers
|
(def markers
|
||||||
#{"now" "later" "todo" "doing" "done" "wait" "waiting"
|
#{"now" "later" "todo" "doing" "done" "wait" "waiting"
|
||||||
"canceled" "cancelled" "started" "in-progress"})
|
"canceled" "cancelled" "started" "in-progress"})
|
||||||
|
|
||||||
|
(defonce idb-db-prefix "logseq-db/")
|
||||||
|
(defonce local-db-prefix "logseq_local_")
|
||||||
|
(defonce local-handle-prefix (str "handle/" local-db-prefix))
|
||||||
|
|
||||||
|
(defn local-db?
|
||||||
|
[s]
|
||||||
|
(string/starts-with? s local-db-prefix))
|
||||||
|
|
||||||
|
(defn get-local-dir
|
||||||
|
[s]
|
||||||
|
(string/replace s local-db-prefix ""))
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
[clojure.set :as set]
|
[clojure.set :as set]
|
||||||
[frontend.utf8 :as utf8]
|
[frontend.utf8 :as utf8]
|
||||||
[frontend.config :as config]
|
[frontend.config :as config]
|
||||||
["localforage" :as localforage]
|
|
||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
[cljs.reader :as reader]
|
[cljs.reader :as reader]
|
||||||
[cljs-time.core :as t]
|
[cljs-time.core :as t]
|
||||||
@@ -21,36 +20,13 @@
|
|||||||
[frontend.extensions.sci :as sci]
|
[frontend.extensions.sci :as sci]
|
||||||
[frontend.db-schema :as db-schema]
|
[frontend.db-schema :as db-schema]
|
||||||
[clojure.core.async :as async]
|
[clojure.core.async :as async]
|
||||||
[frontend.storage :as storage]
|
|
||||||
[lambdaisland.glogi :as log]
|
[lambdaisland.glogi :as log]
|
||||||
[goog.object :as gobj]))
|
[frontend.idb :as idb]))
|
||||||
|
|
||||||
;; offline db
|
|
||||||
(def store-name "dbs")
|
|
||||||
(.config localforage
|
|
||||||
#js
|
|
||||||
{:name "logseq-datascript"
|
|
||||||
:version 1.0
|
|
||||||
:storeName store-name})
|
|
||||||
|
|
||||||
(defonce localforage-instance (.createInstance localforage store-name))
|
|
||||||
|
|
||||||
;; Query atom of map of Key ([repo q inputs]) -> atom
|
;; Query atom of map of Key ([repo q inputs]) -> atom
|
||||||
;; TODO: replace with LRUCache, only keep the latest 20 or 50 items?
|
;; TODO: replace with LRUCache, only keep the latest 20 or 50 items?
|
||||||
(defonce query-state (atom {}))
|
(defonce query-state (atom {}))
|
||||||
|
|
||||||
(defn clear-idb!
|
|
||||||
[]
|
|
||||||
(p/let [_ (.clear localforage-instance)
|
|
||||||
dbs (js/window.indexedDB.databases)]
|
|
||||||
(doseq [db dbs]
|
|
||||||
(js/window.indexedDB.deleteDatabase (gobj/get db "name")))))
|
|
||||||
|
|
||||||
(defn clear-local-storage-and-idb!
|
|
||||||
[]
|
|
||||||
(storage/clear)
|
|
||||||
(clear-idb!))
|
|
||||||
|
|
||||||
(defn get-repo-path
|
(defn get-repo-path
|
||||||
[url]
|
[url]
|
||||||
(if (util/starts-with? url "http")
|
(if (util/starts-with? url "http")
|
||||||
@@ -61,7 +37,7 @@
|
|||||||
(defn datascript-db
|
(defn datascript-db
|
||||||
[repo]
|
[repo]
|
||||||
(when repo
|
(when repo
|
||||||
(str "logseq-db/" (get-repo-path repo))))
|
(str config/idb-db-prefix (get-repo-path repo))))
|
||||||
|
|
||||||
(defn datascript-files-db
|
(defn datascript-files-db
|
||||||
[repo]
|
[repo]
|
||||||
@@ -70,11 +46,11 @@
|
|||||||
|
|
||||||
(defn remove-db!
|
(defn remove-db!
|
||||||
[repo]
|
[repo]
|
||||||
(.removeItem localforage-instance (datascript-db repo)))
|
(idb/remove-item! (datascript-db repo)))
|
||||||
|
|
||||||
(defn remove-files-db!
|
(defn remove-files-db!
|
||||||
[repo]
|
[repo]
|
||||||
(.removeItem localforage-instance (datascript-files-db repo)))
|
(idb/remove-item! (datascript-files-db repo)))
|
||||||
|
|
||||||
(def react util/react)
|
(def react util/react)
|
||||||
|
|
||||||
@@ -110,6 +86,13 @@
|
|||||||
(swap! conns dissoc (datascript-db repo))
|
(swap! conns dissoc (datascript-db repo))
|
||||||
(swap! conns dissoc (datascript-files-db repo)))
|
(swap! conns dissoc (datascript-files-db repo)))
|
||||||
|
|
||||||
|
(defn get-tx-id [tx-report]
|
||||||
|
(get-in tx-report [:tempids :db/current-tx]))
|
||||||
|
|
||||||
|
(defn get-max-tx-id
|
||||||
|
[db]
|
||||||
|
(:max-tx db))
|
||||||
|
|
||||||
;; transit serialization
|
;; transit serialization
|
||||||
|
|
||||||
(defn db->string [db]
|
(defn db->string [db]
|
||||||
@@ -124,13 +107,16 @@
|
|||||||
(defn string->db [s]
|
(defn string->db [s]
|
||||||
(dt/read-transit-str s))
|
(dt/read-transit-str s))
|
||||||
|
|
||||||
;; persisting DB between page reloads
|
;; persisting DBs between page reloads
|
||||||
(defn persist [repo db files-db?]
|
(defn persist! [repo]
|
||||||
(.setItem localforage-instance
|
(let [file-key (datascript-files-db repo)
|
||||||
(if files-db?
|
non-file-key (datascript-db repo)
|
||||||
(datascript-files-db repo)
|
file-db (d/db (get-files-conn repo))
|
||||||
(datascript-db repo))
|
non-file-db (d/db (get-conn repo false))]
|
||||||
(db->string db)))
|
(p/let [_ (idb/set-item! file-key (db->string file-db))
|
||||||
|
_ (idb/set-item! non-file-key (db->string non-file-db))]
|
||||||
|
(state/set-last-persist-transact-id! repo true (get-max-tx-id file-db))
|
||||||
|
(state/set-last-persist-transact-id! repo false (get-max-tx-id non-file-db)))))
|
||||||
|
|
||||||
(defn reset-conn! [conn db]
|
(defn reset-conn! [conn db]
|
||||||
(reset! conn db))
|
(reset! conn db))
|
||||||
@@ -556,9 +542,6 @@
|
|||||||
(group-by-page result)))
|
(group-by-page result)))
|
||||||
result)))
|
result)))
|
||||||
|
|
||||||
(defn get-tx-id [tx-report]
|
|
||||||
(get-in tx-report [:tempids :db/current-tx]))
|
|
||||||
|
|
||||||
(defn transact!
|
(defn transact!
|
||||||
([tx-data]
|
([tx-data]
|
||||||
(transact! (state/get-current-repo) tx-data))
|
(transact! (state/get-current-repo) tx-data))
|
||||||
@@ -568,8 +551,19 @@
|
|||||||
(remove nil?))]
|
(remove nil?))]
|
||||||
(when (seq tx-data)
|
(when (seq tx-data)
|
||||||
(when-let [conn (get-conn repo-url false)]
|
(when-let [conn (get-conn repo-url false)]
|
||||||
(let [tx-report (d/transact! conn (vec tx-data))]
|
(d/transact! conn (vec tx-data))))))))
|
||||||
(state/mark-repo-as-changed! repo-url (get-tx-id tx-report)))))))))
|
|
||||||
|
(defn transact-files-db!
|
||||||
|
([tx-data]
|
||||||
|
(transact! (state/get-current-repo) tx-data))
|
||||||
|
([repo-url tx-data]
|
||||||
|
(when-not config/publishing?
|
||||||
|
(let [tx-data (->> (util/remove-nils tx-data)
|
||||||
|
(remove nil?)
|
||||||
|
(map #(dissoc % :file/handle :file/type)))]
|
||||||
|
(when (seq tx-data)
|
||||||
|
(when-let [conn (get-files-conn repo-url)]
|
||||||
|
(d/transact! conn (vec tx-data))))))))
|
||||||
|
|
||||||
(defn get-key-value
|
(defn get-key-value
|
||||||
([key]
|
([key]
|
||||||
@@ -594,42 +588,41 @@
|
|||||||
(when-not config/publishing?
|
(when-not config/publishing?
|
||||||
(try
|
(try
|
||||||
(let [repo-url (or repo-url (state/get-current-repo))
|
(let [repo-url (or repo-url (state/get-current-repo))
|
||||||
tx-data (->> (util/remove-nils tx-data)
|
tx-data (->> (util/remove-nils tx-data)
|
||||||
(remove nil?))
|
(remove nil?))
|
||||||
get-conn (fn [] (if files-db?
|
get-conn (fn [] (if files-db?
|
||||||
(get-files-conn repo-url)
|
(get-files-conn repo-url)
|
||||||
(get-conn repo-url false)))]
|
(get-conn repo-url false)))]
|
||||||
(when (and (seq tx-data) (get-conn))
|
(when (and (seq tx-data) (get-conn))
|
||||||
(let [tx-result (profile "Transact!" (d/transact! (get-conn) (vec tx-data)))
|
(let [tx-result (profile "Transact!" (d/transact! (get-conn) (vec tx-data)))
|
||||||
_ (state/mark-repo-as-changed! repo-url (get-tx-id tx-result))
|
db (:db-after tx-result)
|
||||||
db (:db-after tx-result)
|
handler-keys (get-handler-keys handler-opts)]
|
||||||
handler-keys (get-handler-keys handler-opts)]
|
(doseq [handler-key handler-keys]
|
||||||
(doseq [handler-key handler-keys]
|
(let [handler-key (vec (cons repo-url handler-key))]
|
||||||
(let [handler-key (vec (cons repo-url handler-key))]
|
(when-let [cache (get @query-state handler-key)]
|
||||||
(when-let [cache (get @query-state handler-key)]
|
(let [{:keys [query inputs transform-fn query-fn inputs-fn]} cache]
|
||||||
(let [{:keys [query inputs transform-fn query-fn inputs-fn]} cache]
|
(when (or query query-fn)
|
||||||
(when (or query query-fn)
|
(let [new-result (->
|
||||||
(let [new-result (->
|
(cond
|
||||||
(cond
|
query-fn
|
||||||
query-fn
|
(profile
|
||||||
(profile
|
"Query:"
|
||||||
"Query:"
|
(doall (query-fn db)))
|
||||||
(doall (query-fn db)))
|
|
||||||
|
|
||||||
inputs-fn
|
inputs-fn
|
||||||
(let [inputs (inputs-fn)]
|
(let [inputs (inputs-fn)]
|
||||||
(apply d/q query db inputs))
|
(apply d/q query db inputs))
|
||||||
|
|
||||||
(keyword? query)
|
(keyword? query)
|
||||||
(get-key-value repo-url query)
|
(get-key-value repo-url query)
|
||||||
|
|
||||||
(seq inputs)
|
(seq inputs)
|
||||||
(apply d/q query db inputs)
|
(apply d/q query db inputs)
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(d/q query db))
|
(d/q query db))
|
||||||
transform-fn)]
|
transform-fn)]
|
||||||
(set-new-result! handler-key new-result))))))))))
|
(set-new-result! handler-key new-result))))))))))
|
||||||
(catch js/Error e
|
(catch js/Error e
|
||||||
;; FIXME: check error type and notice user
|
;; FIXME: check error type and notice user
|
||||||
(log/error :db/transact! e)))))
|
(log/error :db/transact! e)))))
|
||||||
@@ -904,7 +897,8 @@
|
|||||||
(transact-react!
|
(transact-react!
|
||||||
repo
|
repo
|
||||||
[{:file/path path
|
[{:file/path path
|
||||||
:file/content content}]
|
:file/content content
|
||||||
|
:file/last-modified-at (util/time-ms)}]
|
||||||
{:key [:file/content path]
|
{:key [:file/content path]
|
||||||
:files-db? true})))
|
:files-db? true})))
|
||||||
|
|
||||||
@@ -931,13 +925,24 @@
|
|||||||
(when-let [conn (get-files-conn repo)]
|
(when-let [conn (get-files-conn repo)]
|
||||||
(->>
|
(->>
|
||||||
(d/q
|
(d/q
|
||||||
'[:find ?path ?content
|
'[:find ?path ?content
|
||||||
:where
|
:where
|
||||||
[?file :file/path ?path]
|
[?file :file/path ?path]
|
||||||
[?file :file/content ?content]]
|
[?file :file/content ?content]]
|
||||||
@conn)
|
@conn)
|
||||||
(into {}))))
|
(into {}))))
|
||||||
|
|
||||||
|
(defn get-files-full
|
||||||
|
[repo]
|
||||||
|
(when-let [conn (get-files-conn repo)]
|
||||||
|
(->>
|
||||||
|
(d/q
|
||||||
|
'[:find (pull ?file [*])
|
||||||
|
:where
|
||||||
|
[?file :file/path]]
|
||||||
|
@conn)
|
||||||
|
(flatten))))
|
||||||
|
|
||||||
(defn get-custom-css
|
(defn get-custom-css
|
||||||
[]
|
[]
|
||||||
(get-file "logseq/custom.css"))
|
(get-file "logseq/custom.css"))
|
||||||
@@ -960,12 +965,9 @@
|
|||||||
ffirst)))))
|
ffirst)))))
|
||||||
|
|
||||||
(defn reset-contents-and-blocks!
|
(defn reset-contents-and-blocks!
|
||||||
[repo-url contents blocks-pages delete-files delete-blocks]
|
[repo-url files blocks-pages delete-files delete-blocks]
|
||||||
(let [files (doall
|
(transact-files-db! repo-url files)
|
||||||
(map (fn [[file content]]
|
(let [files (map #(select-keys % [:file/path]) files)
|
||||||
(set-file-content! repo-url file content)
|
|
||||||
{:file/path file})
|
|
||||||
contents))
|
|
||||||
all-data (-> (concat delete-files delete-blocks files blocks-pages)
|
all-data (-> (concat delete-files delete-blocks files blocks-pages)
|
||||||
(util/remove-nils))]
|
(util/remove-nils))]
|
||||||
(transact! repo-url all-data)))
|
(transact! repo-url all-data)))
|
||||||
@@ -1504,22 +1506,24 @@
|
|||||||
[[(get-page-name file ast) blocks]])))))
|
[[(get-page-name file ast) blocks]])))))
|
||||||
|
|
||||||
(defn extract-all-blocks-pages
|
(defn extract-all-blocks-pages
|
||||||
[repo-url contents]
|
[repo-url files]
|
||||||
(let [result (->> contents
|
(when (seq files)
|
||||||
(map
|
(let [result (->> files
|
||||||
(fn [[file content] contents]
|
(map
|
||||||
(println "Parsing : " file)
|
(fn [{:file/keys [path content]} contents]
|
||||||
(when content
|
(println "Parsing : " path)
|
||||||
(let [utf8-content (utf8/encode content)]
|
(when content
|
||||||
(extract-blocks-pages repo-url file content utf8-content)))))
|
(let [utf8-content (utf8/encode content)]
|
||||||
(remove empty?))
|
(extract-blocks-pages repo-url path content utf8-content)))))
|
||||||
[pages block-ids blocks] (apply map concat result)
|
(remove empty?))]
|
||||||
block-ids-set (set block-ids)
|
(when (seq result)
|
||||||
blocks (map (fn [b]
|
(let [[pages block-ids blocks] (apply map concat result)
|
||||||
(-> b
|
block-ids-set (set block-ids)
|
||||||
(update :block/ref-blocks #(set/intersection (set %) block-ids-set))
|
blocks (map (fn [b]
|
||||||
(update :block/embed-blocks #(set/intersection (set %) block-ids-set)))) blocks)]
|
(-> b
|
||||||
(apply concat [pages block-ids blocks])))
|
(update :block/ref-blocks #(set/intersection (set %) block-ids-set))
|
||||||
|
(update :block/embed-blocks #(set/intersection (set %) block-ids-set)))) blocks)]
|
||||||
|
(apply concat [pages block-ids blocks]))))))
|
||||||
|
|
||||||
;; TODO: compare blocks
|
;; TODO: compare blocks
|
||||||
(defn reset-file!
|
(defn reset-file!
|
||||||
@@ -1874,17 +1878,74 @@
|
|||||||
(state/set-config! repo-url config)
|
(state/set-config! repo-url config)
|
||||||
config)))
|
config)))
|
||||||
|
|
||||||
|
(defonce persistent-jobs (atom {}))
|
||||||
|
|
||||||
|
(defn clear-repo-persistent-job!
|
||||||
|
[repo]
|
||||||
|
(when-let [old-job (get @persistent-jobs repo)]
|
||||||
|
(js/clearTimeout old-job)))
|
||||||
|
|
||||||
|
(defn- persist-if-idle!
|
||||||
|
[repo]
|
||||||
|
(clear-repo-persistent-job! repo)
|
||||||
|
(let [job (js/setTimeout
|
||||||
|
(fn []
|
||||||
|
(if (and (state/input-idle? repo)
|
||||||
|
(state/db-idle? repo))
|
||||||
|
(do
|
||||||
|
(persist! repo)
|
||||||
|
;; (state/set-db-persisted! repo true)
|
||||||
|
)
|
||||||
|
(let [job (get persistent-jobs repo)]
|
||||||
|
(persist-if-idle! repo))))
|
||||||
|
3000)]
|
||||||
|
(swap! persistent-jobs assoc repo job)))
|
||||||
|
|
||||||
|
;; only save when user's idle
|
||||||
|
(defn- repo-listen-to-tx!
|
||||||
|
[repo conn files-db?]
|
||||||
|
(d/listen! conn :persistence
|
||||||
|
(fn [tx-report]
|
||||||
|
(let [tx-id (get-tx-id tx-report)]
|
||||||
|
(state/set-last-transact-time! repo (util/time-ms))
|
||||||
|
;; (state/persist-transaction! repo files-db? tx-id (:tx-data tx-report))
|
||||||
|
(persist-if-idle! repo)))))
|
||||||
|
|
||||||
|
(defn- listen-and-persist!
|
||||||
|
[repo]
|
||||||
|
(when-let [conn (get-files-conn repo)]
|
||||||
|
(repo-listen-to-tx! repo conn true))
|
||||||
|
(when-let [conn (get-conn repo false)]
|
||||||
|
(repo-listen-to-tx! repo conn false)))
|
||||||
|
|
||||||
(defn start-db-conn!
|
(defn start-db-conn!
|
||||||
[me repo]
|
([me repo]
|
||||||
(let [files-db-name (datascript-files-db repo)
|
(start-db-conn! me repo {}))
|
||||||
files-db-conn (d/create-conn db-schema/files-db-schema)
|
([me repo {:keys [db-type]}]
|
||||||
db-name (datascript-db repo)
|
(let [files-db-name (datascript-files-db repo)
|
||||||
db-conn (d/create-conn db-schema/schema)]
|
files-db-conn (d/create-conn db-schema/files-db-schema)
|
||||||
(swap! conns assoc files-db-name files-db-conn)
|
db-name (datascript-db repo)
|
||||||
(swap! conns assoc db-name db-conn)
|
db-conn (d/create-conn db-schema/schema)]
|
||||||
(d/transact! db-conn [{:schema/version db-schema/version}])
|
(swap! conns assoc files-db-name files-db-conn)
|
||||||
(when me
|
(swap! conns assoc db-name db-conn)
|
||||||
(d/transact! db-conn [(me-tx (d/db db-conn) me)]))))
|
(d/transact! db-conn [(cond-> {:schema/version db-schema/version}
|
||||||
|
db-type
|
||||||
|
(assoc :db/type db-type))])
|
||||||
|
(when me
|
||||||
|
(d/transact! db-conn [(me-tx (d/db db-conn) me)]))
|
||||||
|
|
||||||
|
(listen-and-persist! repo))))
|
||||||
|
|
||||||
|
(defonce tx-data-debug (atom nil))
|
||||||
|
(defn with-latest-txs!
|
||||||
|
[db repo file?]
|
||||||
|
(let [txs (state/get-repo-latest-txs repo file?)
|
||||||
|
tx-data (when (seq txs) (map :tx-data txs))]
|
||||||
|
(if (seq tx-data)
|
||||||
|
(do
|
||||||
|
(swap! tx-data-debug assoc file? tx-data)
|
||||||
|
(d/db-with db tx-data))
|
||||||
|
db)))
|
||||||
|
|
||||||
(defn restore!
|
(defn restore!
|
||||||
[{:keys [repos] :as me} restore-config-handler]
|
[{:keys [repos] :as me} restore-config-handler]
|
||||||
@@ -1892,10 +1953,11 @@
|
|||||||
(doall
|
(doall
|
||||||
(for [{:keys [url]} repos]
|
(for [{:keys [url]} repos]
|
||||||
(let [repo url
|
(let [repo url
|
||||||
|
|
||||||
db-name (datascript-files-db repo)
|
db-name (datascript-files-db repo)
|
||||||
db-conn (d/create-conn db-schema/files-db-schema)]
|
db-conn (d/create-conn db-schema/files-db-schema)]
|
||||||
(swap! conns assoc db-name db-conn)
|
(swap! conns assoc db-name db-conn)
|
||||||
(p/let [stored (-> (.getItem localforage-instance db-name)
|
(p/let [stored (-> (idb/get-item db-name)
|
||||||
(p/then (fn [result]
|
(p/then (fn [result]
|
||||||
result))
|
result))
|
||||||
(p/catch (fn [error]
|
(p/catch (fn [error]
|
||||||
@@ -1908,14 +1970,15 @@
|
|||||||
db-conn (d/create-conn db-schema/schema)
|
db-conn (d/create-conn db-schema/schema)
|
||||||
_ (d/transact! db-conn [{:schema/version db-schema/version}])
|
_ (d/transact! db-conn [{:schema/version db-schema/version}])
|
||||||
_ (swap! conns assoc db-name db-conn)
|
_ (swap! conns assoc db-name db-conn)
|
||||||
stored (.getItem localforage-instance db-name)
|
stored (idb/get-item db-name)
|
||||||
_ (if stored
|
_ (if stored
|
||||||
(let [stored-db (string->db stored)
|
(let [stored-db (string->db stored)
|
||||||
attached-db (d/db-with stored-db [(me-tx stored-db me)])]
|
attached-db (d/db-with stored-db [(me-tx stored-db me)])]
|
||||||
(reset-conn! db-conn attached-db))
|
(reset-conn! db-conn attached-db))
|
||||||
(when logged?
|
(when logged?
|
||||||
(d/transact! db-conn [(me-tx (d/db db-conn) me)])))]
|
(d/transact! db-conn [(me-tx (d/db db-conn) me)])))]
|
||||||
(restore-config-handler repo)))))))
|
(restore-config-handler repo)
|
||||||
|
(listen-and-persist! repo)))))))
|
||||||
|
|
||||||
(defn- build-edges
|
(defn- build-edges
|
||||||
[edges]
|
[edges]
|
||||||
@@ -2447,6 +2510,14 @@
|
|||||||
datoms (d/datoms filtered-db :eavt)]
|
datoms (d/datoms filtered-db :eavt)]
|
||||||
@(d/conn-from-datoms datoms db-schema/schema)))))
|
@(d/conn-from-datoms datoms db-schema/schema)))))
|
||||||
|
|
||||||
|
(defn get-db-type
|
||||||
|
[repo]
|
||||||
|
(get-key-value repo :db/type))
|
||||||
|
|
||||||
|
(defn local-native-fs?
|
||||||
|
[repo]
|
||||||
|
(= :local-native-fs (get-db-type repo)))
|
||||||
|
|
||||||
;; shortcut for query a block with string ref
|
;; shortcut for query a block with string ref
|
||||||
(defn qb
|
(defn qb
|
||||||
[string-id]
|
[string-id]
|
||||||
|
|||||||
@@ -4,13 +4,17 @@
|
|||||||
|
|
||||||
(def files-db-schema
|
(def files-db-schema
|
||||||
{:file/path {:db/unique :db.unique/identity}
|
{:file/path {:db/unique :db.unique/identity}
|
||||||
:file/content {}})
|
:file/content {}
|
||||||
|
:file/last-modified-at {}
|
||||||
|
:file/size {}
|
||||||
|
:file/handle {}})
|
||||||
|
|
||||||
;; A page can corresponds to multiple files (same title),
|
;; A page can corresponds to multiple files (same title),
|
||||||
;; a month journal file can have multiple pages,
|
;; a month journal file can have multiple pages,
|
||||||
;; also, each block can be treated as a page too.
|
;; also, each block can be treated as a page too.
|
||||||
(def schema
|
(def schema
|
||||||
{:schema/version {}
|
{:schema/version {}
|
||||||
|
:db/type {}
|
||||||
:db/ident {:db/unique :db.unique/identity}
|
:db/ident {:db/unique :db.unique/identity}
|
||||||
|
|
||||||
;; user
|
;; user
|
||||||
|
|||||||
@@ -67,13 +67,13 @@ title: How to take dummy notes?
|
|||||||
|
|
||||||
## Hello, I'm a block!
|
## Hello, I'm a block!
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:custom_id: 5f713e91-8a3c-4b04-a33a-c39482428e2d
|
:id: 5f713e91-8a3c-4b04-a33a-c39482428e2d
|
||||||
:END:
|
:END:
|
||||||
### I'm a child block!
|
### I'm a child block!
|
||||||
### I'm another child block!
|
### I'm another child block!
|
||||||
## Hey, I'm another block!
|
## Hey, I'm another block!
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:custom_id: 5f713ea8-8cba-403d-ac00-9964b1ec7190
|
:id: 5f713ea8-8cba-403d-ac00-9964b1ec7190
|
||||||
:END:
|
:END:
|
||||||
"
|
"
|
||||||
:on-boarding/title "Hi, welcome to Logseq!"
|
:on-boarding/title "Hi, welcome to Logseq!"
|
||||||
@@ -301,7 +301,9 @@ title: How to take dummy notes?
|
|||||||
:new-file "New file"
|
:new-file "New file"
|
||||||
:graph "Graph"
|
:graph "Graph"
|
||||||
:publishing "Publishing"
|
:publishing "Publishing"
|
||||||
|
:export "Export public pages"
|
||||||
:all-repos "All repos"
|
:all-repos "All repos"
|
||||||
|
:all-graphs "All graphs"
|
||||||
:all-pages "All pages"
|
:all-pages "All pages"
|
||||||
:all-files "All files"
|
:all-files "All files"
|
||||||
:all-journals "All journals"
|
:all-journals "All journals"
|
||||||
@@ -316,7 +318,6 @@ title: How to take dummy notes?
|
|||||||
:parsing-files "Parsing files"
|
:parsing-files "Parsing files"
|
||||||
:loading-files "Loading files"
|
:loading-files "Loading files"
|
||||||
:login-github "Login with Github"
|
:login-github "Login with Github"
|
||||||
:excalidraw-title "Draw with Excalidraw"
|
|
||||||
:go-to "Go to "
|
:go-to "Go to "
|
||||||
:or "or"
|
:or "or"
|
||||||
:download "Download"
|
:download "Download"
|
||||||
@@ -324,7 +325,9 @@ title: How to take dummy notes?
|
|||||||
:language "Language"
|
:language "Language"
|
||||||
:white "Light"
|
:white "Light"
|
||||||
:dark "Dark"
|
:dark "Dark"
|
||||||
:remove-background "Remove background"}
|
:remove-background "Remove background"
|
||||||
|
:open "Open"
|
||||||
|
:open-a-directory "Open a local directory"}
|
||||||
|
|
||||||
:fr {:help/about "A propos de Logseq"
|
:fr {:help/about "A propos de Logseq"
|
||||||
:help/bug "Signaler une anomalie"
|
:help/bug "Signaler une anomalie"
|
||||||
@@ -517,7 +520,6 @@ title: How to take dummy notes?
|
|||||||
:parsing-files "Analyse des fichiers"
|
:parsing-files "Analyse des fichiers"
|
||||||
:loading-files "Chargement des fichiers"
|
:loading-files "Chargement des fichiers"
|
||||||
:login-github "S'authentifier avec Github"
|
:login-github "S'authentifier avec Github"
|
||||||
:excalidraw-title "Dessiner avec Excalidraw"
|
|
||||||
:go-to "Aller à "
|
:go-to "Aller à "
|
||||||
:or "ou"
|
:or "ou"
|
||||||
:download "Télécharger"
|
:download "Télécharger"
|
||||||
@@ -749,7 +751,9 @@ title: How to take dummy notes?
|
|||||||
:new-page "新页面"
|
:new-page "新页面"
|
||||||
:new-file "新文件"
|
:new-file "新文件"
|
||||||
:graph "图谱"
|
:graph "图谱"
|
||||||
:publishing "发布/下载 HTML 文件"
|
:publishing "发布"
|
||||||
|
:export "导出公开页面"
|
||||||
|
:all-graphs "所有库"
|
||||||
:all-repos "所有库"
|
:all-repos "所有库"
|
||||||
:all-pages "所有页面"
|
:all-pages "所有页面"
|
||||||
:all-files "所有文件"
|
:all-files "所有文件"
|
||||||
@@ -764,7 +768,6 @@ title: How to take dummy notes?
|
|||||||
:parsing-files "正在解析文件"
|
:parsing-files "正在解析文件"
|
||||||
:loading-files "正在加载文件"
|
:loading-files "正在加载文件"
|
||||||
:login-github "用 Github 登录"
|
:login-github "用 Github 登录"
|
||||||
:excalidraw-title "用 Excalidraw 画图"
|
|
||||||
:go-to "转到"
|
:go-to "转到"
|
||||||
:or "或"
|
:or "或"
|
||||||
:download "下载"
|
:download "下载"
|
||||||
@@ -772,7 +775,9 @@ title: How to take dummy notes?
|
|||||||
:language "语言"
|
:language "语言"
|
||||||
:white "亮色"
|
:white "亮色"
|
||||||
:dark "暗黑"
|
:dark "暗黑"
|
||||||
:remove-background "去除背景"}
|
:remove-background "去除背景"
|
||||||
|
:open "打开"
|
||||||
|
:open-a-directory "打开本地文件夹"}
|
||||||
|
|
||||||
:zh-Hant {:on-boarding/title "你好,歡迎使用 Logseq!"
|
:zh-Hant {:on-boarding/title "你好,歡迎使用 Logseq!"
|
||||||
:on-boarding/sharing "分享"
|
:on-boarding/sharing "分享"
|
||||||
@@ -1008,7 +1013,6 @@ title: How to take dummy notes?
|
|||||||
:parsing-files "正在解析文件"
|
:parsing-files "正在解析文件"
|
||||||
:loading-files "正在加載文件"
|
:loading-files "正在加載文件"
|
||||||
:login-github "用 Github 登錄"
|
:login-github "用 Github 登錄"
|
||||||
:excalidraw-title "用 Excalidraw 畫圖"
|
|
||||||
:go-to "轉到"
|
:go-to "轉到"
|
||||||
:or "或"
|
:or "或"
|
||||||
:download "下載"
|
:download "下載"
|
||||||
@@ -1246,7 +1250,6 @@ title: How to take dummy notes?
|
|||||||
:parsing-files "Lêer ontleding"
|
:parsing-files "Lêer ontleding"
|
||||||
:loading-files "Laai lêers"
|
:loading-files "Laai lêers"
|
||||||
:login-github "Aantekening deur Github"
|
:login-github "Aantekening deur Github"
|
||||||
:excalidraw-title "Teken met Excalidraw"
|
|
||||||
:go-to "Gaan na "
|
:go-to "Gaan na "
|
||||||
:or "of"
|
:or "of"
|
||||||
:download "Laai af"
|
:download "Laai af"
|
||||||
|
|||||||
@@ -9,6 +9,19 @@
|
|||||||
(-> ((gobj/get jsdiff "diffLines") s1 s2)
|
(-> ((gobj/get jsdiff "diffLines") s1 s2)
|
||||||
bean/->clj))
|
bean/->clj))
|
||||||
|
|
||||||
|
(defn diff-words
|
||||||
|
[s1 s2]
|
||||||
|
(-> ((gobj/get jsdiff "diffWords") s1 s2)
|
||||||
|
bean/->clj))
|
||||||
|
|
||||||
|
(defn removed?
|
||||||
|
[s1 s2]
|
||||||
|
(when (and s1 s2)
|
||||||
|
(let [diff-result (diff-words s1 s2)]
|
||||||
|
(->> diff-result
|
||||||
|
(some :removed)
|
||||||
|
(boolean)))))
|
||||||
|
|
||||||
;; (find-position "** hello _w_" "hello w")
|
;; (find-position "** hello _w_" "hello w")
|
||||||
(defn find-position
|
(defn find-position
|
||||||
[markup text]
|
[markup text]
|
||||||
|
|||||||
@@ -1,53 +1,259 @@
|
|||||||
(ns frontend.fs
|
(ns frontend.fs
|
||||||
(:require [frontend.util :as util]))
|
(:require [frontend.util :as util :refer-macros [profile]]
|
||||||
|
[frontend.config :as config]
|
||||||
|
[clojure.string :as string]
|
||||||
|
[frontend.idb :as idb]
|
||||||
|
[promesa.core :as p]
|
||||||
|
[goog.object :as gobj]
|
||||||
|
[frontend.diff :as diff]
|
||||||
|
[clojure.set :as set]
|
||||||
|
[lambdaisland.glogi :as log]
|
||||||
|
["/frontend/utils" :as utils]))
|
||||||
|
|
||||||
|
;; We need to cache the file handles in the memory so that
|
||||||
|
;; the browser will not keep asking permissions.
|
||||||
|
(defonce nfs-file-handles-cache (atom {}))
|
||||||
|
|
||||||
|
(defn get-nfs-file-handle
|
||||||
|
[handle-path]
|
||||||
|
(get @nfs-file-handles-cache handle-path))
|
||||||
|
|
||||||
|
(defn add-nfs-file-handle!
|
||||||
|
[handle-path handle]
|
||||||
|
(swap! nfs-file-handles-cache assoc handle-path handle))
|
||||||
|
|
||||||
|
(defn remove-nfs-file-handle!
|
||||||
|
[handle-path]
|
||||||
|
(swap! nfs-file-handles-cache dissoc handle-path))
|
||||||
|
|
||||||
|
;; TODO:
|
||||||
|
;; We need to support several platforms:
|
||||||
|
;; 1. Chrome native file system API (lighting-fs wip)
|
||||||
|
;; 2. IndexedDB (lighting-fs)
|
||||||
|
;; 3. NodeJS
|
||||||
|
#_(defprotocol Fs
|
||||||
|
(mkdir! [this dir])
|
||||||
|
(readdir! [this dir])
|
||||||
|
(unlink! [this path opts])
|
||||||
|
(rename! [this old-path new-path])
|
||||||
|
(rmdir! [this dir])
|
||||||
|
(read-file [dir path option])
|
||||||
|
(write-file! [dir path content])
|
||||||
|
(stat [dir path]))
|
||||||
|
|
||||||
|
(defn local-db?
|
||||||
|
[dir]
|
||||||
|
(and (string? dir)
|
||||||
|
(config/local-db? (subs dir 1))))
|
||||||
|
|
||||||
(defn mkdir
|
(defn mkdir
|
||||||
[dir]
|
[dir]
|
||||||
(when (and dir js/window.pfs)
|
(cond
|
||||||
(js/window.pfs.mkdir dir)))
|
(local-db? dir)
|
||||||
|
(let [[root new-dir] (rest (string/split dir "/"))
|
||||||
|
root-handle (str "handle/" root)]
|
||||||
|
(p/let [handle (idb/get-item root-handle)]
|
||||||
|
(when handle (utils/verifyPermission handle true))
|
||||||
|
(when (and handle new-dir
|
||||||
|
(not (string/blank? new-dir)))
|
||||||
|
(-> (p/let [handle (.getDirectoryHandle ^js handle new-dir
|
||||||
|
#js {:create true})
|
||||||
|
handle-path (str root-handle "/" new-dir)
|
||||||
|
_ (idb/set-item! handle-path handle)]
|
||||||
|
(add-nfs-file-handle! handle-path handle)
|
||||||
|
(println "Stored handle: " (str root-handle "/" new-dir)))
|
||||||
|
(p/catch (fn [error]
|
||||||
|
(println "mkdir error: " error ", dir: " dir)
|
||||||
|
(js/console.error error)))))))
|
||||||
|
|
||||||
(defn mkdir-if-not-exists
|
(and dir js/window.pfs)
|
||||||
[dir]
|
(js/window.pfs.mkdir dir)
|
||||||
(when (and dir js/window.pfs)
|
|
||||||
(util/p-handle
|
:else
|
||||||
(js/window.pfs.stat dir)
|
(println (str "mkdir " dir " failed"))))
|
||||||
(fn [_stat])
|
|
||||||
(fn [_error] (js/window.pfs.mkdir dir)))))
|
|
||||||
|
|
||||||
(defn readdir
|
(defn readdir
|
||||||
[dir]
|
[dir]
|
||||||
(when (and dir js/window.pfs)
|
(cond
|
||||||
(js/window.pfs.readdir dir)))
|
(local-db? dir)
|
||||||
|
(let [prefix (str "handle/" dir)
|
||||||
|
cached-files (keys @nfs-file-handles-cache)]
|
||||||
|
(p/resolved
|
||||||
|
(->> (filter #(string/starts-with? % (str prefix "/")) cached-files)
|
||||||
|
(map (fn [path]
|
||||||
|
(string/replace path prefix ""))))))
|
||||||
|
|
||||||
|
(and dir js/window.pfs)
|
||||||
|
(js/window.pfs.readdir dir)
|
||||||
|
|
||||||
|
:else
|
||||||
|
nil))
|
||||||
|
|
||||||
(defn unlink
|
(defn unlink
|
||||||
[path opts]
|
[path opts]
|
||||||
(js/window.pfs.unlink path opts))
|
(cond
|
||||||
|
(local-db? path)
|
||||||
|
(let [[dir basename] (util/get-dir-and-basename path)
|
||||||
|
handle-path (str "handle" path)]
|
||||||
|
(->
|
||||||
|
(p/let [handle (idb/get-item (str "handle" dir))
|
||||||
|
_ (idb/remove-item! handle-path)]
|
||||||
|
(when handle
|
||||||
|
(.removeEntry ^js handle basename))
|
||||||
|
(remove-nfs-file-handle! handle-path))
|
||||||
|
(p/catch (fn [error]
|
||||||
|
(log/error :unlink/path {:path path
|
||||||
|
:error error})))))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(js/window.pfs.unlink path opts)))
|
||||||
|
|
||||||
|
(defn rmdir
|
||||||
|
"Remove the directory recursively."
|
||||||
|
[dir]
|
||||||
|
(cond
|
||||||
|
(local-db? dir)
|
||||||
|
nil
|
||||||
|
|
||||||
|
:else
|
||||||
|
(js/window.workerThread.rimraf dir)))
|
||||||
|
|
||||||
|
(defn read-file
|
||||||
|
([dir path]
|
||||||
|
(read-file dir path (clj->js {:encoding "utf8"})))
|
||||||
|
([dir path option]
|
||||||
|
(cond
|
||||||
|
(local-db? dir)
|
||||||
|
(let [handle-path (str "handle" dir "/" path)]
|
||||||
|
(p/let [handle (idb/get-item handle-path)
|
||||||
|
local-file (and handle (.getFile handle))]
|
||||||
|
(and local-file (.text local-file))))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(js/window.pfs.readFile (str dir "/" path) option))))
|
||||||
|
|
||||||
|
(defn diff-removed?
|
||||||
|
[format s1 s2]
|
||||||
|
(when (and s1 s2)
|
||||||
|
(let [diff-result (diff/diff-words s1 s2)
|
||||||
|
block-pattern (config/get-block-pattern format)]
|
||||||
|
(some (fn [{:keys [removed value]}]
|
||||||
|
(and removed
|
||||||
|
value
|
||||||
|
;; FIXME: not sure why this happened, it might be related to
|
||||||
|
;; the async block operations (inserting blocks)
|
||||||
|
(not (set/superset? #{"#" "\n"} (set (distinct value))))))
|
||||||
|
diff-result))))
|
||||||
|
|
||||||
|
(defn write-file
|
||||||
|
([dir path content]
|
||||||
|
(write-file dir path content nil))
|
||||||
|
([dir path content old-content]
|
||||||
|
(cond
|
||||||
|
(local-db? dir)
|
||||||
|
(let [parts (string/split path "/")
|
||||||
|
basename (last parts)
|
||||||
|
sub-dir (->> (butlast parts)
|
||||||
|
(remove string/blank?)
|
||||||
|
(string/join "/"))
|
||||||
|
sub-dir-handle-path (str "handle/"
|
||||||
|
(subs dir 1)
|
||||||
|
(if sub-dir
|
||||||
|
(str "/" sub-dir)))
|
||||||
|
handle-path (if (= "/" (last sub-dir-handle-path))
|
||||||
|
(subs sub-dir-handle-path 0 (dec (count sub-dir-handle-path)))
|
||||||
|
sub-dir-handle-path)
|
||||||
|
basename-handle-path (str handle-path "/" basename)]
|
||||||
|
(p/let [file-handle (idb/get-item basename-handle-path)]
|
||||||
|
(add-nfs-file-handle! basename-handle-path file-handle)
|
||||||
|
(if file-handle
|
||||||
|
(p/let [local-file (.getFile file-handle)
|
||||||
|
local-content (.text local-file)]
|
||||||
|
(let [format (-> (util/get-file-ext path)
|
||||||
|
(config/get-file-format))]
|
||||||
|
(if (and local-content old-content
|
||||||
|
;; To prevent data loss, it's not enough to just compare using `=`.
|
||||||
|
;; Also, we need to benchmark the performance of `diff/diff-words `
|
||||||
|
(not (diff-removed?
|
||||||
|
format
|
||||||
|
(string/trim local-content)
|
||||||
|
(string/trim old-content))))
|
||||||
|
(do
|
||||||
|
(utils/verifyPermission file-handle true)
|
||||||
|
(utils/writeFile file-handle content))
|
||||||
|
(js/alert (str "The file has been modified in your local disk! File path: " path
|
||||||
|
", save your changes and click the refresh button to reload it.")))))
|
||||||
|
;; create file handle
|
||||||
|
(->
|
||||||
|
(p/let [handle (idb/get-item handle-path)]
|
||||||
|
(if handle
|
||||||
|
(do
|
||||||
|
(utils/verifyPermission handle true)
|
||||||
|
(p/let [file-handle (.getFileHandle ^js handle basename #js {:create true})
|
||||||
|
_ (idb/set-item! basename-handle-path file-handle)]
|
||||||
|
(utils/writeFile file-handle content)))
|
||||||
|
(println "Error: directory handle not exists: " handle-path)))
|
||||||
|
(p/catch (fn [error]
|
||||||
|
(println "Write local file failed: " {:path path})
|
||||||
|
(js/console.error error)))))))
|
||||||
|
|
||||||
|
js/window.pfs
|
||||||
|
(js/window.pfs.writeFile (str dir "/" path) content)
|
||||||
|
|
||||||
|
:else
|
||||||
|
nil)))
|
||||||
|
|
||||||
(defn rename
|
(defn rename
|
||||||
[old-path new-path]
|
[old-path new-path]
|
||||||
(js/window.pfs.rename old-path new-path))
|
(cond
|
||||||
|
(local-db? old-path)
|
||||||
|
;; create new file
|
||||||
|
;; delete old file
|
||||||
|
(p/let [[dir basename] (util/get-dir-and-basename old-path)
|
||||||
|
[_ new-basename] (util/get-dir-and-basename new-path)
|
||||||
|
handle (idb/get-item (str "handle" old-path))
|
||||||
|
file (.getFile handle)
|
||||||
|
content (.text file)
|
||||||
|
_ (write-file dir new-basename content)]
|
||||||
|
(unlink old-path nil))
|
||||||
|
|
||||||
(defn rmdir
|
:else
|
||||||
[dir]
|
(js/window.pfs.rename old-path new-path)))
|
||||||
(js/window.workerThread.rimraf dir))
|
|
||||||
|
|
||||||
(defn read-file
|
|
||||||
[dir path]
|
|
||||||
(js/window.pfs.readFile (str dir "/" path)
|
|
||||||
(clj->js {:encoding "utf8"})))
|
|
||||||
|
|
||||||
(defn read-file-2
|
|
||||||
[dir path]
|
|
||||||
(js/window.pfs.readFile (str dir "/" path)
|
|
||||||
(clj->js {})))
|
|
||||||
|
|
||||||
(defn write-file
|
|
||||||
[dir path content]
|
|
||||||
(and js/window.pfs (js/window.pfs.writeFile (str dir "/" path) content)))
|
|
||||||
|
|
||||||
(defn stat
|
(defn stat
|
||||||
[dir path]
|
[dir path]
|
||||||
(js/window.pfs.stat (str dir "/" path)))
|
(let [append-path (if path
|
||||||
|
(str "/"
|
||||||
|
(if (= \/ (first path))
|
||||||
|
(subs path 1)
|
||||||
|
path))
|
||||||
|
"")]
|
||||||
|
(cond
|
||||||
|
(local-db? dir)
|
||||||
|
(if-let [file (get-nfs-file-handle (str "handle/"
|
||||||
|
(string/replace-first dir "/" "")
|
||||||
|
append-path))]
|
||||||
|
(p/let [file (.getFile file)]
|
||||||
|
(let [get-attr #(gobj/get file %)]
|
||||||
|
{:file/last-modified-at (get-attr "lastModified")
|
||||||
|
:file/size (get-attr "size")
|
||||||
|
:file/type (get-attr "type")}))
|
||||||
|
(p/rejected "File not exists"))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(do
|
||||||
|
(js/window.pfs.stat (str dir append-path))))))
|
||||||
|
|
||||||
|
(defn mkdir-if-not-exists
|
||||||
|
[dir]
|
||||||
|
(when dir
|
||||||
|
(let [local? (config/local-db? dir)]
|
||||||
|
(when (or local? js/window.pfs)
|
||||||
|
(util/p-handle
|
||||||
|
(stat dir nil)
|
||||||
|
(fn [_stat])
|
||||||
|
(fn [error]
|
||||||
|
(mkdir dir)))))))
|
||||||
|
|
||||||
(defn create-if-not-exists
|
(defn create-if-not-exists
|
||||||
([dir path]
|
([dir path]
|
||||||
@@ -70,5 +276,9 @@
|
|||||||
(fn [_stat] true)
|
(fn [_stat] true)
|
||||||
(fn [_e] false)))
|
(fn [_e] false)))
|
||||||
|
|
||||||
(comment
|
(defn check-directory-permission!
|
||||||
(def dir "/notes"))
|
[repo]
|
||||||
|
(when (config/local-db? repo)
|
||||||
|
(p/let [handle (idb/get-item (str "handle/" repo))]
|
||||||
|
(when handle
|
||||||
|
(utils/verifyPermission handle true)))))
|
||||||
|
|||||||
@@ -13,11 +13,14 @@
|
|||||||
[frontend.handler.page :as page-handler]
|
[frontend.handler.page :as page-handler]
|
||||||
[frontend.handler.repo :as repo-handler]
|
[frontend.handler.repo :as repo-handler]
|
||||||
[frontend.handler.file :as file-handler]
|
[frontend.handler.file :as file-handler]
|
||||||
|
[frontend.handler.editor :as editor-handler]
|
||||||
[frontend.handler.ui :as ui-handler]
|
[frontend.handler.ui :as ui-handler]
|
||||||
[frontend.handler.export :as export-handler]
|
[frontend.handler.export :as export-handler]
|
||||||
|
[frontend.handler.web.nfs :as nfs]
|
||||||
[frontend.ui :as ui]
|
[frontend.ui :as ui]
|
||||||
[goog.object :as gobj]
|
[goog.object :as gobj]
|
||||||
[frontend.helper :as helper]
|
[frontend.helper :as helper]
|
||||||
|
[frontend.idb :as idb]
|
||||||
[lambdaisland.glogi :as log]))
|
[lambdaisland.glogi :as log]))
|
||||||
|
|
||||||
(defn- watch-for-date!
|
(defn- watch-for-date!
|
||||||
@@ -25,7 +28,8 @@
|
|||||||
(js/setInterval (fn []
|
(js/setInterval (fn []
|
||||||
(state/set-today! (date/today))
|
(state/set-today! (date/today))
|
||||||
(when-let [repo (state/get-current-repo)]
|
(when-let [repo (state/get-current-repo)]
|
||||||
(when (db/cloned? repo)
|
(when (or (db/cloned? repo)
|
||||||
|
(config/local-db? repo))
|
||||||
(let [today-page (string/lower-case (date/today))]
|
(let [today-page (string/lower-case (date/today))]
|
||||||
(when (empty? (db/get-page-blocks-no-cache repo today-page))
|
(when (empty? (db/get-page-blocks-no-cache repo today-page))
|
||||||
(repo-handler/create-today-journal-if-not-exists repo))))))
|
(repo-handler/create-today-journal-if-not-exists repo))))))
|
||||||
@@ -55,15 +59,13 @@
|
|||||||
|
|
||||||
(defn clear-stores-and-refresh!
|
(defn clear-stores-and-refresh!
|
||||||
[]
|
[]
|
||||||
(p/let [_ (db/clear-local-storage-and-idb!)]
|
(p/let [_ (idb/clear-local-storage-and-idb!)]
|
||||||
(let [{:keys [me logged? repos]} (get-me-and-repos)]
|
(let [{:keys [me logged? repos]} (get-me-and-repos)]
|
||||||
(js/window.location.reload))))
|
(js/window.location.reload))))
|
||||||
|
|
||||||
(defn restore-and-setup!
|
(defn restore-and-setup!
|
||||||
[me repos logged?]
|
[me repos logged?]
|
||||||
;; wait until pfs is loaded
|
(let [interval (atom nil)
|
||||||
(let [pfs-loaded? (atom js/window.pfs)
|
|
||||||
interval (atom nil)
|
|
||||||
inner-fn (fn []
|
inner-fn (fn []
|
||||||
(when (and @interval js/window.pfs)
|
(when (and @interval js/window.pfs)
|
||||||
(js/clearInterval @interval)
|
(js/clearInterval @interval)
|
||||||
@@ -74,11 +76,15 @@
|
|||||||
(ui-handler/add-style-if-exists!))))
|
(ui-handler/add-style-if-exists!))))
|
||||||
(p/then
|
(p/then
|
||||||
(fn []
|
(fn []
|
||||||
(if (and (not logged?)
|
(cond
|
||||||
(not (seq (db/get-files config/local-repo))))
|
(and (not logged?)
|
||||||
|
(not (seq (db/get-files config/local-repo)))
|
||||||
|
;; Not native local directory
|
||||||
|
(not (some config/local-db? (map :url repos))))
|
||||||
(repo-handler/setup-local-repo-if-not-exists!)
|
(repo-handler/setup-local-repo-if-not-exists!)
|
||||||
(state/set-db-restoring! false))
|
|
||||||
|
|
||||||
|
:else
|
||||||
|
(state/set-db-restoring! false))
|
||||||
(if (schema-changed?)
|
(if (schema-changed?)
|
||||||
(do
|
(do
|
||||||
(notification/show!
|
(notification/show!
|
||||||
@@ -86,18 +92,20 @@
|
|||||||
:warning
|
:warning
|
||||||
false)
|
false)
|
||||||
(let [export-repos (for [repo repos]
|
(let [export-repos (for [repo repos]
|
||||||
(when-let [url (:url repo)]
|
(when-let [url (:url repo)]
|
||||||
(println "Export repo: " url)
|
(println "Export repo: " url)
|
||||||
(export-handler/export-repo-as-zip! url)))]
|
(export-handler/export-repo-as-zip! url)))]
|
||||||
(-> (p/all export-repos)
|
(-> (p/all export-repos)
|
||||||
(p/then (fn []
|
(p/then (fn []
|
||||||
(store-schema!)
|
(store-schema!)
|
||||||
(js/setTimeout clear-stores-and-refresh! 5000)))
|
(js/setTimeout clear-stores-and-refresh! 5000)))
|
||||||
(p/catch (fn [error]
|
(p/catch (fn [error]
|
||||||
(log/error :export/zip {:error error
|
(log/error :export/zip {:error error
|
||||||
:repos repos}))))))
|
:repos repos}))))))
|
||||||
(store-schema!))
|
(store-schema!))
|
||||||
|
|
||||||
|
(nfs/ask-permission-if-local?)
|
||||||
|
|
||||||
(page-handler/init-commands!)
|
(page-handler/init-commands!)
|
||||||
(if (seq (:repos me))
|
(if (seq (:repos me))
|
||||||
;; FIXME: handle error
|
;; FIXME: handle error
|
||||||
@@ -107,52 +115,13 @@
|
|||||||
(fn []
|
(fn []
|
||||||
(js/console.error "Failed to request GitHub app tokens."))))
|
(js/console.error "Failed to request GitHub app tokens."))))
|
||||||
|
|
||||||
(watch-for-date!))))))]
|
(watch-for-date!)))
|
||||||
|
(p/catch (fn [error]
|
||||||
|
(log/error :db/restore-failed error))))))]
|
||||||
;; clear this interval
|
;; clear this interval
|
||||||
(let [interval-id (js/setInterval inner-fn 50)]
|
(let [interval-id (js/setInterval inner-fn 50)]
|
||||||
(reset! interval interval-id))))
|
(reset! interval interval-id))))
|
||||||
|
|
||||||
(defn persist-repo-to-indexeddb!
|
|
||||||
([]
|
|
||||||
(persist-repo-to-indexeddb! false))
|
|
||||||
([force?]
|
|
||||||
(let [status (state/get-repo-persist-status)]
|
|
||||||
(doseq [[repo {:keys [last-stored-at last-modified-at] :as repo-status}] status]
|
|
||||||
(when (and (> last-modified-at last-stored-at)
|
|
||||||
(or force?
|
|
||||||
(and (state/get-edit-input-id)
|
|
||||||
(> (- (util/time-ms) last-stored-at) (* 5 60 1000)) ; 5 minutes
|
|
||||||
)
|
|
||||||
(nil? (state/get-edit-input-id))))
|
|
||||||
(p/let [_ (repo-handler/persist-repo! repo)]
|
|
||||||
(state/update-repo-last-stored-at! repo)))))))
|
|
||||||
|
|
||||||
(defn periodically-persist-repo-to-indexeddb!
|
|
||||||
[]
|
|
||||||
(js/setInterval persist-repo-to-indexeddb! (* 5 1000)))
|
|
||||||
|
|
||||||
(defn set-save-before-unload! []
|
|
||||||
(.addEventListener js/window "beforeunload"
|
|
||||||
(fn [e]
|
|
||||||
(when (and (not config/dev?) (state/repos-need-to-be-stored?))
|
|
||||||
(let [notification-id (atom nil)]
|
|
||||||
(let [id (notification/show!
|
|
||||||
[:div
|
|
||||||
[:p "It seems that you have some unsaved changes!"]
|
|
||||||
(ui/button "Save"
|
|
||||||
:on-click (fn [e]
|
|
||||||
(persist-repo-to-indexeddb!)
|
|
||||||
(notification/show!
|
|
||||||
"Saved successfully!"
|
|
||||||
:success)
|
|
||||||
(and @notification-id (notification/clear! @notification-id))))]
|
|
||||||
:warning
|
|
||||||
false)]
|
|
||||||
(reset! notification-id id)))
|
|
||||||
(let [message "\\o/"]
|
|
||||||
(set! (.-returnValue (or e js/window.event)) message)
|
|
||||||
message)))))
|
|
||||||
|
|
||||||
(defn- handle-connection-change
|
(defn- handle-connection-change
|
||||||
[e]
|
[e]
|
||||||
(let [online? (= (gobj/get e "type") "online")]
|
(let [online? (= (gobj/get e "type") "online")]
|
||||||
@@ -177,9 +146,22 @@
|
|||||||
(notification/show! "Sorry, it seems that your browser doesn't support IndexedDB, we recommend to use latest Chrome(Chromium) or Firefox(Non-private mode)." :error false)
|
(notification/show! "Sorry, it seems that your browser doesn't support IndexedDB, we recommend to use latest Chrome(Chromium) or Firefox(Non-private mode)." :error false)
|
||||||
(state/set-indexedb-support! false)))
|
(state/set-indexedb-support! false)))
|
||||||
|
|
||||||
(restore-and-setup! me repos logged?)
|
(p/let [nfs-dbs (idb/get-nfs-dbs)
|
||||||
|
nfs-dbs (map (fn [db]
|
||||||
|
{:url db :nfs? true}) nfs-dbs)]
|
||||||
|
(let [repos (cond
|
||||||
|
logged?
|
||||||
|
(concat
|
||||||
|
nfs-dbs
|
||||||
|
(:repos me))
|
||||||
|
|
||||||
(periodically-persist-repo-to-indexeddb!)
|
(seq nfs-dbs)
|
||||||
|
nfs-dbs
|
||||||
|
|
||||||
(db/run-batch-txs!))
|
:else
|
||||||
(set-save-before-unload!))
|
[{:url config/local-repo
|
||||||
|
:example? true}])]
|
||||||
|
(state/set-repos! repos)
|
||||||
|
(restore-and-setup! me repos logged?)))
|
||||||
|
(db/run-batch-txs!)
|
||||||
|
(editor-handler/periodically-save!)))
|
||||||
|
|||||||
@@ -18,8 +18,6 @@
|
|||||||
;; TODO: what if the remote is not named "origin", check the api from isomorphic-git
|
;; TODO: what if the remote is not named "origin", check the api from isomorphic-git
|
||||||
(git/resolve-ref repo-url (str "refs/remotes/origin/" branch))))
|
(git/resolve-ref repo-url (str "refs/remotes/origin/" branch))))
|
||||||
|
|
||||||
|
|
||||||
;; Should include un-pushed committed files too
|
|
||||||
(defn check-changed-files-status
|
(defn check-changed-files-status
|
||||||
([]
|
([]
|
||||||
(check-changed-files-status (state/get-current-repo)))
|
(check-changed-files-status (state/get-current-repo)))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
[frontend.handler.git :as git-handler]
|
[frontend.handler.git :as git-handler]
|
||||||
[frontend.handler.ui :as ui-handler]
|
[frontend.handler.ui :as ui-handler]
|
||||||
[frontend.handler.repo :as repo-handler]
|
[frontend.handler.repo :as repo-handler]
|
||||||
|
[frontend.handler.file :as file-handler]
|
||||||
[frontend.handler.notification :as notification]
|
[frontend.handler.notification :as notification]
|
||||||
[frontend.handler.draw :as draw]
|
[frontend.handler.draw :as draw]
|
||||||
[frontend.handler.expand :as expand]
|
[frontend.handler.expand :as expand]
|
||||||
@@ -614,6 +615,7 @@
|
|||||||
(block/parse-block (assoc block :block/content new-value) format))
|
(block/parse-block (assoc block :block/content new-value) format))
|
||||||
parse-result)
|
parse-result)
|
||||||
after-blocks (rebuild-after-blocks repo file (:end-pos meta) end-pos)
|
after-blocks (rebuild-after-blocks repo file (:end-pos meta) end-pos)
|
||||||
|
files [[file-path new-content]]
|
||||||
transact-fn (fn []
|
transact-fn (fn []
|
||||||
(repo-handler/transact-react-and-alter-file!
|
(repo-handler/transact-react-and-alter-file!
|
||||||
repo
|
repo
|
||||||
@@ -624,7 +626,7 @@
|
|||||||
after-blocks)
|
after-blocks)
|
||||||
{:key :block/insert
|
{:key :block/insert
|
||||||
:data (map (fn [block] (assoc block :block/page page)) blocks)}
|
:data (map (fn [block] (assoc block :block/page page)) blocks)}
|
||||||
[[file-path new-content]])
|
files)
|
||||||
(state/set-editor-op! nil))]
|
(state/set-editor-op! nil))]
|
||||||
|
|
||||||
;; Replace with batch transactions
|
;; Replace with batch transactions
|
||||||
@@ -1320,17 +1322,38 @@
|
|||||||
nil)
|
nil)
|
||||||
(state/conj-selection-block! element up?)))))))
|
(state/conj-selection-block! element up?)))))))
|
||||||
|
|
||||||
|
(defn save-block-aux!
|
||||||
|
[block value format]
|
||||||
|
(let [value (text/remove-level-spaces value format true)
|
||||||
|
new-value (block/with-levels value format block)
|
||||||
|
properties (with-timetracking-properties block value)]
|
||||||
|
;; FIXME: somehow frontend.components.editor's will-unmount event will loop forever
|
||||||
|
;; maybe we shouldn't save the block/file in "will-unmount" event?
|
||||||
|
(save-block-if-changed! block new-value
|
||||||
|
{:custom-properties properties})))
|
||||||
|
|
||||||
(defn save-block!
|
(defn save-block!
|
||||||
[{:keys [format block id repo dummy?] :as state} value]
|
[{:keys [format block id repo dummy?] :as state} value]
|
||||||
(when (or (:db/id (db/entity repo [:block/uuid (:block/uuid block)]))
|
(when (or (:db/id (db/entity repo [:block/uuid (:block/uuid block)]))
|
||||||
dummy?)
|
dummy?)
|
||||||
(let [value (text/remove-level-spaces value format true)
|
(save-block-aux! block value format)))
|
||||||
new-value (block/with-levels value format block)
|
|
||||||
properties (with-timetracking-properties block value)]
|
(defn save-current-block-when-idle!
|
||||||
;; FIXME: somehow frontend.components.editor's will-unmount event will loop forever
|
[]
|
||||||
;; maybe we shouldn't save the block/file in "will-unmount" event?
|
(when-let [repo (state/get-current-repo)]
|
||||||
(save-block-if-changed! block new-value
|
(when (state/input-idle? repo)
|
||||||
{:custom-properties properties}))))
|
(let [input-id (state/get-edit-input-id)
|
||||||
|
block (state/get-edit-block)
|
||||||
|
elem (and input-id (gdom/getElement input-id))
|
||||||
|
db-block (db/entity [:block/uuid (:block/uuid block)])
|
||||||
|
db-content (:block/content db-block)
|
||||||
|
db-content-without-heading (and db-content
|
||||||
|
(util/safe-subs db-content (:block/level db-block)))
|
||||||
|
value (and elem (gobj/get elem "value"))]
|
||||||
|
(when (and block value db-content-without-heading
|
||||||
|
(not= (string/trim db-content-without-heading)
|
||||||
|
(string/trim value)))
|
||||||
|
(save-block-aux! block value (:block/format block)))))))
|
||||||
|
|
||||||
(defn on-up-down
|
(defn on-up-down
|
||||||
[state e up?]
|
[state e up?]
|
||||||
@@ -1957,3 +1980,14 @@
|
|||||||
(state/set-editor-show-block-search! false)
|
(state/set-editor-show-block-search! false)
|
||||||
(state/set-editor-show-page-search! false)
|
(state/set-editor-show-page-search! false)
|
||||||
(state/set-editor-show-page-search-hashtag! false))))))
|
(state/set-editor-show-page-search-hashtag! false))))))
|
||||||
|
|
||||||
|
(defn periodically-save!
|
||||||
|
[]
|
||||||
|
(js/setInterval save-current-block-when-idle! 3000))
|
||||||
|
|
||||||
|
(defn get-current-input-value
|
||||||
|
[]
|
||||||
|
(let [edit-input-id (state/get-edit-input-id)
|
||||||
|
input (and edit-input-id (gdom/getElement edit-input-id))]
|
||||||
|
(when input
|
||||||
|
(gobj/get input "value"))))
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
[frontend.format :as format]
|
[frontend.format :as format]
|
||||||
[clojure.string :as string]
|
[clojure.string :as string]
|
||||||
[frontend.history :as history]
|
[frontend.history :as history]
|
||||||
[frontend.handler.project :as project-handler]))
|
[frontend.handler.project :as project-handler]
|
||||||
|
[lambdaisland.glogi :as log]
|
||||||
|
["ignore" :as Ignore]))
|
||||||
|
|
||||||
(defn load-file
|
(defn load-file
|
||||||
[repo-url path]
|
[repo-url path]
|
||||||
@@ -59,11 +61,11 @@
|
|||||||
(subs path 1)
|
(subs path 1)
|
||||||
path)]
|
path)]
|
||||||
(some (fn [pattern]
|
(some (fn [pattern]
|
||||||
(let [pattern (if (and (string? pattern)
|
(let [pattern (if (and (string? pattern)
|
||||||
(not= \/ (first pattern)))
|
(not= \/ (first pattern)))
|
||||||
(str "/" pattern)
|
(str "/" pattern)
|
||||||
pattern)]
|
pattern)]
|
||||||
(string/starts-with? (str "/" path) pattern))) patterns)))
|
(string/starts-with? (str "/" path) pattern))) patterns)))
|
||||||
|
|
||||||
(defn restore-config!
|
(defn restore-config!
|
||||||
([repo-url project-changed-check?]
|
([repo-url project-changed-check?]
|
||||||
@@ -71,7 +73,8 @@
|
|||||||
([repo-url config-content project-changed-check?]
|
([repo-url config-content project-changed-check?]
|
||||||
(let [old-project (:project (state/get-config))
|
(let [old-project (:project (state/get-config))
|
||||||
new-config (db/reset-config! repo-url config-content)]
|
new-config (db/reset-config! repo-url config-content)]
|
||||||
(when project-changed-check?
|
(when (and (not (config/local-db? repo-url))
|
||||||
|
project-changed-check?)
|
||||||
(let [new-project (:project new-config)
|
(let [new-project (:project new-config)
|
||||||
project-name (:name old-project)]
|
project-name (:name old-project)]
|
||||||
(when-not (= new-project old-project)
|
(when-not (= new-project old-project)
|
||||||
@@ -80,7 +83,7 @@
|
|||||||
(defn load-files
|
(defn load-files
|
||||||
[repo-url]
|
[repo-url]
|
||||||
(state/set-cloning! false)
|
(state/set-cloning! false)
|
||||||
(state/set-state! :repo/loading-files? true)
|
(state/set-loading-files! true)
|
||||||
(p/let [files (git/list-files repo-url)
|
(p/let [files (git/list-files repo-url)
|
||||||
files (bean/->clj files)
|
files (bean/->clj files)
|
||||||
config-content (load-file repo-url (str config/app-name "/" config/config-file))
|
config-content (load-file repo-url (str config/app-name "/" config/config-file))
|
||||||
@@ -98,15 +101,17 @@
|
|||||||
files (only-text-formats files)]
|
files (only-text-formats files)]
|
||||||
(-> (p/all (load-multiple-files repo-url files))
|
(-> (p/all (load-multiple-files repo-url files))
|
||||||
(p/then (fn [contents]
|
(p/then (fn [contents]
|
||||||
(ok-handler
|
(let [file-contents (cond->
|
||||||
(cond->
|
(zipmap files contents)
|
||||||
(zipmap files contents)
|
|
||||||
|
|
||||||
(seq images)
|
(seq images)
|
||||||
(merge (zipmap images (repeat (count images) "")))))))
|
(merge (zipmap images (repeat (count images) ""))))
|
||||||
|
file-contents (for [[file content] file-contents]
|
||||||
|
{:file/path file
|
||||||
|
:file/content content})]
|
||||||
|
(ok-handler file-contents))))
|
||||||
(p/catch (fn [error]
|
(p/catch (fn [error]
|
||||||
(println "load files failed: ")
|
(log/error :load-files-error error))))))
|
||||||
(js/console.dir error))))))
|
|
||||||
|
|
||||||
(defn alter-file
|
(defn alter-file
|
||||||
[repo path content {:keys [reset? re-render-root? add-history? update-status?]
|
[repo path content {:keys [reset? re-render-root? add-history? update-status?]
|
||||||
@@ -124,7 +129,7 @@
|
|||||||
(db/reset-file! repo path content))
|
(db/reset-file! repo path content))
|
||||||
(db/set-file-content! repo path content))
|
(db/set-file-content! repo path content))
|
||||||
(util/p-handle
|
(util/p-handle
|
||||||
(fs/write-file (util/get-repo-dir repo) path content)
|
(fs/write-file (util/get-repo-dir repo) path content original-content)
|
||||||
(fn [_]
|
(fn [_]
|
||||||
(git-handler/git-add repo path update-status?)
|
(git-handler/git-add repo path update-status?)
|
||||||
(when (= path (str config/app-name "/" config/config-file))
|
(when (= path (str config/app-name "/" config/config-file))
|
||||||
@@ -148,7 +153,7 @@
|
|||||||
:re-render-root? false
|
:re-render-root? false
|
||||||
:update-status? true})]
|
:update-status? true})]
|
||||||
(route-handler/redirect! {:to :file
|
(route-handler/redirect! {:to :file
|
||||||
:path-params {:path path}}))))) )
|
:path-params {:path path}}))))))
|
||||||
|
|
||||||
(defn alter-files
|
(defn alter-files
|
||||||
([repo files]
|
([repo files]
|
||||||
@@ -157,42 +162,46 @@
|
|||||||
:or {add-history? true
|
:or {add-history? true
|
||||||
update-status? true
|
update-status? true
|
||||||
reset? false}}]
|
reset? false}}]
|
||||||
(let [files-tx (mapv (fn [[path content]]
|
(p/let [file->content (let [paths (map first files)]
|
||||||
(let [original-content (db/get-file-no-sub repo path)]
|
(zipmap paths
|
||||||
[path original-content content])) files)
|
(map (fn [path] (db/get-file-no-sub repo path)) paths)))]
|
||||||
write-file-f (fn [[path content]]
|
(let [files-tx (mapv (fn [[path content]]
|
||||||
(if reset?
|
(let [original-content (get file->content path)]
|
||||||
(db/reset-file! repo path content)
|
[path original-content content])) files)
|
||||||
(db/set-file-content! repo path content))
|
write-file-f (fn [[path content]]
|
||||||
(util/p-handle
|
(if reset?
|
||||||
(fs/write-file (util/get-repo-dir repo) path content)
|
(db/reset-file! repo path content)
|
||||||
(fn [_])
|
(db/set-file-content! repo path content))
|
||||||
(fn [error]
|
(let [original-content (get file->content path)]
|
||||||
(println "Write file failed, path: " path ", content: " content)
|
(-> (p/let [_ (fs/check-directory-permission! repo)]
|
||||||
(js/console.error error))))
|
(fs/write-file (util/get-repo-dir repo) path content original-content))
|
||||||
git-add-f (fn [_result]
|
(p/catch (fn [error]
|
||||||
(let [add-helper
|
(log/error :write-file/failed {:path path
|
||||||
(fn []
|
:content content
|
||||||
(doall
|
:error error}))))))
|
||||||
(map
|
git-add-f (fn [_result]
|
||||||
(fn [[path content]]
|
(let [add-helper
|
||||||
(git-handler/git-add repo path update-status?))
|
(fn []
|
||||||
files)))]
|
(doall
|
||||||
(-> (p/all (add-helper))
|
(map
|
||||||
(p/then (fn [_]
|
(fn [[path content]]
|
||||||
(when git-add-cb
|
(git-handler/git-add repo path update-status?))
|
||||||
(git-add-cb))))
|
files)))]
|
||||||
(p/catch (fn [error]
|
(-> (p/all (add-helper))
|
||||||
(println "Git add failed:")
|
(p/then (fn [_]
|
||||||
(js/console.error error)))))
|
(when git-add-cb
|
||||||
(ui-handler/re-render-file!)
|
(git-add-cb))))
|
||||||
(when add-history?
|
(p/catch (fn [error]
|
||||||
(history/add-history! repo files-tx)))]
|
(println "Git add failed:")
|
||||||
(-> (p/all (doall (map write-file-f files)))
|
(js/console.error error)))))
|
||||||
(p/then git-add-f)
|
(ui-handler/re-render-file!)
|
||||||
(p/catch (fn [error]
|
(when add-history?
|
||||||
(println "Alter files failed:")
|
(history/add-history! repo files-tx)))]
|
||||||
(js/console.error error)))))))
|
(-> (p/all (doall (map write-file-f files)))
|
||||||
|
(p/then git-add-f)
|
||||||
|
(p/catch (fn [error]
|
||||||
|
(println "Alter files failed:")
|
||||||
|
(js/console.error error))))))))
|
||||||
|
|
||||||
(defn remove-file!
|
(defn remove-file!
|
||||||
[repo file]
|
[repo file]
|
||||||
@@ -222,3 +231,10 @@
|
|||||||
(let [path (:file/path file)
|
(let [path (:file/path file)
|
||||||
content (db/get-file path)]
|
content (db/get-file path)]
|
||||||
(alter-file repo path content {:re-render-root? true}))))
|
(alter-file repo path content {:re-render-root? true}))))
|
||||||
|
|
||||||
|
(defn ignore-files
|
||||||
|
[pattern paths]
|
||||||
|
(-> (Ignore)
|
||||||
|
(.add pattern)
|
||||||
|
(.filter (bean/->js paths))
|
||||||
|
(bean/->clj)))
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
(ns frontend.handler.git
|
(ns frontend.handler.git
|
||||||
(:refer-clojure :exclude [clone load-file])
|
|
||||||
(:require [frontend.util :as util :refer-macros [profile]]
|
(:require [frontend.util :as util :refer-macros [profile]]
|
||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
[frontend.state :as state]
|
[frontend.state :as state]
|
||||||
@@ -10,6 +9,7 @@
|
|||||||
[frontend.handler.notification :as notification]
|
[frontend.handler.notification :as notification]
|
||||||
[frontend.handler.route :as route-handler]
|
[frontend.handler.route :as route-handler]
|
||||||
[frontend.handler.common :as common-handler]
|
[frontend.handler.common :as common-handler]
|
||||||
|
[frontend.config :as config]
|
||||||
[cljs-time.local :as tl]
|
[cljs-time.local :as tl]
|
||||||
[frontend.helper :as helper]))
|
[frontend.helper :as helper]))
|
||||||
|
|
||||||
@@ -31,12 +31,13 @@
|
|||||||
([repo-url file]
|
([repo-url file]
|
||||||
(git-add repo-url file true))
|
(git-add repo-url file true))
|
||||||
([repo-url file update-status?]
|
([repo-url file update-status?]
|
||||||
(-> (p/let [result (git/add repo-url file)]
|
(when-not (config/local-db? repo-url)
|
||||||
(when update-status?
|
(-> (p/let [result (git/add repo-url file)]
|
||||||
(common-handler/check-changed-files-status)))
|
(when update-status?
|
||||||
(p/catch (fn [error]
|
(common-handler/check-changed-files-status)))
|
||||||
(println "git add '" file "' failed: " error)
|
(p/catch (fn [error]
|
||||||
(js/console.error error))))))
|
(println "git add '" file "' failed: " error)
|
||||||
|
(js/console.error error)))))))
|
||||||
|
|
||||||
(defn commit-and-force-push!
|
(defn commit-and-force-push!
|
||||||
[commit-message pushing?]
|
[commit-message pushing?]
|
||||||
|
|||||||
@@ -33,8 +33,9 @@
|
|||||||
(subs path 1)
|
(subs path 1)
|
||||||
path)]
|
path)]
|
||||||
(util/p-handle
|
(util/p-handle
|
||||||
(fs/read-file-2 (util/get-repo-dir (state/get-current-repo))
|
(fs/read-file (util/get-repo-dir (state/get-current-repo))
|
||||||
path)
|
path
|
||||||
|
{})
|
||||||
(fn [blob]
|
(fn [blob]
|
||||||
(let [blob (js/Blob. (array blob) (clj->js {:type "image"}))
|
(let [blob (js/Blob. (array blob) (clj->js {:type "image"}))
|
||||||
img-url (image/create-object-url blob)]
|
img-url (image/create-object-url blob)]
|
||||||
|
|||||||
@@ -16,38 +16,41 @@
|
|||||||
([ok-handler]
|
([ok-handler]
|
||||||
(create-project! (state/get-current-project) ok-handler))
|
(create-project! (state/get-current-project) ok-handler))
|
||||||
([project ok-handler]
|
([project ok-handler]
|
||||||
(let [config (state/get-config)
|
(when (state/logged?)
|
||||||
data {:name project
|
(let [config (state/get-config)
|
||||||
:repo (state/get-current-repo)
|
data {:name project
|
||||||
:settings (or (get config :project)
|
:repo (state/get-current-repo)
|
||||||
{:name project})}]
|
:settings (or (get config :project)
|
||||||
(util/post (str config/api "projects")
|
{:name project})}]
|
||||||
data
|
(util/post (str config/api "projects")
|
||||||
(fn [result]
|
data
|
||||||
(when-not (:message result) ; exists
|
(fn [result]
|
||||||
(swap! state/state
|
(when-not (:message result) ; exists
|
||||||
update-in [:me :projects]
|
(swap! state/state
|
||||||
(fn [projects]
|
update-in [:me :projects]
|
||||||
(util/distinct-by :name (conj projects result))))
|
(fn [projects]
|
||||||
(ok-handler project)))
|
(util/distinct-by :name (conj projects result))))
|
||||||
(fn [error]
|
(ok-handler project)))
|
||||||
(js/console.dir error)
|
(fn [error]
|
||||||
(notification/show! (util/format "Project \"%s\" already taken, please change to another name." project) :error))))))
|
(js/console.dir error)
|
||||||
|
(notification/show! (util/format "Project \"%s\" already taken, please change to another name." project) :error)))))))
|
||||||
|
|
||||||
(defn exists-or-create!
|
(defn exists-or-create!
|
||||||
[ok-handler modal-content]
|
[ok-handler modal-content]
|
||||||
(if-let [project (state/get-current-project)]
|
(when (state/logged?)
|
||||||
(if (project-exists? project)
|
(if-let [project (state/get-current-project)]
|
||||||
(ok-handler project)
|
(if (project-exists? project)
|
||||||
(create-project! ok-handler))
|
(ok-handler project)
|
||||||
(state/set-modal! modal-content)))
|
(create-project! ok-handler))
|
||||||
|
(state/set-modal! modal-content))))
|
||||||
|
|
||||||
(defn add-project!
|
(defn add-project!
|
||||||
[project]
|
[project]
|
||||||
(create-project! project
|
(when (state/logged?)
|
||||||
(fn []
|
(create-project! project
|
||||||
(notification/show! (util/format "Project \"%s\" was created successfully." project) :success)
|
(fn []
|
||||||
(state/close-modal!))))
|
(notification/show! (util/format "Project \"%s\" was created successfully." project) :success)
|
||||||
|
(state/close-modal!)))))
|
||||||
|
|
||||||
(defn sync-project-settings!
|
(defn sync-project-settings!
|
||||||
([]
|
([]
|
||||||
@@ -55,18 +58,19 @@
|
|||||||
(let [settings (:project (state/get-config))]
|
(let [settings (:project (state/get-config))]
|
||||||
(sync-project-settings! project-name settings))))
|
(sync-project-settings! project-name settings))))
|
||||||
([project-name settings]
|
([project-name settings]
|
||||||
(when-let [repo (state/get-current-repo)]
|
(when (state/logged?)
|
||||||
(if (project-exists? project-name)
|
(when-let [repo (state/get-current-repo)]
|
||||||
(util/post (str config/api "projects/" project-name)
|
(if (project-exists? project-name)
|
||||||
{:name project-name
|
(util/post (str config/api "projects/" project-name)
|
||||||
:settings settings
|
{:name project-name
|
||||||
:repo repo}
|
:settings settings
|
||||||
(fn [response]
|
:repo repo}
|
||||||
(notification/show! "Project settings changed successfully!" :success))
|
(fn [response]
|
||||||
(fn [error]
|
(notification/show! "Project settings changed successfully!" :success))
|
||||||
(println "Project settings updated failed, reason: ")
|
(fn [error]
|
||||||
(js/console.dir error)))
|
(println "Project settings updated failed, reason: ")
|
||||||
(when (and settings
|
(js/console.dir error)))
|
||||||
(not (string/blank? (:name settings)))
|
(when (and settings
|
||||||
(>= (count (string/trim (:name settings))) 2))
|
(not (string/blank? (:name settings)))
|
||||||
(add-project! (:name settings)))))))
|
(>= (count (string/trim (:name settings))) 2))
|
||||||
|
(add-project! (:name settings))))))))
|
||||||
|
|||||||
@@ -67,10 +67,12 @@
|
|||||||
(spec/validate :repos/url repo-url)
|
(spec/validate :repos/url repo-url)
|
||||||
(let [repo-dir (util/get-repo-dir repo-url)
|
(let [repo-dir (util/get-repo-dir repo-url)
|
||||||
format (state/get-preferred-format)
|
format (state/get-preferred-format)
|
||||||
path (str "pages/contents." (config/get-file-extension format))
|
path (str (state/get-pages-directory)
|
||||||
|
"/contents."
|
||||||
|
(config/get-file-extension format))
|
||||||
file-path (str "/" path)
|
file-path (str "/" path)
|
||||||
default-content (util/default-content-with-title format "contents")]
|
default-content (util/default-content-with-title format "contents")]
|
||||||
(p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/pages"))
|
(p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" (state/get-pages-directory)))
|
||||||
file-exists? (fs/create-if-not-exists repo-dir file-path default-content)]
|
file-exists? (fs/create-if-not-exists repo-dir file-path default-content)]
|
||||||
(when-not file-exists?
|
(when-not file-exists?
|
||||||
(db/reset-file! repo-url path default-content)
|
(db/reset-file! repo-url path default-content)
|
||||||
@@ -104,6 +106,8 @@
|
|||||||
(create-today-journal-if-not-exists repo-url nil))
|
(create-today-journal-if-not-exists repo-url nil))
|
||||||
([repo-url content]
|
([repo-url content]
|
||||||
(spec/validate :repos/url repo-url)
|
(spec/validate :repos/url repo-url)
|
||||||
|
(when (config/local-db? repo-url)
|
||||||
|
(fs/check-directory-permission! repo-url))
|
||||||
(let [repo-dir (util/get-repo-dir repo-url)
|
(let [repo-dir (util/get-repo-dir repo-url)
|
||||||
format (state/get-preferred-format repo-url)
|
format (state/get-preferred-format repo-url)
|
||||||
title (date/today)
|
title (date/today)
|
||||||
@@ -139,49 +143,60 @@
|
|||||||
(defn create-default-files!
|
(defn create-default-files!
|
||||||
[repo-url]
|
[repo-url]
|
||||||
(spec/validate :repos/url repo-url)
|
(spec/validate :repos/url repo-url)
|
||||||
(when (state/logged?)
|
(create-config-file-if-not-exists repo-url)
|
||||||
(create-config-file-if-not-exists repo-url)
|
(create-today-journal-if-not-exists repo-url)
|
||||||
(create-today-journal-if-not-exists repo-url)
|
(create-contents-file repo-url)
|
||||||
(create-contents-file repo-url)
|
(create-custom-theme repo-url))
|
||||||
(create-custom-theme repo-url)))
|
|
||||||
|
(defn- parse-files-and-load-to-db!
|
||||||
|
[repo-url files {:keys [first-clone? delete-files delete-blocks re-render? re-render-opts] :as opts}]
|
||||||
|
(state/set-loading-files! false)
|
||||||
|
(state/set-importing-to-db! true)
|
||||||
|
(let [file-paths (map :file/path files)
|
||||||
|
parsed-files (filter
|
||||||
|
(fn [file]
|
||||||
|
(let [format (format/get-format (:file/path file))]
|
||||||
|
(contains? config/mldoc-support-formats format)))
|
||||||
|
files)
|
||||||
|
blocks-pages (if (seq parsed-files)
|
||||||
|
(db/extract-all-blocks-pages repo-url parsed-files)
|
||||||
|
[])]
|
||||||
|
(db/reset-contents-and-blocks! repo-url files blocks-pages delete-files delete-blocks)
|
||||||
|
(let [config-file (str config/app-name "/" config/config-file)]
|
||||||
|
(if (contains? (set file-paths) config-file)
|
||||||
|
(when-let [content (some #(when (= (:file/path %) config-file)
|
||||||
|
(:file/content %)) files)]
|
||||||
|
(file-handler/restore-config! repo-url content true))))
|
||||||
|
(when first-clone? (create-default-files! repo-url))
|
||||||
|
(when re-render?
|
||||||
|
(ui-handler/re-render-root! re-render-opts))
|
||||||
|
(state/set-importing-to-db! false)))
|
||||||
|
|
||||||
(defn load-repo-to-db!
|
(defn load-repo-to-db!
|
||||||
[repo-url diffs first-clone?]
|
[repo-url {:keys [first-clone? diffs nfs-files]}]
|
||||||
(spec/validate :repos/url repo-url)
|
(spec/validate :repos/url repo-url)
|
||||||
(let [load-contents (fn [files delete-files delete-blocks re-render?]
|
(let [load-contents (fn [files option]
|
||||||
(file-handler/load-files-contents!
|
(file-handler/load-files-contents!
|
||||||
repo-url
|
repo-url
|
||||||
files
|
files
|
||||||
(fn [contents]
|
(fn [files-contents]
|
||||||
(state/set-state! :repo/loading-files? false)
|
(parse-files-and-load-to-db! repo-url files-contents option))))]
|
||||||
(state/set-state! :repo/importing-to-db? true)
|
(cond
|
||||||
(let [parsed-files (filter
|
(and (not (seq diffs)) (seq nfs-files))
|
||||||
(fn [[file _]]
|
(parse-files-and-load-to-db! repo-url nfs-files {:first-clone? true})
|
||||||
(let [format (format/get-format file)]
|
|
||||||
(contains? config/mldoc-support-formats format)))
|
first-clone?
|
||||||
contents)
|
|
||||||
blocks-pages (if (seq parsed-files)
|
|
||||||
(db/extract-all-blocks-pages repo-url parsed-files)
|
|
||||||
[])]
|
|
||||||
(db/reset-contents-and-blocks! repo-url contents blocks-pages delete-files delete-blocks)
|
|
||||||
(let [config-file (str config/app-name "/" config/config-file)]
|
|
||||||
(if (contains? (set files) config-file)
|
|
||||||
(when-let [content (get contents config-file)]
|
|
||||||
(file-handler/restore-config! repo-url content true))))
|
|
||||||
(when first-clone? (create-default-files! repo-url))
|
|
||||||
(state/set-state! :repo/importing-to-db? false)
|
|
||||||
(when re-render?
|
|
||||||
(ui-handler/re-render-root!))))))]
|
|
||||||
(if first-clone?
|
|
||||||
(->
|
(->
|
||||||
(p/let [files (file-handler/load-files repo-url)]
|
(p/let [files (file-handler/load-files repo-url)]
|
||||||
(load-contents files nil nil false))
|
(load-contents files {:first-clone? first-clone?}))
|
||||||
(p/catch (fn [error]
|
(p/catch (fn [error]
|
||||||
(println "loading files failed: ")
|
(println "loading files failed: ")
|
||||||
(js/console.dir error)
|
(js/console.dir error)
|
||||||
;; Empty repo
|
;; Empty repo
|
||||||
(create-default-files! repo-url)
|
(create-default-files! repo-url)
|
||||||
(state/set-state! :repo/loading-files? false))))
|
(state/set-loading-files! false))))
|
||||||
|
|
||||||
|
:else
|
||||||
(when (seq diffs)
|
(when (seq diffs)
|
||||||
(let [filter-diffs (fn [type] (->> (filter (fn [f] (= type (:type f))) diffs)
|
(let [filter-diffs (fn [type] (->> (filter (fn [f] (= type (:type f))) diffs)
|
||||||
(map :path)))
|
(map :path)))
|
||||||
@@ -194,22 +209,24 @@
|
|||||||
delete-pages (if (seq remove-files)
|
delete-pages (if (seq remove-files)
|
||||||
(db/delete-pages-by-files remove-files)
|
(db/delete-pages-by-files remove-files)
|
||||||
[])
|
[])
|
||||||
add-or-modify-files (util/remove-nils (concat add-files modify-files))]
|
add-or-modify-files (some->>
|
||||||
(load-contents add-or-modify-files (concat delete-files delete-pages) delete-blocks true))))))
|
(concat modify-files add-files)
|
||||||
|
(util/remove-nils))
|
||||||
(defn persist-repo!
|
options {:first-clone? first-clone?
|
||||||
[repo]
|
:delete-files (concat delete-files delete-pages)
|
||||||
(spec/validate :repos/url repo)
|
:delete-blocks delete-blocks
|
||||||
(when-let [files-conn (db/get-files-conn repo)]
|
:re-render? true}]
|
||||||
(db/persist repo @files-conn true))
|
(if (seq nfs-files)
|
||||||
(when-let [db (db/get-conn repo)]
|
(parse-files-and-load-to-db! repo-url nfs-files
|
||||||
(db/persist repo db false)))
|
(assoc options :re-render-opts {:clear-all-query-state? true}))
|
||||||
|
(load-contents add-or-modify-files options)))))))
|
||||||
|
|
||||||
(defn load-db-and-journals!
|
(defn load-db-and-journals!
|
||||||
[repo-url diffs first-clone?]
|
[repo-url diffs first-clone?]
|
||||||
(spec/validate :repos/url repo-url)
|
(spec/validate :repos/url repo-url)
|
||||||
(when (or diffs first-clone?)
|
(when (or diffs first-clone?)
|
||||||
(load-repo-to-db! repo-url diffs first-clone?)))
|
(load-repo-to-db! repo-url {:first-clone? first-clone?
|
||||||
|
:diffs diffs})))
|
||||||
|
|
||||||
(defn transact-react-and-alter-file!
|
(defn transact-react-and-alter-file!
|
||||||
[repo tx transact-option files]
|
[repo tx transact-option files]
|
||||||
@@ -224,27 +241,9 @@
|
|||||||
(when (seq pages)
|
(when (seq pages)
|
||||||
(let [children-tx (mapcat #(db/rebuild-page-blocks-children repo %) pages)]
|
(let [children-tx (mapcat #(db/rebuild-page-blocks-children repo %) pages)]
|
||||||
(when (seq children-tx)
|
(when (seq children-tx)
|
||||||
(db/transact! repo children-tx)))))
|
(db/transact! repo children-tx))))
|
||||||
(when (seq files)
|
|
||||||
(file-handler/alter-files repo files)))
|
|
||||||
|
|
||||||
; FIXME: Unused
|
|
||||||
(defn persist-repo-metadata!
|
|
||||||
[repo]
|
|
||||||
(spec/validate :repos/url repo)
|
|
||||||
(let [files (db/get-files repo)]
|
|
||||||
(when (seq files)
|
(when (seq files)
|
||||||
(let [data (db/get-sync-metadata repo)
|
(file-handler/alter-files repo files))))
|
||||||
data-str (pr-str data)]
|
|
||||||
(file-handler/alter-file repo
|
|
||||||
(str config/app-name "/" config/metadata-file)
|
|
||||||
data-str
|
|
||||||
{:reset? false})))))
|
|
||||||
|
|
||||||
(defn periodically-persist-app-metadata
|
|
||||||
[repo-url]
|
|
||||||
(js/setInterval #(persist-repo-metadata! repo-url)
|
|
||||||
(* 5 60 1000)))
|
|
||||||
|
|
||||||
(declare push)
|
(declare push)
|
||||||
|
|
||||||
@@ -351,7 +350,7 @@
|
|||||||
(let [status (db/get-key-value repo-url :git/status)]
|
(let [status (db/get-key-value repo-url :git/status)]
|
||||||
(if (and
|
(if (and
|
||||||
(db/cloned? repo-url)
|
(db/cloned? repo-url)
|
||||||
(not (state/get-edit-input-id))
|
(state/input-idle? repo-url)
|
||||||
(or (not= status :pushing)
|
(or (not= status :pushing)
|
||||||
custom-commit?))
|
custom-commit?))
|
||||||
(-> (p/let [files (js/window.workerThread.getChangedFiles (util/get-repo-dir (state/get-current-repo)))]
|
(-> (p/let [files (js/window.workerThread.getChangedFiles (util/get-repo-dir (state/get-current-repo)))]
|
||||||
@@ -445,15 +444,26 @@
|
|||||||
(defn remove-repo!
|
(defn remove-repo!
|
||||||
[{:keys [id url] :as repo}]
|
[{:keys [id url] :as repo}]
|
||||||
(spec/validate :repos/repo repo)
|
(spec/validate :repos/repo repo)
|
||||||
(util/delete (str config/api "repos/" id)
|
(let [delete-db-f (fn []
|
||||||
(fn []
|
(db/remove-conn! url)
|
||||||
(db/remove-conn! url)
|
(db/remove-db! url)
|
||||||
(db/remove-db! url)
|
(db/remove-files-db! url)
|
||||||
(db/remove-files-db! url)
|
(fs/rmdir (util/get-repo-dir url))
|
||||||
(fs/rmdir (util/get-repo-dir url))
|
(state/delete-repo! repo))]
|
||||||
(state/delete-repo! repo))
|
(if (config/local-db? url)
|
||||||
(fn [error]
|
(do
|
||||||
(prn "Delete repo failed, error: " error))))
|
(delete-db-f)
|
||||||
|
;; clear handles
|
||||||
|
)
|
||||||
|
(util/delete (str config/api "repos/" id)
|
||||||
|
delete-db-f
|
||||||
|
(fn [error]
|
||||||
|
(prn "Delete repo failed, error: " error))))))
|
||||||
|
|
||||||
|
(defn start-repo-db-if-not-exists!
|
||||||
|
[repo option]
|
||||||
|
(state/set-current-repo! repo)
|
||||||
|
(db/start-db-conn! nil repo option))
|
||||||
|
|
||||||
(defn setup-local-repo-if-not-exists!
|
(defn setup-local-repo-if-not-exists!
|
||||||
[]
|
[]
|
||||||
|
|||||||
@@ -79,7 +79,9 @@
|
|||||||
(let [{:keys [data path-params]} route
|
(let [{:keys [data path-params]} route
|
||||||
title (get-title (:name data) path-params)]
|
title (get-title (:name data) path-params)]
|
||||||
(util/set-title! title)
|
(util/set-title! title)
|
||||||
(ui-handler/scroll-and-highlight! nil)))
|
(if-let [fragment (util/get-fragment)]
|
||||||
|
(ui-handler/highlight-element! fragment)
|
||||||
|
(util/scroll-to-top))))
|
||||||
|
|
||||||
(defn go-to-search!
|
(defn go-to-search!
|
||||||
[]
|
[]
|
||||||
|
|||||||
@@ -30,12 +30,17 @@
|
|||||||
|
|
||||||
|
|
||||||
(defn re-render-root!
|
(defn re-render-root!
|
||||||
[]
|
([]
|
||||||
(when-let [component (state/get-root-component)]
|
(re-render-root! {}))
|
||||||
(db/clear-query-state-without-refs-and-embeds!)
|
([{:keys [clear-all-query-state?]
|
||||||
(rum/request-render component)
|
:or {clear-all-query-state? false}}]
|
||||||
(doseq [component (state/get-custom-query-components)]
|
(when-let [component (state/get-root-component)]
|
||||||
(rum/request-render component))))
|
(if clear-all-query-state?
|
||||||
|
(db/clear-query-state!)
|
||||||
|
(db/clear-query-state-without-refs-and-embeds!))
|
||||||
|
(rum/request-render component)
|
||||||
|
(doseq [component (state/get-custom-query-components)]
|
||||||
|
(rum/request-render component)))))
|
||||||
|
|
||||||
(defn re-render-file!
|
(defn re-render-file!
|
||||||
[]
|
[]
|
||||||
@@ -65,8 +70,7 @@
|
|||||||
(defn scroll-and-highlight!
|
(defn scroll-and-highlight!
|
||||||
[state]
|
[state]
|
||||||
(if-let [fragment (util/get-fragment)]
|
(if-let [fragment (util/get-fragment)]
|
||||||
(highlight-element! fragment)
|
(highlight-element! fragment))
|
||||||
(util/scroll-to-top))
|
|
||||||
state)
|
state)
|
||||||
|
|
||||||
(defn add-style-if-exists!
|
(defn add-style-if-exists!
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
(:require [frontend.util :as util :refer-macros [profile]]
|
(:require [frontend.util :as util :refer-macros [profile]]
|
||||||
[frontend.state :as state]
|
[frontend.state :as state]
|
||||||
[frontend.db :as db]
|
[frontend.db :as db]
|
||||||
|
[frontend.idb :as idb]
|
||||||
[frontend.config :as config]
|
[frontend.config :as config]
|
||||||
[frontend.storage :as storage]
|
[frontend.storage :as storage]
|
||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
(defn sign-out!
|
(defn sign-out!
|
||||||
[e]
|
[e]
|
||||||
(->
|
(->
|
||||||
(db/clear-local-storage-and-idb!)
|
(idb/clear-local-storage-and-idb!)
|
||||||
(p/catch (fn [e]
|
(p/catch (fn [e]
|
||||||
(println "sign out error: ")
|
(println "sign out error: ")
|
||||||
(js/console.dir e)))
|
(js/console.dir e)))
|
||||||
|
|||||||
235
src/main/frontend/handler/web/nfs.cljs
Normal file
235
src/main/frontend/handler/web/nfs.cljs
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
(ns frontend.handler.web.nfs
|
||||||
|
"The File System Access API, https://web.dev/file-system-access/."
|
||||||
|
(:require [cljs-bean.core :as bean]
|
||||||
|
[promesa.core :as p]
|
||||||
|
[goog.object :as gobj]
|
||||||
|
[goog.dom :as gdom]
|
||||||
|
[frontend.util :as util]
|
||||||
|
["/frontend/utils" :as utils]
|
||||||
|
[frontend.handler.repo :as repo-handler]
|
||||||
|
[frontend.handler.file :as file-handler]
|
||||||
|
[frontend.idb :as idb]
|
||||||
|
[frontend.state :as state]
|
||||||
|
[clojure.string :as string]
|
||||||
|
[clojure.set :as set]
|
||||||
|
[frontend.ui :as ui]
|
||||||
|
[frontend.fs :as fs]
|
||||||
|
[frontend.db :as db]
|
||||||
|
[frontend.config :as config]
|
||||||
|
[lambdaisland.glogi :as log]))
|
||||||
|
|
||||||
|
(defn remove-ignore-files
|
||||||
|
[files]
|
||||||
|
(let [files (remove (fn [f]
|
||||||
|
(string/starts-with? (:file/path f) ".git/"))
|
||||||
|
files)]
|
||||||
|
(if-let [ignore-file (some #(when (= (:file/name %) ".gitignore")
|
||||||
|
%) files)]
|
||||||
|
(if-let [file (:file/file ignore-file)]
|
||||||
|
(p/let [content (.text file)]
|
||||||
|
(when content
|
||||||
|
(let [paths (set (file-handler/ignore-files content (map :file/path files)))]
|
||||||
|
(when (seq paths)
|
||||||
|
(filter (fn [f] (contains? paths (:file/path f))) files)))))
|
||||||
|
(p/resolved files))
|
||||||
|
(p/resolved files))))
|
||||||
|
|
||||||
|
(defn- ->db-files
|
||||||
|
[dir-name result]
|
||||||
|
(let [result (flatten (bean/->clj result))]
|
||||||
|
(map (fn [file]
|
||||||
|
(let [handle (gobj/get file "handle")
|
||||||
|
get-attr #(gobj/get file %)
|
||||||
|
path (-> (get-attr "webkitRelativePath")
|
||||||
|
(string/replace-first (str dir-name "/") ""))]
|
||||||
|
{:file/name (get-attr "name")
|
||||||
|
:file/path path
|
||||||
|
:file/last-modified-at (get-attr "lastModified")
|
||||||
|
:file/size (get-attr "size")
|
||||||
|
:file/type (get-attr "type")
|
||||||
|
:file/file file
|
||||||
|
:file/handle handle})) result)))
|
||||||
|
|
||||||
|
(defn- filter-markup-and-built-in-files
|
||||||
|
[files]
|
||||||
|
(filter (fn [file]
|
||||||
|
(contains? (set/union config/markup-formats #{:css :edn})
|
||||||
|
(keyword (util/get-file-ext (:file/path file)))))
|
||||||
|
files))
|
||||||
|
|
||||||
|
(defn- set-files!
|
||||||
|
[handles]
|
||||||
|
(doseq [[path handle] handles]
|
||||||
|
(let [handle-path (str config/local-handle-prefix path)]
|
||||||
|
(idb/set-item! handle-path handle)
|
||||||
|
(fs/add-nfs-file-handle! handle-path handle))))
|
||||||
|
|
||||||
|
(defn ls-dir-files
|
||||||
|
[]
|
||||||
|
(let [path-handles (atom {})]
|
||||||
|
(->
|
||||||
|
(p/let [result (utils/openDirectory #js {:recursive true}
|
||||||
|
(fn [path handle]
|
||||||
|
(swap! path-handles assoc path handle)))
|
||||||
|
_ (state/set-loading-files! true)
|
||||||
|
root-handle (nth result 0)
|
||||||
|
dir-name (gobj/get root-handle "name")
|
||||||
|
repo (str config/local-db-prefix dir-name)
|
||||||
|
root-handle-path (str config/local-handle-prefix dir-name)
|
||||||
|
_ (idb/set-item! root-handle-path root-handle)
|
||||||
|
_ (fs/add-nfs-file-handle! root-handle-path root-handle)
|
||||||
|
result (nth result 1)
|
||||||
|
files (-> (->db-files dir-name result)
|
||||||
|
remove-ignore-files)
|
||||||
|
_ (let [file-paths (set (map :file/path files))]
|
||||||
|
(swap! path-handles (fn [handles]
|
||||||
|
(->> handles
|
||||||
|
(filter (fn [[path _handle]]
|
||||||
|
(contains? file-paths
|
||||||
|
(string/replace-first path (str dir-name "/") ""))))
|
||||||
|
(into {})))))
|
||||||
|
_ (set-files! @path-handles)
|
||||||
|
markup-files (filter-markup-and-built-in-files files)]
|
||||||
|
(-> (p/all (map (fn [file]
|
||||||
|
(p/let [content (.text (:file/file file))]
|
||||||
|
(assoc file :file/content content))) markup-files))
|
||||||
|
(p/then (fn [result]
|
||||||
|
_ (state/set-loading-files! false)
|
||||||
|
(let [files (map #(dissoc % :file/file) result)]
|
||||||
|
(repo-handler/start-repo-db-if-not-exists! repo {:db-type :local-native-fs})
|
||||||
|
(repo-handler/load-repo-to-db! repo
|
||||||
|
{:first-clone? true
|
||||||
|
:nfs-files files})
|
||||||
|
|
||||||
|
(state/add-repo! {:url repo :nfs? true}))))
|
||||||
|
(p/catch (fn [error]
|
||||||
|
(log/error :nfs/load-files-error error)))))
|
||||||
|
(p/catch (fn [error]
|
||||||
|
(when (not= "AbortError" (gobj/get error "name"))
|
||||||
|
(log/error :nfs/open-dir-error error)))))))
|
||||||
|
|
||||||
|
(defn open-file-picker
|
||||||
|
"Shows a file picker that lets a user select a single existing file, returning a handle for the selected file. "
|
||||||
|
([]
|
||||||
|
(open-file-picker {}))
|
||||||
|
([option]
|
||||||
|
(.showOpenFilePicker js/window (bean/->js option))))
|
||||||
|
|
||||||
|
(defn get-local-repo
|
||||||
|
[]
|
||||||
|
(when-let [repo (state/get-current-repo)]
|
||||||
|
(when (config/local-db? repo)
|
||||||
|
repo)))
|
||||||
|
|
||||||
|
(defn ask-permission
|
||||||
|
[repo]
|
||||||
|
(fn [close-fn]
|
||||||
|
[:div
|
||||||
|
[:p.text-gray-700
|
||||||
|
"Grant native filesystem permission for directory: "
|
||||||
|
[:b (config/get-local-dir repo)]]
|
||||||
|
(ui/button
|
||||||
|
"Grant"
|
||||||
|
:on-click (fn []
|
||||||
|
(fs/check-directory-permission! repo)
|
||||||
|
(close-fn)))]))
|
||||||
|
|
||||||
|
(defn ask-permission-if-local? []
|
||||||
|
(when-let [repo (get-local-repo)]
|
||||||
|
(state/set-modal! (ask-permission repo))))
|
||||||
|
|
||||||
|
(defn- compute-diffs
|
||||||
|
[old-files new-files]
|
||||||
|
(let [ks [:file/path :file/last-modified-at]
|
||||||
|
->set (fn [files ks]
|
||||||
|
(when (seq files)
|
||||||
|
(->> files
|
||||||
|
(map #(select-keys % ks))
|
||||||
|
set)))
|
||||||
|
old-files (->set old-files ks)
|
||||||
|
new-files (->set new-files ks)
|
||||||
|
file-path-set-f (fn [col] (set (map :file/path col)))
|
||||||
|
get-file-f (fn [files path] (some #(when (= (:file/path %) path) %) files))
|
||||||
|
old-file-paths (file-path-set-f old-files)
|
||||||
|
new-file-paths (file-path-set-f new-files)
|
||||||
|
added (set/difference new-file-paths old-file-paths)
|
||||||
|
deleted (set/difference old-file-paths new-file-paths)
|
||||||
|
modified (set/difference new-file-paths added)]
|
||||||
|
{:added added
|
||||||
|
:modified modified
|
||||||
|
:deleted deleted}))
|
||||||
|
|
||||||
|
(defn- reload-dir!
|
||||||
|
[repo]
|
||||||
|
(when (and repo (config/local-db? repo))
|
||||||
|
(let [old-files (db/get-files-full repo)
|
||||||
|
dir-name (config/get-local-dir repo)
|
||||||
|
handle-path (str config/local-handle-prefix dir-name)
|
||||||
|
path-handles (atom {})]
|
||||||
|
(state/set-graph-syncing? true)
|
||||||
|
(p/let [handle (idb/get-item handle-path)
|
||||||
|
_ (when handle (utils/verifyPermission handle true))
|
||||||
|
files-result (utils/getFiles handle true
|
||||||
|
(fn [path handle]
|
||||||
|
(swap! path-handles assoc path handle)))
|
||||||
|
new-files (-> (->db-files dir-name files-result)
|
||||||
|
remove-ignore-files)
|
||||||
|
_ (let [file-paths (set (map :file/path new-files))]
|
||||||
|
(swap! path-handles (fn [handles]
|
||||||
|
(->> handles
|
||||||
|
(filter (fn [[path _handle]]
|
||||||
|
(contains? file-paths
|
||||||
|
(string/replace-first path (str dir-name "/") ""))))
|
||||||
|
(into {})))))
|
||||||
|
_ (set-files! @path-handles)
|
||||||
|
get-file-f (fn [path files] (some #(when (= (:file/path %) path) %) files))
|
||||||
|
{:keys [added modified deleted] :as diffs} (compute-diffs old-files new-files)
|
||||||
|
;; Use the same labels as isomorphic-git
|
||||||
|
rename-f (fn [typ col] (mapv (fn [file] {:type typ :path file}) col))
|
||||||
|
_ (when (seq deleted)
|
||||||
|
(p/all (map (fn [path]
|
||||||
|
(let [handle-path (str handle-path path)]
|
||||||
|
(idb/remove-item! handle-path)
|
||||||
|
(fs/remove-nfs-file-handle! handle-path))) deleted)))
|
||||||
|
added-or-modified (set (concat added modified))
|
||||||
|
_ (when (seq added-or-modified)
|
||||||
|
(p/all (map (fn [path]
|
||||||
|
(when-let [handle (get @path-handles path)]
|
||||||
|
(idb/set-item! (str handle-path path) handle))) added-or-modified)))]
|
||||||
|
(-> (p/all (map (fn [path]
|
||||||
|
(when-let [file (get-file-f path new-files)]
|
||||||
|
(p/let [content (.text (:file/file file))]
|
||||||
|
(assoc file :file/content content)))) added-or-modified))
|
||||||
|
(p/then (fn [result]
|
||||||
|
(let [files (map #(dissoc % :file/file :file/handle) result)
|
||||||
|
non-modified? (fn [file]
|
||||||
|
(let [content (:file/content file)
|
||||||
|
old-content (:file/content (get-file-f (:file/path file) old-files))]
|
||||||
|
(= content old-content)))
|
||||||
|
non-modified-files (->> (filter non-modified? files)
|
||||||
|
(map :file/path))
|
||||||
|
modified-files (remove non-modified? files)
|
||||||
|
modified (set/difference (set modified) (set non-modified-files))
|
||||||
|
diffs (concat
|
||||||
|
(rename-f "remove" deleted)
|
||||||
|
(rename-f "add" added)
|
||||||
|
(rename-f "modify" modified))]
|
||||||
|
(when (or (and (seq diffs) (seq modified-files))
|
||||||
|
(seq diffs) ; delete
|
||||||
|
)
|
||||||
|
(repo-handler/load-repo-to-db! repo
|
||||||
|
{:diffs diffs
|
||||||
|
:nfs-files modified-files})))))
|
||||||
|
(p/catch (fn [error]
|
||||||
|
(log/error :nfs/load-files-error error)))
|
||||||
|
(p/finally (fn [_]
|
||||||
|
(state/set-graph-syncing? false))))))))
|
||||||
|
|
||||||
|
(defn- refresh!
|
||||||
|
[repo]
|
||||||
|
(when repo
|
||||||
|
(reload-dir! repo)))
|
||||||
|
|
||||||
|
(defn supported?
|
||||||
|
[]
|
||||||
|
(utils/nfsSupported))
|
||||||
@@ -16,15 +16,15 @@
|
|||||||
(when (or (seq repos)
|
(when (or (seq repos)
|
||||||
(seq installation-ids))
|
(seq installation-ids))
|
||||||
(util/post (str config/api "refresh_github_token")
|
(util/post (str config/api "refresh_github_token")
|
||||||
{:installation-ids installation-ids
|
{:installation-ids installation-ids
|
||||||
:repos repos}
|
:repos repos}
|
||||||
(fn [result]
|
(fn [result]
|
||||||
(state/set-github-installation-tokens! result)
|
(state/set-github-installation-tokens! result)
|
||||||
(when ok-handler (ok-handler)))
|
(when ok-handler (ok-handler)))
|
||||||
(fn [error]
|
(fn [error]
|
||||||
(log/error :token/http-request-failed error)
|
(log/error :token/http-request-failed error)
|
||||||
(js/console.dir error)
|
(js/console.dir error)
|
||||||
(when error-handler (error-handler)))))))
|
(when error-handler (error-handler)))))))
|
||||||
|
|
||||||
(defn- get-github-token*
|
(defn- get-github-token*
|
||||||
[repo]
|
[repo]
|
||||||
@@ -47,20 +47,19 @@
|
|||||||
([]
|
([]
|
||||||
(get-github-token (state/get-current-repo)))
|
(get-github-token (state/get-current-repo)))
|
||||||
([repo]
|
([repo]
|
||||||
(js/Promise.
|
(when-not (config/local-db? repo)
|
||||||
(fn [resolve reject]
|
(js/Promise.
|
||||||
(let [{:keys [expired? token exist?]} (get-github-token* repo)
|
(fn [resolve reject]
|
||||||
valid-token? (and exist? (not expired?))]
|
(let [{:keys [expired? token exist?]} (get-github-token* repo)
|
||||||
(if valid-token?
|
valid-token? (and exist? (not expired?))]
|
||||||
(resolve token)
|
(if valid-token?
|
||||||
(request-app-tokens!
|
(resolve token)
|
||||||
(fn []
|
(request-app-tokens!
|
||||||
(let [{:keys [expired? token exist?] :as token-m} (get-github-token* repo)
|
(fn []
|
||||||
valid-token? (and exist? (not expired?))]
|
(let [{:keys [expired? token exist?] :as token-m} (get-github-token* repo)
|
||||||
(if valid-token?
|
valid-token? (and exist? (not expired?))]
|
||||||
(resolve token)
|
(if valid-token?
|
||||||
(do (log/error :token/failed-get-token token-m)
|
(resolve token)
|
||||||
(reject)))))
|
(do (log/error :token/failed-get-token token-m)
|
||||||
nil)))))))
|
(reject)))))
|
||||||
|
nil))))))))
|
||||||
|
|
||||||
|
|||||||
52
src/main/frontend/idb.cljs
Normal file
52
src/main/frontend/idb.cljs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
(ns frontend.idb
|
||||||
|
(:require ["localforage" :as localforage]
|
||||||
|
[cljs-bean.core :as bean]
|
||||||
|
[goog.object :as gobj]
|
||||||
|
[promesa.core :as p]
|
||||||
|
[clojure.string :as string]
|
||||||
|
[frontend.config :as config]
|
||||||
|
[frontend.storage :as storage]))
|
||||||
|
|
||||||
|
;; offline db
|
||||||
|
(def store-name "dbs")
|
||||||
|
(.config localforage
|
||||||
|
(bean/->js
|
||||||
|
{:name "logseq-datascript"
|
||||||
|
:version 1.0
|
||||||
|
:storeName store-name}))
|
||||||
|
|
||||||
|
(defonce localforage-instance (.createInstance localforage store-name))
|
||||||
|
|
||||||
|
(defn clear-idb!
|
||||||
|
[]
|
||||||
|
(p/let [_ (.clear localforage-instance)
|
||||||
|
dbs (js/window.indexedDB.databases)]
|
||||||
|
(doseq [db dbs]
|
||||||
|
(js/window.indexedDB.deleteDatabase (gobj/get db "name")))))
|
||||||
|
|
||||||
|
(defn clear-local-storage-and-idb!
|
||||||
|
[]
|
||||||
|
(storage/clear)
|
||||||
|
(clear-idb!))
|
||||||
|
|
||||||
|
(defn remove-item!
|
||||||
|
[key]
|
||||||
|
(.removeItem localforage-instance key))
|
||||||
|
|
||||||
|
(defn set-item!
|
||||||
|
[key value]
|
||||||
|
(.setItem localforage-instance key value))
|
||||||
|
|
||||||
|
(defn get-item
|
||||||
|
[key]
|
||||||
|
(.getItem localforage-instance key))
|
||||||
|
|
||||||
|
(defn get-keys
|
||||||
|
[]
|
||||||
|
(.keys localforage-instance))
|
||||||
|
|
||||||
|
(defn get-nfs-dbs
|
||||||
|
[]
|
||||||
|
(p/let [ks (get-keys)]
|
||||||
|
(->> (filter (fn [k] (string/starts-with? k (str config/idb-db-prefix config/local-db-prefix))) ks)
|
||||||
|
(map #(string/replace-first % config/idb-db-prefix "")))))
|
||||||
@@ -61,16 +61,16 @@
|
|||||||
;; If the click target is outside of current node
|
;; If the click target is outside of current node
|
||||||
(when-not (dom/contains dom-node (.. e -target))
|
(when-not (dom/contains dom-node (.. e -target))
|
||||||
(on-hide state e :click))))
|
(on-hide state e :click))))
|
||||||
(when visibilitychange?
|
(listen state js/window "keydown"
|
||||||
(listen state js/window "visibilitychange"
|
|
||||||
(fn [e]
|
|
||||||
(on-hide state e :visibilitychange))))
|
|
||||||
(listen state dom-node "keydown"
|
|
||||||
(fn [e]
|
(fn [e]
|
||||||
(case (.-keyCode e)
|
(case (.-keyCode e)
|
||||||
;; Esc
|
;; Esc
|
||||||
27 (on-hide state e :esc)
|
27 (on-hide state e :esc)
|
||||||
nil)))))
|
nil)))
|
||||||
|
(when visibilitychange?
|
||||||
|
(listen state js/window "visibilitychange"
|
||||||
|
(fn [e]
|
||||||
|
(on-hide state e :visibilitychange))))))
|
||||||
(catch js/Error e
|
(catch js/Error e
|
||||||
;; TODO: Unable to find node on an unmounted component.
|
;; TODO: Unable to find node on an unmounted component.
|
||||||
nil)))
|
nil)))
|
||||||
@@ -130,11 +130,7 @@
|
|||||||
:did-remount (fn [old-state new-state]
|
:did-remount (fn [old-state new-state]
|
||||||
(detach old-state)
|
(detach old-state)
|
||||||
(attach-listeners new-state)
|
(attach-listeners new-state)
|
||||||
new-state)
|
new-state)})))
|
||||||
;; :will-unmount (fn [state]
|
|
||||||
;; (detach state)
|
|
||||||
;; state)
|
|
||||||
})))
|
|
||||||
|
|
||||||
(defn modal
|
(defn modal
|
||||||
[k]
|
[k]
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
(atom
|
(atom
|
||||||
{:route-match nil
|
{:route-match nil
|
||||||
:today nil
|
:today nil
|
||||||
:daily/migrating? nil
|
|
||||||
:db/batch-txs (async/chan 100)
|
:db/batch-txs (async/chan 100)
|
||||||
:notification/show? false
|
:notification/show? false
|
||||||
:notification/content nil
|
:notification/content nil
|
||||||
@@ -25,14 +24,10 @@
|
|||||||
:repo/importing-to-db? nil
|
:repo/importing-to-db? nil
|
||||||
:repo/sync-status {}
|
:repo/sync-status {}
|
||||||
:repo/changed-files nil
|
:repo/changed-files nil
|
||||||
|
:nfs/loading-files? nil
|
||||||
;; TODO: how to detect the network reliably?
|
;; TODO: how to detect the network reliably?
|
||||||
:network/online? true
|
:network/online? true
|
||||||
:indexeddb/support? true
|
:indexeddb/support? true
|
||||||
;; TODO: save in local storage so that if :changed? is true when user
|
|
||||||
;; reloads the browser, the app should re-index the repo (another way
|
|
||||||
;; is to save all the tx data since :last-stored-at)
|
|
||||||
;; repo -> {:last-stored-at :last-modified-at}
|
|
||||||
:repo/persist-status {}
|
|
||||||
:me nil
|
:me nil
|
||||||
:git/current-repo (storage/get :git/current-repo)
|
:git/current-repo (storage/get :git/current-repo)
|
||||||
:git/status {}
|
:git/status {}
|
||||||
@@ -78,6 +73,12 @@
|
|||||||
:editor/block nil
|
:editor/block nil
|
||||||
:editor/block-dom-id nil
|
:editor/block-dom-id nil
|
||||||
:editor/set-timestamp-block nil
|
:editor/set-timestamp-block nil
|
||||||
|
:editor/last-input-time nil
|
||||||
|
:db/last-transact-time {}
|
||||||
|
:db/last-persist-transact-ids {}
|
||||||
|
;; whether database is persisted
|
||||||
|
:db/persisted? {}
|
||||||
|
:db/latest-txs (or (storage/get-transit :db/latest-txs) {})
|
||||||
:cursor-range nil
|
:cursor-range nil
|
||||||
|
|
||||||
:selection/mode false
|
:selection/mode false
|
||||||
@@ -93,7 +94,8 @@
|
|||||||
:preferred-language (storage/get :preferred-language)
|
:preferred-language (storage/get :preferred-language)
|
||||||
|
|
||||||
;; all notification contents as k-v pairs
|
;; all notification contents as k-v pairs
|
||||||
:notification/contents {}}))
|
:notification/contents {}
|
||||||
|
:graph/syncing? false}))
|
||||||
|
|
||||||
(defn get-route-match
|
(defn get-route-match
|
||||||
[]
|
[]
|
||||||
@@ -194,8 +196,10 @@
|
|||||||
|
|
||||||
(defn get-pages-directory
|
(defn get-pages-directory
|
||||||
[]
|
[]
|
||||||
(when-let [repo (get-current-repo)]
|
(or
|
||||||
(:pages-directory (get-config repo))))
|
(when-let [repo (get-current-repo)]
|
||||||
|
(:pages-directory (get-config repo)))
|
||||||
|
"pages"))
|
||||||
|
|
||||||
(defn org-mode-file-link?
|
(defn org-mode-file-link?
|
||||||
[repo]
|
[repo]
|
||||||
@@ -237,6 +241,18 @@
|
|||||||
[]
|
[]
|
||||||
(get-in @state [:me :repos]))
|
(get-in @state [:me :repos]))
|
||||||
|
|
||||||
|
(defn set-repos!
|
||||||
|
[repos]
|
||||||
|
(set-state! [:me :repos] repos))
|
||||||
|
|
||||||
|
(defn add-repo!
|
||||||
|
[repo]
|
||||||
|
(when repo
|
||||||
|
(update-state! [:me :repos]
|
||||||
|
(fn [repos]
|
||||||
|
(->> (conj repos repo)
|
||||||
|
(distinct))))))
|
||||||
|
|
||||||
(defn set-current-repo!
|
(defn set-current-repo!
|
||||||
[repo]
|
[repo]
|
||||||
(swap! state assoc :git/current-repo repo)
|
(swap! state assoc :git/current-repo repo)
|
||||||
@@ -778,18 +794,6 @@
|
|||||||
:modal/show? false
|
:modal/show? false
|
||||||
:modal/panel-content nil))
|
:modal/panel-content nil))
|
||||||
|
|
||||||
(defn update-repo-last-stored-at!
|
|
||||||
[repo]
|
|
||||||
(swap! state assoc-in [:repo/persist-status repo :last-stored-at] (util/time-ms)))
|
|
||||||
|
|
||||||
(defn get-repo-persist-status
|
|
||||||
[]
|
|
||||||
(:repo/persist-status @state))
|
|
||||||
|
|
||||||
(defn mark-repo-as-changed!
|
|
||||||
[repo _tx-id]
|
|
||||||
(swap! state assoc-in [:repo/persist-status repo :last-modified-at] (util/time-ms)))
|
|
||||||
|
|
||||||
(defn get-db-batch-txs-chan
|
(defn get-db-batch-txs-chan
|
||||||
[]
|
[]
|
||||||
(:db/batch-txs @state))
|
(:db/batch-txs @state))
|
||||||
@@ -801,13 +805,6 @@
|
|||||||
(when-let [chan (get-db-batch-txs-chan)]
|
(when-let [chan (get-db-batch-txs-chan)]
|
||||||
(async/put! chan f))))
|
(async/put! chan f))))
|
||||||
|
|
||||||
(defn repos-need-to-be-stored?
|
|
||||||
[]
|
|
||||||
(let [status (vals (get-repo-persist-status))]
|
|
||||||
(some (fn [{:keys [last-stored-at last-modified-at]}]
|
|
||||||
(> last-modified-at last-stored-at))
|
|
||||||
status)))
|
|
||||||
|
|
||||||
(defn get-left-sidebar-open?
|
(defn get-left-sidebar-open?
|
||||||
[]
|
[]
|
||||||
(get-in @state [:ui/left-sidebar-open?]))
|
(get-in @state [:ui/left-sidebar-open?]))
|
||||||
@@ -816,10 +813,6 @@
|
|||||||
[value]
|
[value]
|
||||||
(set-state! :ui/left-sidebar-open? value))
|
(set-state! :ui/left-sidebar-open? value))
|
||||||
|
|
||||||
(defn set-daily-migrating!
|
|
||||||
[value]
|
|
||||||
(set-state! :daily/migrating? value))
|
|
||||||
|
|
||||||
(defn set-developer-mode!
|
(defn set-developer-mode!
|
||||||
[value]
|
[value]
|
||||||
(set-state! :ui/developer-mode? value)
|
(set-state! :ui/developer-mode? value)
|
||||||
@@ -886,7 +879,80 @@
|
|||||||
[]
|
[]
|
||||||
(:commands (get-config)))
|
(:commands (get-config)))
|
||||||
|
|
||||||
|
(defn set-graph-syncing?
|
||||||
|
[value]
|
||||||
|
(set-state! :graph/syncing? value))
|
||||||
|
|
||||||
|
(defn set-loading-files!
|
||||||
|
[value]
|
||||||
|
(set-state! :repo/loading-files? value))
|
||||||
|
|
||||||
|
(defn set-importing-to-db!
|
||||||
|
[value]
|
||||||
|
(set-state! :repo/importing-to-db? value))
|
||||||
|
|
||||||
|
(defn set-editor-last-input-time!
|
||||||
|
[repo time]
|
||||||
|
(swap! state assoc-in [:editor/last-input-time repo] time))
|
||||||
|
|
||||||
|
(defn set-last-transact-time!
|
||||||
|
[repo time]
|
||||||
|
(swap! state assoc-in [:db/last-transact-time repo] time)
|
||||||
|
|
||||||
|
;; THINK: new block, indent/outdent, drag && drop, etc.
|
||||||
|
(set-editor-last-input-time! repo time))
|
||||||
|
|
||||||
|
(defn set-db-persisted!
|
||||||
|
[repo value]
|
||||||
|
(swap! state assoc-in [:db/persisted? repo] value))
|
||||||
|
|
||||||
|
(defn db-idle?
|
||||||
|
[repo]
|
||||||
|
(when repo
|
||||||
|
(when-let [last-time (get-in @state [:db/last-transact-time repo])]
|
||||||
|
(let [now (util/time-ms)]
|
||||||
|
(>= (- now last-time) 3000)))))
|
||||||
|
|
||||||
|
(defn input-idle?
|
||||||
|
[repo]
|
||||||
|
(when repo
|
||||||
|
(or
|
||||||
|
(when-let [last-time (get-in @state [:editor/last-input-time repo])]
|
||||||
|
(let [now (util/time-ms)]
|
||||||
|
(>= (- now last-time) 3000)))
|
||||||
|
;; not in editing mode
|
||||||
|
(not (get-edit-input-id)))))
|
||||||
|
|
||||||
|
(defn set-last-persist-transact-id!
|
||||||
|
[repo files? id]
|
||||||
|
(swap! state assoc-in [:db/last-persist-transact-ids :repo files?] id))
|
||||||
|
|
||||||
|
(defn get-last-persist-transact-id
|
||||||
|
[repo files?]
|
||||||
|
(get-in @state [:db/last-persist-transact-ids :repo files?]))
|
||||||
|
|
||||||
|
(defn persist-transaction!
|
||||||
|
[repo files? tx-id tx-data]
|
||||||
|
(when (seq tx-data)
|
||||||
|
(let [latest-txs (:db/latest-txs @state)
|
||||||
|
last-persist-tx-id (get-last-persist-transact-id repo files?)
|
||||||
|
latest-txs (if last-persist-tx-id
|
||||||
|
(update-in latest-txs [repo files?]
|
||||||
|
(fn [result]
|
||||||
|
(remove (fn [tx] (<= (:tx-id tx) last-persist-tx-id)) result)))
|
||||||
|
latest-txs)
|
||||||
|
new-txs (update-in latest-txs [repo files?] (fn [result]
|
||||||
|
(vec (conj result {:tx-id tx-id
|
||||||
|
:tx-data tx-data}))))]
|
||||||
|
(storage/set-transit! :db/latest-txs new-txs)
|
||||||
|
(set-state! :db/latest-txs new-txs))))
|
||||||
|
|
||||||
|
(defn get-repo-latest-txs
|
||||||
|
[repo file?]
|
||||||
|
(get-in (:db/latest-txs @state) [repo file?]))
|
||||||
|
|
||||||
;; TODO: Move those to the uni `state`
|
;; TODO: Move those to the uni `state`
|
||||||
|
|
||||||
(defonce editor-op (atom nil))
|
(defonce editor-op (atom nil))
|
||||||
(defn set-editor-op!
|
(defn set-editor-op!
|
||||||
[value]
|
[value]
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
(ns frontend.storage
|
(ns frontend.storage
|
||||||
(:refer-clojure :exclude [get set remove])
|
(:refer-clojure :exclude [get set remove])
|
||||||
(:require [cljs.reader :as reader]))
|
(:require [cljs.reader :as reader]
|
||||||
|
[datascript.transit :as dt]))
|
||||||
|
|
||||||
;; TODO: deprecate this, will persistent datascript
|
|
||||||
(defn get
|
(defn get
|
||||||
[key]
|
[key]
|
||||||
(reader/read-string ^js (.getItem js/localStorage (name key))))
|
(reader/read-string ^js (.getItem js/localStorage (name key))))
|
||||||
@@ -11,6 +11,14 @@
|
|||||||
[key value]
|
[key value]
|
||||||
(.setItem ^js js/localStorage (name key) (pr-str value)))
|
(.setItem ^js js/localStorage (name key) (pr-str value)))
|
||||||
|
|
||||||
|
(defn get-transit
|
||||||
|
[key]
|
||||||
|
(dt/read-transit-str ^js (.getItem js/localStorage (name key))))
|
||||||
|
|
||||||
|
(defn set-transit!
|
||||||
|
[key value]
|
||||||
|
(.setItem ^js js/localStorage (name key) (dt/write-transit-str value)))
|
||||||
|
|
||||||
(defn get-json
|
(defn get-json
|
||||||
[key]
|
[key]
|
||||||
(when-let [value (.getItem js/localStorage (name key))]
|
(when-let [value (.getItem js/localStorage (name key))]
|
||||||
|
|||||||
@@ -384,11 +384,16 @@
|
|||||||
:aria-hidden "true"}]]])
|
:aria-hidden "true"}]]])
|
||||||
|
|
||||||
(defn tooltip
|
(defn tooltip
|
||||||
[label children]
|
([label children]
|
||||||
[:div.Tooltip {:style {:display "inline"}}
|
(tooltip label children {}))
|
||||||
[:div {:class "Tooltip__label"}
|
([label children {:keys [label-style]}]
|
||||||
label]
|
[:div.Tooltip {:style {:display "inline"}}
|
||||||
children])
|
[:div (cond->
|
||||||
|
{:class "Tooltip__label"}
|
||||||
|
label-style
|
||||||
|
(assoc :style label-style))
|
||||||
|
label]
|
||||||
|
children]))
|
||||||
|
|
||||||
(defonce modal-show? (atom false))
|
(defonce modal-show? (atom false))
|
||||||
(rum/defc modal-overlay
|
(rum/defc modal-overlay
|
||||||
|
|||||||
@@ -19,6 +19,11 @@
|
|||||||
[clojure.pprint :refer [pprint]]
|
[clojure.pprint :refer [pprint]]
|
||||||
[goog.userAgent]))
|
[goog.userAgent]))
|
||||||
|
|
||||||
|
(extend-protocol IPrintWithWriter
|
||||||
|
js/Symbol
|
||||||
|
(-pr-writer [sym writer _]
|
||||||
|
(-write writer (str "\"" (.toString sym) "\""))))
|
||||||
|
|
||||||
;; envs
|
;; envs
|
||||||
(defn ios?
|
(defn ios?
|
||||||
[]
|
[]
|
||||||
@@ -946,6 +951,14 @@
|
|||||||
[file]
|
[file]
|
||||||
(last (string/split file #"\.")))
|
(last (string/split file #"\.")))
|
||||||
|
|
||||||
|
(defn get-dir-and-basename
|
||||||
|
[path]
|
||||||
|
(let [parts (string/split path "/")
|
||||||
|
basename (last parts)
|
||||||
|
dir (->> (butlast parts)
|
||||||
|
(string/join "/"))]
|
||||||
|
[dir basename]))
|
||||||
|
|
||||||
(defn get-relative-path
|
(defn get-relative-path
|
||||||
[current-file-path another-file-path]
|
[current-file-path another-file-path]
|
||||||
(let [directories-f #(butlast (string/split % "/"))
|
(let [directories-f #(butlast (string/split % "/"))
|
||||||
|
|||||||
@@ -74,7 +74,83 @@ export var getSelectionText = function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ''
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified from https://github.com/GoogleChromeLabs/browser-nativefs
|
||||||
|
// because shadow-cljs doesn't handle this babel transform
|
||||||
|
export var getFiles = async function (dirHandle, recursive, cb, path = dirHandle.name) {
|
||||||
|
const dirs = [];
|
||||||
|
const files = [];
|
||||||
|
for await (const entry of dirHandle.values()) {
|
||||||
|
const nestedPath = `${path}/${entry.name}`;
|
||||||
|
if (entry.kind === 'file') {
|
||||||
|
cb(nestedPath, entry);
|
||||||
|
files.push(
|
||||||
|
entry.getFile().then((file) => {
|
||||||
|
Object.defineProperty(file, 'webkitRelativePath', {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
get: () => nestedPath,
|
||||||
|
});
|
||||||
|
Object.defineProperty(file, 'handle', {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
get: () => entry,
|
||||||
|
});
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (entry.kind === 'directory' && recursive) {
|
||||||
|
cb(nestedPath, entry);
|
||||||
|
dirs.push(getFiles(entry, recursive, cb, nestedPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [(await Promise.all(dirs)), (await Promise.all(files))];
|
||||||
|
};
|
||||||
|
|
||||||
|
export var verifyPermission = async function (handle, readWrite) {
|
||||||
|
const options = {};
|
||||||
|
if (readWrite) {
|
||||||
|
options.mode = 'readwrite';
|
||||||
|
}
|
||||||
|
// Check if permission was already granted. If so, return true.
|
||||||
|
if ((await handle.queryPermission(options)) === 'granted') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Request permission. If the user grants permission, return true.
|
||||||
|
if ((await handle.requestPermission(options)) === 'granted') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// The user didn't grant permission, so return false.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export var openDirectory = async function (options = {}, cb) {
|
||||||
|
options.recursive = options.recursive || false;
|
||||||
|
const handle = await window.showDirectoryPicker({ mode: 'readwrite' });
|
||||||
|
const _ask = await verifyPermission(handle, true);
|
||||||
|
return [handle, getFiles(handle, options.recursive, cb)];
|
||||||
|
};
|
||||||
|
|
||||||
|
export var writeFile = async function (fileHandle, contents) {
|
||||||
|
// Create a FileSystemWritableFileStream to write to.
|
||||||
|
const writable = await fileHandle.createWritable();
|
||||||
|
// Write the contents of the file to the stream.
|
||||||
|
await writable.write(contents);
|
||||||
|
// Close the file and write the contents to disk.
|
||||||
|
await writable.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
export var nfsSupported = function () {
|
||||||
|
if ('chooseFileSystemEntries' in self) {
|
||||||
|
return 'chooseFileSystemEntries';
|
||||||
|
} else if ('showOpenFilePicker' in self) {
|
||||||
|
return 'showOpenFilePicker';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputTypes = [
|
const inputTypes = [
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
(ns frontend.version)
|
(ns frontend.version)
|
||||||
|
|
||||||
(defonce version "0.0.4.7-3")
|
(defonce version "0.0.4.8")
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
@echo off
|
|
||||||
SET ENVIRONMENT=dev
|
|
||||||
SET JWT_SECRET=4fa183cf1d28460498b13330835e80ad
|
|
||||||
SET COOKIE_SECRET=10a42ca724e34f4db6086a772d787034
|
|
||||||
SET DATABASE_URL=postgres://localhost:5432/logseq
|
|
||||||
SET GITHUB_APP2_ID=78728
|
|
||||||
SET GITHUB_APP2_KEY=xxxxxxxxxxxxxxxxxxxx
|
|
||||||
SET GITHUB_APP2_SECRET=xxxxxxxxxxxxxxxxxxxx
|
|
||||||
SET GITHUB_APP_PEM=
|
|
||||||
SET LOG_PATH=%AppData%\..\Local\Temp\logseq
|
|
||||||
|
|
||||||
pg_ctl start
|
|
||||||
start cmd.exe /k "java -Duser.timezone=UTC -jar logseq.jar"
|
|
||||||
yarn && yarn watch
|
|
||||||
Reference in New Issue
Block a user