mirror of
https://github.com/zadam/trilium.git
synced 2025-11-01 10:55:55 +01:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25ce2e4253 | ||
|
|
c27f573eed | ||
|
|
1d99c4e80b | ||
|
|
dc7c64a94d | ||
|
|
dc6a530d8c | ||
|
|
a674a12706 | ||
|
|
c29f1af48f | ||
|
|
a8d72c46e4 | ||
|
|
ec36fbd83e | ||
|
|
aedb05cbab | ||
|
|
bb16840a72 | ||
|
|
7b5d44a329 | ||
|
|
a53a65be1f | ||
|
|
44af431a93 | ||
|
|
caa11b8f7e | ||
|
|
41cce4dcb9 | ||
|
|
858072cc10 | ||
|
|
855c5e0e67 | ||
|
|
f0cc3d0bcd | ||
|
|
7672f22ce0 | ||
|
|
ef37a52a06 | ||
|
|
2318d615bb | ||
|
|
bc14c3d665 | ||
|
|
b89ea9a684 | ||
|
|
496767a52b | ||
|
|
9139c597e5 | ||
|
|
942132c01d | ||
|
|
1862acd1ff | ||
|
|
ce7e18d0b0 | ||
|
|
65280d5ba3 | ||
|
|
7e3d424e23 | ||
|
|
bff04c121a | ||
|
|
e8903e82a1 | ||
|
|
0cfd95d9b8 | ||
|
|
1aa5349628 | ||
|
|
4e21d12202 |
1
.idea/vcs.xml
generated
1
.idea/vcs.xml
generated
@@ -2,6 +2,5 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -10,7 +10,7 @@ fi
|
|||||||
|
|
||||||
cd dist
|
cd dist
|
||||||
wget https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz
|
wget https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz
|
||||||
tar xvfJ node-v${NODE_VERSION}-linux-x64.tar.xz
|
tar xfJ node-v${NODE_VERSION}-linux-x64.tar.xz
|
||||||
rm node-v${NODE_VERSION}-linux-x64.tar.xz
|
rm node-v${NODE_VERSION}-linux-x64.tar.xz
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ if [[ $# -eq 0 ]] ; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
npm run webpack
|
n exec 12 npm run webpack
|
||||||
|
|
||||||
DIR=$1
|
DIR=$1
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ cp -r electron.js $DIR/
|
|||||||
cp webpack-* $DIR/
|
cp webpack-* $DIR/
|
||||||
|
|
||||||
# run in subshell (so we return to original dir)
|
# run in subshell (so we return to original dir)
|
||||||
(cd $DIR && npm install --only=prod)
|
(cd $DIR && n exec 12 npm install --only=prod)
|
||||||
|
|
||||||
# cleanup of useless files in dependencies
|
# cleanup of useless files in dependencies
|
||||||
rm -r $DIR/node_modules/image-q/demo
|
rm -r $DIR/node_modules/image-q/demo
|
||||||
|
|||||||
@@ -55,47 +55,20 @@ echo "Creating release in GitHub"
|
|||||||
EXTRA=
|
EXTRA=
|
||||||
|
|
||||||
if [[ $TAG == *"beta"* ]]; then
|
if [[ $TAG == *"beta"* ]]; then
|
||||||
EXTRA=--pre-release
|
EXTRA=--prerelease
|
||||||
fi
|
fi
|
||||||
|
|
||||||
github-release release \
|
echo "$GITHUB_CLI_AUTH_TOKEN" | gh auth login --with-token
|
||||||
--tag $TAG \
|
|
||||||
--name "$TAG release" $EXTRA
|
|
||||||
|
|
||||||
echo "Uploading debian x64 package"
|
gh release create "$TAG" \
|
||||||
|
--title "$TAG release" \
|
||||||
github-release upload \
|
--notes "" \
|
||||||
--tag $TAG \
|
$EXTRA \
|
||||||
--name "$DEBIAN_X64_BUILD" \
|
"dist/$DEBIAN_X64_BUILD" \
|
||||||
--file "dist/$DEBIAN_X64_BUILD"
|
"dist/$LINUX_X64_BUILD" \
|
||||||
|
"dist/$WINDOWS_X64_BUILD" \
|
||||||
echo "Uploading linux x64 build"
|
"dist/$MAC_X64_BUILD" \
|
||||||
|
"dist/$SERVER_BUILD"
|
||||||
github-release upload \
|
|
||||||
--tag $TAG \
|
|
||||||
--name "$LINUX_X64_BUILD" \
|
|
||||||
--file "dist/$LINUX_X64_BUILD"
|
|
||||||
|
|
||||||
echo "Uploading windows x64 build"
|
|
||||||
|
|
||||||
github-release upload \
|
|
||||||
--tag $TAG \
|
|
||||||
--name "$WINDOWS_X64_BUILD" \
|
|
||||||
--file "dist/$WINDOWS_X64_BUILD"
|
|
||||||
|
|
||||||
echo "Uploading mac x64 build"
|
|
||||||
|
|
||||||
github-release upload \
|
|
||||||
--tag $TAG \
|
|
||||||
--name "$MAC_X64_BUILD" \
|
|
||||||
--file "dist/$MAC_X64_BUILD"
|
|
||||||
|
|
||||||
echo "Uploading linux x64 server build"
|
|
||||||
|
|
||||||
github-release upload \
|
|
||||||
--tag $TAG \
|
|
||||||
--name "$SERVER_BUILD" \
|
|
||||||
--file "dist/$SERVER_BUILD"
|
|
||||||
|
|
||||||
echo "Building docker image"
|
echo "Building docker image"
|
||||||
|
|
||||||
|
|||||||
BIN
db/demo.zip
BIN
db/demo.zip
Binary file not shown.
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"version": "0.46.4-beta",
|
"version": "0.46.7",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"productName": "Trilium Notes",
|
"productName": "Trilium Notes",
|
||||||
"description": "Trilium Notes",
|
"description": "Trilium Notes",
|
||||||
"version": "0.46.5",
|
"version": "0.46.9",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start-server": "cross-env TRILIUM_ENV=dev node ./src/www",
|
"start-server": "cross-env TRILIUM_ENV=dev node ./src/www",
|
||||||
"start-electron": "cross-env TRILIUM_ENV=dev electron .",
|
"start-electron": "cross-env TRILIUM_ENV=dev electron --inspect=5858 .",
|
||||||
"build-backend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js",
|
"build-backend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js",
|
||||||
"build-frontend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/collapsible_widget.js",
|
"build-frontend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/collapsible_widget.js",
|
||||||
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
|
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
|
||||||
@@ -51,10 +51,11 @@
|
|||||||
"is-animated": "^2.0.1",
|
"is-animated": "^2.0.1",
|
||||||
"is-svg": "4.2.1",
|
"is-svg": "4.2.1",
|
||||||
"jimp": "0.16.1",
|
"jimp": "0.16.1",
|
||||||
|
"joplin-turndown-plugin-gfm": "1.0.12",
|
||||||
"jsdom": "16.5.0",
|
"jsdom": "16.5.0",
|
||||||
"mime-types": "2.1.29",
|
"mime-types": "2.1.29",
|
||||||
"multer": "1.4.2",
|
"multer": "1.4.2",
|
||||||
"node-abi": "2.21.0",
|
"node-abi": "2.26.0",
|
||||||
"open": "7.4.2",
|
"open": "7.4.2",
|
||||||
"portscanner": "2.2.0",
|
"portscanner": "2.2.0",
|
||||||
"rand-token": "1.0.1",
|
"rand-token": "1.0.1",
|
||||||
@@ -70,7 +71,6 @@
|
|||||||
"striptags": "3.1.1",
|
"striptags": "3.1.1",
|
||||||
"tmp": "^0.2.1",
|
"tmp": "^0.2.1",
|
||||||
"turndown": "7.0.0",
|
"turndown": "7.0.0",
|
||||||
"joplin-turndown-plugin-gfm": "1.0.12",
|
|
||||||
"unescape": "1.0.1",
|
"unescape": "1.0.1",
|
||||||
"ws": "7.4.4",
|
"ws": "7.4.4",
|
||||||
"yauzl": "2.10.0",
|
"yauzl": "2.10.0",
|
||||||
|
|||||||
@@ -87,14 +87,16 @@ describe("Lexer expression", () => {
|
|||||||
.toEqual(["#label", "*=*", "text"]);
|
.toEqual(["#label", "*=*", "text"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("simple label operator with in quotes and without", () => {
|
it("simple label operator with in quotes", () => {
|
||||||
expect(lex("#label*=*'text'").expressionTokens)
|
expect(lex("#label*=*'text'").expressionTokens)
|
||||||
.toEqual([
|
.toEqual([
|
||||||
{token: "#label", inQuotes: false, startIndex: 0, endIndex: 5},
|
{token: "#label", inQuotes: false, startIndex: 0, endIndex: 5},
|
||||||
{token: "*=*", inQuotes: false, startIndex: 6, endIndex: 8},
|
{token: "*=*", inQuotes: false, startIndex: 6, endIndex: 8},
|
||||||
{token: "text", inQuotes: true, startIndex: 10, endIndex: 13}
|
{token: "text", inQuotes: true, startIndex: 10, endIndex: 13}
|
||||||
]);
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("simple label operator with param without quotes", () => {
|
||||||
expect(lex("#label*=*text").expressionTokens)
|
expect(lex("#label*=*text").expressionTokens)
|
||||||
.toEqual([
|
.toEqual([
|
||||||
{token: "#label", inQuotes: false, startIndex: 0, endIndex: 5},
|
{token: "#label", inQuotes: false, startIndex: 0, endIndex: 5},
|
||||||
@@ -103,6 +105,16 @@ describe("Lexer expression", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("simple label operator with empty string param", () => {
|
||||||
|
expect(lex("#label = ''").expressionTokens)
|
||||||
|
.toEqual([
|
||||||
|
{token: "#label", inQuotes: false, startIndex: 0, endIndex: 5},
|
||||||
|
{token: "=", inQuotes: false, startIndex: 7, endIndex: 7},
|
||||||
|
// weird case for empty strings which ends up with endIndex < startIndex :-(
|
||||||
|
{token: "", inQuotes: true, startIndex: 10, endIndex: 9}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it("note. prefix also separates fulltext from expression", () => {
|
it("note. prefix also separates fulltext from expression", () => {
|
||||||
expect(lex(`hello fulltext note.labels.capital = Prague`).expressionTokens.map(t => t.token))
|
expect(lex(`hello fulltext note.labels.capital = Prague`).expressionTokens.map(t => t.token))
|
||||||
.toEqual(["note", ".", "labels", ".", "capital", "=", "prague"]);
|
.toEqual(["note", ".", "labels", ".", "capital", "=", "prague"]);
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ function tokens(toks, cur = 0) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertIsArchived(exp) {
|
||||||
|
expect(exp.constructor.name).toEqual("PropertyComparisonExp");
|
||||||
|
expect(exp.propertyName).toEqual("isArchived");
|
||||||
|
expect(exp.operator).toEqual("=");
|
||||||
|
expect(exp.comparedValue).toEqual("false");
|
||||||
|
}
|
||||||
|
|
||||||
describe("Parser", () => {
|
describe("Parser", () => {
|
||||||
it("fulltext parser without content", () => {
|
it("fulltext parser without content", () => {
|
||||||
const rootExp = parse({
|
const rootExp = parse({
|
||||||
@@ -29,8 +36,9 @@ describe("Parser", () => {
|
|||||||
|
|
||||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||||
expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp");
|
expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp");
|
||||||
expect(rootExp.subExpressions[1].constructor.name).toEqual("NoteCacheFlatTextExp");
|
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
|
||||||
expect(rootExp.subExpressions[1].tokens).toEqual(["hello", "hi"]);
|
expect(rootExp.subExpressions[1].subExpressions[0].constructor.name).toEqual("NoteCacheFlatTextExp");
|
||||||
|
expect(rootExp.subExpressions[1].subExpressions[0].tokens).toEqual(["hello", "hi"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("fulltext parser with content", () => {
|
it("fulltext parser with content", () => {
|
||||||
@@ -40,9 +48,12 @@ describe("Parser", () => {
|
|||||||
searchContext: new SearchContext({includeNoteContent: true})
|
searchContext: new SearchContext({includeNoteContent: true})
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(rootExp.constructor.name).toEqual("OrExp");
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||||
|
assertIsArchived(rootExp.subExpressions[0]);
|
||||||
|
|
||||||
const subs = rootExp.subExpressions;
|
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
|
||||||
|
|
||||||
|
const subs = rootExp.subExpressions[1].subExpressions;
|
||||||
|
|
||||||
expect(subs[0].constructor.name).toEqual("NoteCacheFlatTextExp");
|
expect(subs[0].constructor.name).toEqual("NoteCacheFlatTextExp");
|
||||||
expect(subs[0].tokens).toEqual(["hello", "hi"]);
|
expect(subs[0].tokens).toEqual(["hello", "hi"]);
|
||||||
@@ -61,10 +72,12 @@ describe("Parser", () => {
|
|||||||
searchContext: new SearchContext()
|
searchContext: new SearchContext()
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(rootExp.constructor.name).toEqual("LabelComparisonExp");
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||||
expect(rootExp.attributeType).toEqual("label");
|
assertIsArchived(rootExp.subExpressions[0]);
|
||||||
expect(rootExp.attributeName).toEqual("mylabel");
|
expect(rootExp.subExpressions[1].constructor.name).toEqual("LabelComparisonExp");
|
||||||
expect(rootExp.comparator).toBeTruthy();
|
expect(rootExp.subExpressions[1].attributeType).toEqual("label");
|
||||||
|
expect(rootExp.subExpressions[1].attributeName).toEqual("mylabel");
|
||||||
|
expect(rootExp.subExpressions[1].comparator).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("simple attribute negation", () => {
|
it("simple attribute negation", () => {
|
||||||
@@ -74,10 +87,12 @@ describe("Parser", () => {
|
|||||||
searchContext: new SearchContext()
|
searchContext: new SearchContext()
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(rootExp.constructor.name).toEqual("NotExp");
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||||
expect(rootExp.subExpression.constructor.name).toEqual("AttributeExistsExp");
|
assertIsArchived(rootExp.subExpressions[0]);
|
||||||
expect(rootExp.subExpression.attributeType).toEqual("label");
|
expect(rootExp.subExpressions[1].constructor.name).toEqual("NotExp");
|
||||||
expect(rootExp.subExpression.attributeName).toEqual("mylabel");
|
expect(rootExp.subExpressions[1].subExpression.constructor.name).toEqual("AttributeExistsExp");
|
||||||
|
expect(rootExp.subExpressions[1].subExpression.attributeType).toEqual("label");
|
||||||
|
expect(rootExp.subExpressions[1].subExpression.attributeName).toEqual("mylabel");
|
||||||
|
|
||||||
rootExp = parse({
|
rootExp = parse({
|
||||||
fulltextTokens: [],
|
fulltextTokens: [],
|
||||||
@@ -85,10 +100,12 @@ describe("Parser", () => {
|
|||||||
searchContext: new SearchContext()
|
searchContext: new SearchContext()
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(rootExp.constructor.name).toEqual("NotExp");
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||||
expect(rootExp.subExpression.constructor.name).toEqual("AttributeExistsExp");
|
assertIsArchived(rootExp.subExpressions[0]);
|
||||||
expect(rootExp.subExpression.attributeType).toEqual("relation");
|
expect(rootExp.subExpressions[1].constructor.name).toEqual("NotExp");
|
||||||
expect(rootExp.subExpression.attributeName).toEqual("myrelation");
|
expect(rootExp.subExpressions[1].subExpression.constructor.name).toEqual("AttributeExistsExp");
|
||||||
|
expect(rootExp.subExpressions[1].subExpression.attributeType).toEqual("relation");
|
||||||
|
expect(rootExp.subExpressions[1].subExpression.attributeName).toEqual("myrelation");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("simple label AND", () => {
|
it("simple label AND", () => {
|
||||||
@@ -99,7 +116,10 @@ describe("Parser", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||||
const [firstSub, secondSub] = rootExp.subExpressions;
|
assertIsArchived(rootExp.subExpressions[0]);
|
||||||
|
|
||||||
|
expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
|
||||||
|
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
|
||||||
|
|
||||||
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
||||||
expect(firstSub.attributeName).toEqual("first");
|
expect(firstSub.attributeName).toEqual("first");
|
||||||
@@ -116,7 +136,10 @@ describe("Parser", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||||
const [firstSub, secondSub] = rootExp.subExpressions;
|
assertIsArchived(rootExp.subExpressions[0]);
|
||||||
|
|
||||||
|
expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
|
||||||
|
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
|
||||||
|
|
||||||
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
||||||
expect(firstSub.attributeName).toEqual("first");
|
expect(firstSub.attributeName).toEqual("first");
|
||||||
@@ -132,8 +155,11 @@ describe("Parser", () => {
|
|||||||
searchContext: new SearchContext()
|
searchContext: new SearchContext()
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(rootExp.constructor.name).toEqual("OrExp");
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||||
const [firstSub, secondSub] = rootExp.subExpressions;
|
assertIsArchived(rootExp.subExpressions[0]);
|
||||||
|
|
||||||
|
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
|
||||||
|
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
|
||||||
|
|
||||||
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
||||||
expect(firstSub.attributeName).toEqual("first");
|
expect(firstSub.attributeName).toEqual("first");
|
||||||
@@ -155,8 +181,9 @@ describe("Parser", () => {
|
|||||||
expect(firstSub.constructor.name).toEqual("PropertyComparisonExp");
|
expect(firstSub.constructor.name).toEqual("PropertyComparisonExp");
|
||||||
expect(firstSub.propertyName).toEqual('isArchived');
|
expect(firstSub.propertyName).toEqual('isArchived');
|
||||||
|
|
||||||
expect(secondSub.constructor.name).toEqual("NoteCacheFlatTextExp");
|
expect(secondSub.constructor.name).toEqual("OrExp");
|
||||||
expect(secondSub.tokens).toEqual(["hello"]);
|
expect(secondSub.subExpressions[0].constructor.name).toEqual("NoteCacheFlatTextExp");
|
||||||
|
expect(secondSub.subExpressions[0].tokens).toEqual(["hello"]);
|
||||||
|
|
||||||
expect(thirdSub.constructor.name).toEqual("LabelComparisonExp");
|
expect(thirdSub.constructor.name).toEqual("LabelComparisonExp");
|
||||||
expect(thirdSub.attributeName).toEqual("mylabel");
|
expect(thirdSub.attributeName).toEqual("mylabel");
|
||||||
@@ -169,8 +196,11 @@ describe("Parser", () => {
|
|||||||
searchContext: new SearchContext()
|
searchContext: new SearchContext()
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(rootExp.constructor.name).toEqual("OrExp");
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||||
const [firstSub, secondSub] = rootExp.subExpressions;
|
assertIsArchived(rootExp.subExpressions[0]);
|
||||||
|
|
||||||
|
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
|
||||||
|
const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
|
||||||
|
|
||||||
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
|
||||||
expect(firstSub.attributeName).toEqual("first");
|
expect(firstSub.attributeName).toEqual("first");
|
||||||
@@ -232,10 +262,12 @@ describe("Invalid expressions", () => {
|
|||||||
searchContext: new SearchContext()
|
searchContext: new SearchContext()
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(rootExp.constructor.name).toEqual("LabelComparisonExp");
|
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||||
expect(rootExp.attributeType).toEqual("label");
|
assertIsArchived(rootExp.subExpressions[0]);
|
||||||
expect(rootExp.attributeName).toEqual("first");
|
expect(rootExp.subExpressions[1].constructor.name).toEqual("LabelComparisonExp");
|
||||||
expect(rootExp.comparator).toBeTruthy();
|
expect(rootExp.subExpressions[1].attributeType).toEqual("label");
|
||||||
|
expect(rootExp.subExpressions[1].attributeName).toEqual("first");
|
||||||
|
expect(rootExp.subExpressions[1].comparator).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("searching by relation without note property", () => {
|
it("searching by relation without note property", () => {
|
||||||
|
|||||||
@@ -562,8 +562,8 @@ describe("Search", () => {
|
|||||||
expect(noteCache.notes[searchResults[0].noteId].title).toEqual("Austria");
|
expect(noteCache.notes[searchResults[0].noteId].title).toEqual("Austria");
|
||||||
expect(noteCache.notes[searchResults[1].noteId].title).toEqual("Italy");
|
expect(noteCache.notes[searchResults[1].noteId].title).toEqual("Italy");
|
||||||
|
|
||||||
searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 0', searchContext);
|
searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 1', searchContext);
|
||||||
expect(searchResults.length).toEqual(0);
|
expect(searchResults.length).toEqual(1);
|
||||||
|
|
||||||
searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 1000', searchContext);
|
searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 1000', searchContext);
|
||||||
expect(searchResults.length).toEqual(4);
|
expect(searchResults.length).toEqual(4);
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
const {note} = require('./note_cache_mocking.js');
|
const {note} = require('./note_cache_mocking.js');
|
||||||
const ValueExtractor = require('../../src/services/search/value_extractor.js');
|
const ValueExtractor = require('../../src/services/search/value_extractor.js');
|
||||||
const noteCache = require('../../src/services/note_cache/note_cache.js');
|
const noteCache = require('../../src/services/note_cache/note_cache.js');
|
||||||
|
const SearchContext = require("../../src/services/search/search_context.js");
|
||||||
|
|
||||||
|
const dsc = new SearchContext();
|
||||||
|
|
||||||
describe("Value extractor", () => {
|
describe("Value extractor", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -10,7 +13,7 @@ describe("Value extractor", () => {
|
|||||||
it("simple title extraction", async () => {
|
it("simple title extraction", async () => {
|
||||||
const europe = note("Europe").note;
|
const europe = note("Europe").note;
|
||||||
|
|
||||||
const valueExtractor = new ValueExtractor(["note", "title"]);
|
const valueExtractor = new ValueExtractor(dsc, ["note", "title"]);
|
||||||
|
|
||||||
expect(valueExtractor.validate()).toBeFalsy();
|
expect(valueExtractor.validate()).toBeFalsy();
|
||||||
expect(valueExtractor.extract(europe)).toEqual("Europe");
|
expect(valueExtractor.extract(europe)).toEqual("Europe");
|
||||||
@@ -21,12 +24,12 @@ describe("Value extractor", () => {
|
|||||||
.label("Capital", "Vienna")
|
.label("Capital", "Vienna")
|
||||||
.note;
|
.note;
|
||||||
|
|
||||||
let valueExtractor = new ValueExtractor(["note", "labels", "capital"]);
|
let valueExtractor = new ValueExtractor(dsc, ["note", "labels", "capital"]);
|
||||||
|
|
||||||
expect(valueExtractor.validate()).toBeFalsy();
|
expect(valueExtractor.validate()).toBeFalsy();
|
||||||
expect(valueExtractor.extract(austria)).toEqual("Vienna");
|
expect(valueExtractor.extract(austria)).toEqual("Vienna");
|
||||||
|
|
||||||
valueExtractor = new ValueExtractor(["#capital"]);
|
valueExtractor = new ValueExtractor(dsc, ["#capital"]);
|
||||||
|
|
||||||
expect(valueExtractor.validate()).toBeFalsy();
|
expect(valueExtractor.validate()).toBeFalsy();
|
||||||
expect(valueExtractor.extract(austria)).toEqual("Vienna");
|
expect(valueExtractor.extract(austria)).toEqual("Vienna");
|
||||||
@@ -38,12 +41,12 @@ describe("Value extractor", () => {
|
|||||||
.child(note("Austria")
|
.child(note("Austria")
|
||||||
.child(vienna));
|
.child(vienna));
|
||||||
|
|
||||||
let valueExtractor = new ValueExtractor(["note", "children", "children", "title"]);
|
let valueExtractor = new ValueExtractor(dsc, ["note", "children", "children", "title"]);
|
||||||
|
|
||||||
expect(valueExtractor.validate()).toBeFalsy();
|
expect(valueExtractor.validate()).toBeFalsy();
|
||||||
expect(valueExtractor.extract(europe.note)).toEqual("Vienna");
|
expect(valueExtractor.extract(europe.note)).toEqual("Vienna");
|
||||||
|
|
||||||
valueExtractor = new ValueExtractor(["note", "parents", "parents", "title"]);
|
valueExtractor = new ValueExtractor(dsc, ["note", "parents", "parents", "title"]);
|
||||||
|
|
||||||
expect(valueExtractor.validate()).toBeFalsy();
|
expect(valueExtractor.validate()).toBeFalsy();
|
||||||
expect(valueExtractor.extract(vienna.note)).toEqual("Europe");
|
expect(valueExtractor.extract(vienna.note)).toEqual("Europe");
|
||||||
@@ -56,12 +59,12 @@ describe("Value extractor", () => {
|
|||||||
.relation('neighbor', czechRepublic.note)
|
.relation('neighbor', czechRepublic.note)
|
||||||
.relation('neighbor', slovakia.note);
|
.relation('neighbor', slovakia.note);
|
||||||
|
|
||||||
let valueExtractor = new ValueExtractor(["note", "relations", "neighbor", "labels", "capital"]);
|
let valueExtractor = new ValueExtractor(dsc, ["note", "relations", "neighbor", "labels", "capital"]);
|
||||||
|
|
||||||
expect(valueExtractor.validate()).toBeFalsy();
|
expect(valueExtractor.validate()).toBeFalsy();
|
||||||
expect(valueExtractor.extract(austria.note)).toEqual("Prague");
|
expect(valueExtractor.extract(austria.note)).toEqual("Prague");
|
||||||
|
|
||||||
valueExtractor = new ValueExtractor(["~neighbor", "labels", "capital"]);
|
valueExtractor = new ValueExtractor(dsc, ["~neighbor", "labels", "capital"]);
|
||||||
|
|
||||||
expect(valueExtractor.validate()).toBeFalsy();
|
expect(valueExtractor.validate()).toBeFalsy();
|
||||||
expect(valueExtractor.extract(austria.note)).toEqual("Prague");
|
expect(valueExtractor.extract(austria.note)).toEqual("Prague");
|
||||||
@@ -70,17 +73,17 @@ describe("Value extractor", () => {
|
|||||||
|
|
||||||
describe("Invalid value extractor property path", () => {
|
describe("Invalid value extractor property path", () => {
|
||||||
it('each path must start with "note" (or label/relation)',
|
it('each path must start with "note" (or label/relation)',
|
||||||
() => expect(new ValueExtractor(["neighbor"]).validate()).toBeTruthy());
|
() => expect(new ValueExtractor(dsc, ["neighbor"]).validate()).toBeTruthy());
|
||||||
|
|
||||||
it("extra path element after terminal label",
|
it("extra path element after terminal label",
|
||||||
() => expect(new ValueExtractor(["~neighbor", "labels", "capital", "noteId"]).validate()).toBeTruthy());
|
() => expect(new ValueExtractor(dsc, ["~neighbor", "labels", "capital", "noteId"]).validate()).toBeTruthy());
|
||||||
|
|
||||||
it("extra path element after terminal title",
|
it("extra path element after terminal title",
|
||||||
() => expect(new ValueExtractor(["note", "title", "isProtected"]).validate()).toBeTruthy());
|
() => expect(new ValueExtractor(dsc, ["note", "title", "isProtected"]).validate()).toBeTruthy());
|
||||||
|
|
||||||
it("relation name and note property is missing",
|
it("relation name and note property is missing",
|
||||||
() => expect(new ValueExtractor(["note", "relations"]).validate()).toBeTruthy());
|
() => expect(new ValueExtractor(dsc, ["note", "relations"]).validate()).toBeTruthy());
|
||||||
|
|
||||||
it("relation is specified but target note property is not specified",
|
it("relation is specified but target note property is not specified",
|
||||||
() => expect(new ValueExtractor(["note", "relations", "myrel"]).validate()).toBeTruthy());
|
() => expect(new ValueExtractor(dsc, ["note", "relations", "myrel"]).validate()).toBeTruthy());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -49,8 +49,13 @@ class Note extends Entity {
|
|||||||
this.isContentAvailable = protectedSessionService.isProtectedSessionAvailable();
|
this.isContentAvailable = protectedSessionService.isProtectedSessionAvailable();
|
||||||
|
|
||||||
if (this.isContentAvailable) {
|
if (this.isContentAvailable) {
|
||||||
|
try {
|
||||||
this.title = protectedSessionService.decryptString(this.title);
|
this.title = protectedSessionService.decryptString(this.title);
|
||||||
}
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw new Error(`Could not decrypt title of note ${this.noteId}: ${e.message} ${e.stack}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
this.title = "[protected]";
|
this.title = "[protected]";
|
||||||
}
|
}
|
||||||
@@ -156,14 +161,14 @@ class Note extends Entity {
|
|||||||
|
|
||||||
sql.upsert("note_contents", "noteId", pojo);
|
sql.upsert("note_contents", "noteId", pojo);
|
||||||
|
|
||||||
const hash = utils.hash(this.noteId + "|" + content.toString());
|
const hash = utils.hash(this.noteId + "|" + pojo.content.toString());
|
||||||
|
|
||||||
entityChangesService.addEntityChange({
|
entityChangesService.addEntityChange({
|
||||||
entityName: 'note_contents',
|
entityName: 'note_contents',
|
||||||
entityId: this.noteId,
|
entityId: this.noteId,
|
||||||
hash: hash,
|
hash: hash,
|
||||||
isErased: false,
|
isErased: false,
|
||||||
utcDateChanged: this.getUtcDateChanged()
|
utcDateChanged: pojo.utcDateModified
|
||||||
}, null);
|
}, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,7 +370,6 @@ class Note extends Entity {
|
|||||||
WHERE attributes.isDeleted = 0
|
WHERE attributes.isDeleted = 0
|
||||||
AND attributes.type = 'relation'
|
AND attributes.type = 'relation'
|
||||||
AND attributes.name = 'template'
|
AND attributes.name = 'template'
|
||||||
AND (treeWithAttrs.level = 0 OR attributes.isInheritable = 1)
|
|
||||||
)
|
)
|
||||||
SELECT attributes.* FROM attributes JOIN treeWithAttrs ON attributes.noteId = treeWithAttrs.noteId
|
SELECT attributes.* FROM attributes JOIN treeWithAttrs ON attributes.noteId = treeWithAttrs.noteId
|
||||||
WHERE attributes.isDeleted = 0 AND (attributes.isInheritable = 1 OR treeWithAttrs.level = 0)
|
WHERE attributes.isDeleted = 0 AND (attributes.isInheritable = 1 OR treeWithAttrs.level = 0)
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ class NoteShort {
|
|||||||
const workspaceIconClass = this.getWorkspaceIconClass();
|
const workspaceIconClass = this.getWorkspaceIconClass();
|
||||||
|
|
||||||
if (iconClassLabels.length > 0) {
|
if (iconClassLabels.length > 0) {
|
||||||
return iconClassLabels.map(l => l.value).join(' ');
|
return iconClassLabels[0].value;
|
||||||
}
|
}
|
||||||
else if (workspaceIconClass) {
|
else if (workspaceIconClass) {
|
||||||
return workspaceIconClass;
|
return workspaceIconClass;
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ export default class MobileLayout {
|
|||||||
.child(new NoteTitleWidget())
|
.child(new NoteTitleWidget())
|
||||||
.child(new CloseDetailButtonWidget()))
|
.child(new CloseDetailButtonWidget()))
|
||||||
.child(new NoteDetailWidget()
|
.child(new NoteDetailWidget()
|
||||||
.css('padding', '5px 20px 10px 0')));
|
.css('padding', '5px 20px 10px 0')
|
||||||
|
.css('overflow', 'auto')
|
||||||
|
.css('height', '100%')
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import utils from "./utils.js";
|
import utils from "./utils.js";
|
||||||
import options from './options.js';
|
import options from './options.js';
|
||||||
|
import server from "./server.js";
|
||||||
|
|
||||||
const PROTECTED_SESSION_ID_KEY = 'protectedSessionId';
|
const PROTECTED_SESSION_ID_KEY = 'protectedSessionId';
|
||||||
|
|
||||||
@@ -23,11 +24,11 @@ function resetSessionCookie() {
|
|||||||
utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, null);
|
utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetProtectedSession() {
|
async function resetProtectedSession() {
|
||||||
resetSessionCookie();
|
resetSessionCookie();
|
||||||
|
|
||||||
// most secure solution - guarantees nothing remained in memory
|
await server.post("logout/protected");
|
||||||
// since this expires because user doesn't use the app, it shouldn't be disruptive
|
|
||||||
utils.reloadApp();
|
utils.reloadApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default class NoteTitleWidget extends TabAwareWidget {
|
|||||||
|
|
||||||
protectedSessionHolder.touchProtectedSessionIfNecessary(this.note);
|
protectedSessionHolder.touchProtectedSessionIfNecessary(this.note);
|
||||||
|
|
||||||
await server.put(`notes/${this.noteId}/change-title`, {title});
|
await server.put(`notes/${this.noteId}/change-title`, {title}, this.componentId);
|
||||||
});
|
});
|
||||||
|
|
||||||
appContext.addBeforeUnloadListener(this);
|
appContext.addBeforeUnloadListener(this);
|
||||||
|
|||||||
@@ -176,6 +176,15 @@ const TPL = `
|
|||||||
title="Images which are shown in the parent text note will not be displayed in the tree"></span>
|
title="Images which are shown in the parent text note will not be displayed in the tree"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<label class="form-check-label">
|
||||||
|
<input class="form-check-input auto-collapse-note-tree" type="checkbox" value="">
|
||||||
|
|
||||||
|
Automatically collapse notes
|
||||||
|
<span class="bx bx-info-circle"
|
||||||
|
title="Notes will be collapsed after period of inactivity to declutter the tree."></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
@@ -235,6 +244,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
this.$treeSettingsPopup = this.$widget.find('.tree-settings-popup');
|
this.$treeSettingsPopup = this.$widget.find('.tree-settings-popup');
|
||||||
this.$hideArchivedNotesCheckbox = this.$treeSettingsPopup.find('.hide-archived-notes');
|
this.$hideArchivedNotesCheckbox = this.$treeSettingsPopup.find('.hide-archived-notes');
|
||||||
this.$hideIncludedImages = this.$treeSettingsPopup.find('.hide-included-images');
|
this.$hideIncludedImages = this.$treeSettingsPopup.find('.hide-included-images');
|
||||||
|
this.$autoCollapseNoteTree = this.$treeSettingsPopup.find('.auto-collapse-note-tree');
|
||||||
|
|
||||||
this.$treeSettingsButton = this.$widget.find('.tree-settings-button');
|
this.$treeSettingsButton = this.$widget.find('.tree-settings-button');
|
||||||
this.$treeSettingsButton.on("click", e => {
|
this.$treeSettingsButton.on("click", e => {
|
||||||
@@ -245,6 +255,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
|
|
||||||
this.$hideArchivedNotesCheckbox.prop("checked", this.hideArchivedNotes);
|
this.$hideArchivedNotesCheckbox.prop("checked", this.hideArchivedNotes);
|
||||||
this.$hideIncludedImages.prop("checked", this.hideIncludedImages);
|
this.$hideIncludedImages.prop("checked", this.hideIncludedImages);
|
||||||
|
this.$autoCollapseNoteTree.prop("checked", this.autoCollapseNoteTree);
|
||||||
|
|
||||||
let top = this.$treeSettingsButton[0].offsetTop;
|
let top = this.$treeSettingsButton[0].offsetTop;
|
||||||
let left = this.$treeSettingsButton[0].offsetLeft;
|
let left = this.$treeSettingsButton[0].offsetLeft;
|
||||||
@@ -272,6 +283,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
this.$saveTreeSettingsButton.on('click', async () => {
|
this.$saveTreeSettingsButton.on('click', async () => {
|
||||||
await this.setHideArchivedNotes(this.$hideArchivedNotesCheckbox.prop("checked"));
|
await this.setHideArchivedNotes(this.$hideArchivedNotesCheckbox.prop("checked"));
|
||||||
await this.setHideIncludedImages(this.$hideIncludedImages.prop("checked"));
|
await this.setHideIncludedImages(this.$hideIncludedImages.prop("checked"));
|
||||||
|
await this.setAutoCollapseNoteTree(this.$autoCollapseNoteTree.prop("checked"));
|
||||||
|
|
||||||
this.$treeSettingsPopup.hide();
|
this.$treeSettingsPopup.hide();
|
||||||
|
|
||||||
@@ -327,6 +339,14 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
await options.save("hideIncludedImages_" + this.treeName, val.toString());
|
await options.save("hideIncludedImages_" + this.treeName, val.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get autoCollapseNoteTree() {
|
||||||
|
return options.is("autoCollapseNoteTree");
|
||||||
|
}
|
||||||
|
|
||||||
|
async setAutoCollapseNoteTree(val) {
|
||||||
|
await options.save("autoCollapseNoteTree", val.toString());
|
||||||
|
}
|
||||||
|
|
||||||
initFancyTree() {
|
initFancyTree() {
|
||||||
const treeData = [this.prepareRootNode()];
|
const treeData = [this.prepareRootNode()];
|
||||||
|
|
||||||
@@ -362,8 +382,6 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
node.setActive();
|
node.setActive();
|
||||||
|
|
||||||
this.clearSelectedNodes();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -373,6 +391,8 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
// click event won't propagate so let's close context menu manually
|
// click event won't propagate so let's close context menu manually
|
||||||
contextMenu.hide();
|
contextMenu.hide();
|
||||||
|
|
||||||
|
this.clearSelectedNodes();
|
||||||
|
|
||||||
const notePath = treeService.getNotePath(data.node);
|
const notePath = treeService.getNotePath(data.node);
|
||||||
|
|
||||||
const activeTabContext = appContext.tabManager.getActiveTabContext();
|
const activeTabContext = appContext.tabManager.getActiveTabContext();
|
||||||
@@ -797,10 +817,12 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
|
|
||||||
const node = await this.expandToNote(activeContext.notePath);
|
const node = await this.expandToNote(activeContext.notePath);
|
||||||
|
|
||||||
|
if (node) {
|
||||||
await node.makeVisible({scrollIntoView: true});
|
await node.makeVisible({scrollIntoView: true});
|
||||||
node.setActive(true, {noEvents: true, noFocus: false});
|
node.setActive(true, {noEvents: true, noFocus: false});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @return {FancytreeNode} */
|
/** @return {FancytreeNode} */
|
||||||
async getNodeFromPath(notePath, expand = false, logErrors = true) {
|
async getNodeFromPath(notePath, expand = false, logErrors = true) {
|
||||||
@@ -851,8 +873,12 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
// these are real notes with real notePath, user can display them in a detail
|
// these are real notes with real notePath, user can display them in a detail
|
||||||
// but they don't have a node in the tree
|
// but they don't have a node in the tree
|
||||||
|
|
||||||
|
const childNote = await treeCache.getNote(childNoteId);
|
||||||
|
|
||||||
|
if (!childNote || childNote.type !== 'image') {
|
||||||
ws.logError(`Can't find node for child node of noteId=${childNoteId} for parent of noteId=${parentNode.data.noteId} and hoistedNoteId=${hoistedNoteService.getHoistedNoteId()}, requested path is ${notePath}`);
|
ws.logError(`Can't find node for child node of noteId=${childNoteId} for parent of noteId=${parentNode.data.noteId} and hoistedNoteId=${hoistedNoteService.getHoistedNoteId()}, requested path is ${notePath}`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -955,6 +981,10 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.autoCollapseTimeoutId = setTimeout(() => {
|
this.autoCollapseTimeoutId = setTimeout(() => {
|
||||||
|
if (!this.autoCollapseNoteTree) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We're collapsing notes after period of inactivity to "cleanup" the tree - users rarely
|
* We're collapsing notes after period of inactivity to "cleanup" the tree - users rarely
|
||||||
* collapse the notes and the tree becomes unusuably large.
|
* collapse the notes and the tree becomes unusuably large.
|
||||||
@@ -1114,11 +1144,12 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
node.setActive(true, {noEvents: true, noFocus: !activeNodeFocused});
|
|
||||||
|
|
||||||
if (activeNodeFocused) {
|
if (activeNodeFocused) {
|
||||||
node.setFocus(true);
|
// needed by Firefox: https://github.com/zadam/trilium/issues/1865
|
||||||
|
this.tree.$container.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await node.setActive(true, {noEvents: true, noFocus: !activeNodeFocused});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// this is used when original note has been deleted and we want to move the focus to the note above/below
|
// this is used when original note has been deleted and we want to move the focus to the note above/below
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const TPL = `
|
|||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<span class="bx bx-trash"></span>
|
<span class="bx bx-trash"></span>
|
||||||
|
|
||||||
Delete matched note
|
Delete matched notes
|
||||||
</td>
|
</td>
|
||||||
<td class="button-column">
|
<td class="button-column">
|
||||||
<span class="bx bx-x icon-action action-conf-del"></span>
|
<span class="bx bx-x icon-action action-conf-del"></span>
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ const TPL = `
|
|||||||
height: 35px;
|
height: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.standard-top-widget > div {
|
||||||
|
flex-shrink: 0; /* fixes https://github.com/zadam/trilium/issues/1745 */
|
||||||
|
}
|
||||||
|
|
||||||
.standard-top-widget button.noborder {
|
.standard-top-widget button.noborder {
|
||||||
padding: 1px 5px 1px 5px;
|
padding: 1px 5px 1px 5px;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
|
|||||||
@@ -5,24 +5,47 @@ import linkService from "../../services/link.js";
|
|||||||
import noteContentRenderer from "../../services/note_content_renderer.js";
|
import noteContentRenderer from "../../services/note_content_renderer.js";
|
||||||
|
|
||||||
export default class AbstractTextTypeWidget extends TypeWidget {
|
export default class AbstractTextTypeWidget extends TypeWidget {
|
||||||
doRender() {
|
setupImageOpening(singleClickOpens) {
|
||||||
this.$widget.on("dblclick", "img", e => {
|
this.$widget.on("dblclick", "img", e => this.openImageInCurrentTab($(e.target)));
|
||||||
const $img = $(e.target);
|
|
||||||
const src = $img.prop("src");
|
|
||||||
|
|
||||||
const match = src.match(/\/api\/images\/([A-Za-z0-9]+)\//);
|
this.$widget.on("click", "img", e => {
|
||||||
|
if ((e.which === 1 && e.ctrlKey) || e.which === 2) {
|
||||||
if (match) {
|
this.openImageInNewTab($(e.target));
|
||||||
const noteId = match[1];
|
|
||||||
|
|
||||||
appContext.tabManager.getActiveTabContext().setNote(noteId);
|
|
||||||
}
|
}
|
||||||
else {
|
else if (e.which === 1 && singleClickOpens) {
|
||||||
window.open(src, '_blank');
|
this.openImageInCurrentTab($(e.target));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openImageInCurrentTab($img) {
|
||||||
|
const imgSrc = $img.prop("src");
|
||||||
|
const noteId = this.getNoteIdFromImage(imgSrc);
|
||||||
|
|
||||||
|
if (noteId) {
|
||||||
|
appContext.tabManager.getActiveTabContext().setNote(noteId);
|
||||||
|
} else {
|
||||||
|
window.open(imgSrc, '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openImageInNewTab($img) {
|
||||||
|
const imgSrc = $img.prop("src");
|
||||||
|
const noteId = this.getNoteIdFromImage(imgSrc);
|
||||||
|
|
||||||
|
if (noteId) {
|
||||||
|
appContext.tabManager.openTabWithNoteWithHoisting(noteId);
|
||||||
|
} else {
|
||||||
|
window.open(imgSrc, '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getNoteIdFromImage(imgSrc) {
|
||||||
|
const match = imgSrc.match(/\/api\/images\/([A-Za-z0-9]+)\//);
|
||||||
|
|
||||||
|
return match ? match[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
async loadIncludedNote(noteId, $el) {
|
async loadIncludedNote(noteId, $el) {
|
||||||
const note = await treeCache.getNote(noteId);
|
const note = await treeCache.getNote(noteId);
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
|
|
||||||
keyboardActionService.setupActionsForElement('text-detail', this.$widget, this);
|
keyboardActionService.setupActionsForElement('text-detail', this.$widget, this);
|
||||||
|
|
||||||
|
this.setupImageOpening(false);
|
||||||
|
|
||||||
super.doRender();
|
super.doRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const TPL = `
|
|||||||
<style>
|
<style>
|
||||||
.note-detail-read-only-code {
|
.note-detail-read-only-code {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
min-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-detail-read-only-code-content {
|
.note-detail-read-only-code-content {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const TPL = `
|
|||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
font-family: var(--detail-text-font-family);
|
font-family: var(--detail-text-font-family);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
min-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-detail-readonly-text p:first-child, .note-detail-readonly-text::before {
|
.note-detail-readonly-text p:first-child, .note-detail-readonly-text::before {
|
||||||
@@ -33,6 +34,7 @@ const TPL = `
|
|||||||
|
|
||||||
.note-detail-readonly-text img {
|
.note-detail-readonly-text img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-text-note-container {
|
.edit-text-note-container {
|
||||||
@@ -65,6 +67,8 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
this.triggerEvent('textPreviewDisabled', {tabContext: this.tabContext});
|
this.triggerEvent('textPreviewDisabled', {tabContext: this.tabContext});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.setupImageOpening(true);
|
||||||
|
|
||||||
super.doRender();
|
super.doRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,11 @@ function processContent(images, note, content) {
|
|||||||
const filename = path.basename(src);
|
const filename = path.basename(src);
|
||||||
|
|
||||||
if (!dataUrl || !dataUrl.startsWith("data:image")) {
|
if (!dataUrl || !dataUrl.startsWith("data:image")) {
|
||||||
log.info("Image could not be recognized as data URL:", dataUrl.substr(0, Math.min(100, dataUrl.length)));
|
const excerpt = dataUrl
|
||||||
|
? dataUrl.substr(0, Math.min(100, dataUrl.length))
|
||||||
|
: "null";
|
||||||
|
|
||||||
|
log.info("Image could not be recognized as data URL: " + excerpt);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,12 @@ function loginToProtectedSession(req) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function logoutFromProtectedSession() {
|
||||||
|
protectedSessionService.resetDataKey();
|
||||||
|
|
||||||
|
eventService.emit(eventService.LEAVE_PROTECTED_SESSION);
|
||||||
|
}
|
||||||
|
|
||||||
function token(req) {
|
function token(req) {
|
||||||
const username = req.body.username;
|
const username = req.body.username;
|
||||||
const password = req.body.password;
|
const password = req.body.password;
|
||||||
@@ -101,5 +107,6 @@ function token(req) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
loginSync,
|
loginSync,
|
||||||
loginToProtectedSession,
|
loginToProtectedSession,
|
||||||
|
logoutFromProtectedSession,
|
||||||
token
|
token
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const noteCacheService = require('../../services/note_cache/note_cache_service')
|
|||||||
const protectedSessionService = require('../../services/protected_session');
|
const protectedSessionService = require('../../services/protected_session');
|
||||||
const noteRevisionService = require('../../services/note_revisions');
|
const noteRevisionService = require('../../services/note_revisions');
|
||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
|
const sql = require('../../services/sql');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
function getNoteRevisions(req) {
|
function getNoteRevisions(req) {
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ const ALLOWED_OPTIONS = new Set([
|
|||||||
'attributeListExpanded',
|
'attributeListExpanded',
|
||||||
'promotedAttributesExpanded',
|
'promotedAttributesExpanded',
|
||||||
'similarNotesExpanded',
|
'similarNotesExpanded',
|
||||||
'headingStyle'
|
'headingStyle',
|
||||||
|
'autoCollapseNoteTree'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function getOptions() {
|
function getOptions() {
|
||||||
|
|||||||
@@ -200,9 +200,7 @@ function queueSector(req) {
|
|||||||
const entityName = utils.sanitizeSqlIdentifier(req.params.entityName);
|
const entityName = utils.sanitizeSqlIdentifier(req.params.entityName);
|
||||||
const sector = utils.sanitizeSqlIdentifier(req.params.sector);
|
const sector = utils.sanitizeSqlIdentifier(req.params.sector);
|
||||||
|
|
||||||
const entityPrimaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName;
|
entityChangesService.addEntityChangesForSector(entityName, sector);
|
||||||
|
|
||||||
entityChangesService.addEntityChangesForSector(entityName, entityPrimaryKey, sector);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -270,6 +270,8 @@ function register(app) {
|
|||||||
route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler);
|
route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler);
|
||||||
// this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username)
|
// this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username)
|
||||||
apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession);
|
apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession);
|
||||||
|
apiRoute(POST, '/api/logout/protected', loginApiRoute.logoutFromProtectedSession);
|
||||||
|
|
||||||
route(POST, '/api/login/token', [], loginApiRoute.token, apiResultHandler);
|
route(POST, '/api/login/token', [], loginApiRoute.token, apiResultHandler);
|
||||||
|
|
||||||
// in case of local electron, local calls are allowed unauthenticated, for server they need auth
|
// in case of local electron, local calls are allowed unauthenticated, for server they need auth
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
module.exports = { buildDate:"2021-03-14T22:56:27+01:00", buildRevision: "6c8d20288df302f3a415bd1bdcace98bf29d4bf6" };
|
module.exports = { buildDate:"2021-04-22T20:50:22+02:00", buildRevision: "c27f573eed8905fc1d958adf5bdee0efc3aff593" };
|
||||||
|
|||||||
@@ -53,22 +53,14 @@ function moveEntityChangeToTop(entityName, entityId) {
|
|||||||
addEntityChange(entityName, entityId, hash, null, isSynced);
|
addEntityChange(entityName, entityId, hash, null, isSynced);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addEntityChangesForSector(entityName, entityPrimaryKey, sector) {
|
function addEntityChangesForSector(entityName, sector) {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const repository = require('./repository');
|
|
||||||
|
const entityChanges = sql.getRows(`SELECT * FROM entity_changes WHERE entityName = ? AND SUBSTR(entityId, 1, 1) = ?`, [entityName, sector]);
|
||||||
|
|
||||||
sql.transactional(() => {
|
sql.transactional(() => {
|
||||||
const entityIds = sql.getColumn(`SELECT ${entityPrimaryKey} FROM ${entityName} WHERE SUBSTR(${entityPrimaryKey}, 1, 1) = ?`, [sector]);
|
for (const ec of entityChanges) {
|
||||||
|
insertEntityChange(entityName, ec.entityId, ec.hash, ec.isErased, ec.utcDateChanged, ec.sourceId, ec.isSynced);
|
||||||
for (const entityId of entityIds) {
|
|
||||||
// retrieving entity one by one to avoid memory issues with note_contents
|
|
||||||
const entity = repository.getEntity(`SELECT * FROM ${entityName} WHERE ${entityPrimaryKey} = ?`, [entityId]);
|
|
||||||
|
|
||||||
if (entityName === 'options' && !entity.isSynced) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
insertEntityChange(entityName, entityId, entity.generateHash(), false, entity.getUtcDateChanged(), 'content-check', true);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const log = require('./log');
|
|||||||
|
|
||||||
const NOTE_TITLE_CHANGED = "NOTE_TITLE_CHANGED";
|
const NOTE_TITLE_CHANGED = "NOTE_TITLE_CHANGED";
|
||||||
const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION";
|
const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION";
|
||||||
|
const LEAVE_PROTECTED_SESSION = "LEAVE_PROTECTED_SESSION";
|
||||||
const ENTITY_CREATED = "ENTITY_CREATED";
|
const ENTITY_CREATED = "ENTITY_CREATED";
|
||||||
const ENTITY_CHANGED = "ENTITY_CHANGED";
|
const ENTITY_CHANGED = "ENTITY_CHANGED";
|
||||||
const ENTITY_DELETED = "ENTITY_DELETED";
|
const ENTITY_DELETED = "ENTITY_DELETED";
|
||||||
@@ -47,6 +48,7 @@ module.exports = {
|
|||||||
// event types:
|
// event types:
|
||||||
NOTE_TITLE_CHANGED,
|
NOTE_TITLE_CHANGED,
|
||||||
ENTER_PROTECTED_SESSION,
|
ENTER_PROTECTED_SESSION,
|
||||||
|
LEAVE_PROTECTED_SESSION,
|
||||||
ENTITY_CREATED,
|
ENTITY_CREATED,
|
||||||
ENTITY_CHANGED,
|
ENTITY_CHANGED,
|
||||||
ENTITY_DELETED,
|
ENTITY_DELETED,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const protectedSessionService = require('../../protected_session');
|
const protectedSessionService = require('../../protected_session');
|
||||||
|
const log = require('../../log');
|
||||||
|
|
||||||
class Note {
|
class Note {
|
||||||
constructor(noteCache, row) {
|
constructor(noteCache, row) {
|
||||||
@@ -405,7 +406,7 @@ class Note {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let minDistance = 999_999;
|
let minDistance = 999999;
|
||||||
|
|
||||||
for (const parent of this.parents) {
|
for (const parent of this.parents) {
|
||||||
minDistance = Math.min(minDistance, parent.getDistanceToAncestor(ancestorNoteId) + 1);
|
minDistance = Math.min(minDistance, parent.getDistanceToAncestor(ancestorNoteId) + 1);
|
||||||
@@ -416,10 +417,15 @@ class Note {
|
|||||||
|
|
||||||
decrypt() {
|
decrypt() {
|
||||||
if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
|
if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
|
||||||
|
try {
|
||||||
this.title = protectedSessionService.decryptString(this.title);
|
this.title = protectedSessionService.decryptString(this.title);
|
||||||
|
|
||||||
this.isDecrypted = true;
|
this.isDecrypted = true;
|
||||||
}
|
}
|
||||||
|
catch (e) {
|
||||||
|
log.error(`Could not decrypt note ${this.noteId}: ${e.message} ${e.stack}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for logging etc
|
// for logging etc
|
||||||
|
|||||||
@@ -177,6 +177,10 @@ eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
eventService.subscribe(eventService.LEAVE_PROTECTED_SESSION, () => {
|
||||||
|
load();
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
load
|
load
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ async function findSimilarNotes(noteId) {
|
|||||||
|
|
||||||
const baseNote = noteCache.notes[noteId];
|
const baseNote = noteCache.notes[noteId];
|
||||||
|
|
||||||
if (!baseNote) {
|
if (!baseNote || !baseNote.utcDateCreated) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ const defaultOptions = [
|
|||||||
{ name: 'similarNotesExpanded', value: 'true', isSynced: true },
|
{ name: 'similarNotesExpanded', value: 'true', isSynced: true },
|
||||||
{ name: 'debugModeEnabled', value: 'false', isSynced: false },
|
{ name: 'debugModeEnabled', value: 'false', isSynced: false },
|
||||||
{ name: 'headingStyle', value: 'markdown', isSynced: true },
|
{ name: 'headingStyle', value: 'markdown', isSynced: true },
|
||||||
|
{ name: 'autoCollapseNoteTree', value: 'true', isSynced: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
function initStartupOptions() {
|
function initStartupOptions() {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const log = require('./log');
|
|||||||
const dataEncryptionService = require('./data_encryption');
|
const dataEncryptionService = require('./data_encryption');
|
||||||
const cls = require('./cls');
|
const cls = require('./cls');
|
||||||
|
|
||||||
const dataKeyMap = {};
|
let dataKeyMap = {};
|
||||||
|
|
||||||
function setDataKey(decryptedDataKey) {
|
function setDataKey(decryptedDataKey) {
|
||||||
const protectedSessionId = utils.randomSecureToken(32);
|
const protectedSessionId = utils.randomSecureToken(32);
|
||||||
@@ -29,6 +29,10 @@ function getDataKey() {
|
|||||||
return dataKeyMap[protectedSessionId];
|
return dataKeyMap[protectedSessionId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetDataKey() {
|
||||||
|
dataKeyMap = {};
|
||||||
|
}
|
||||||
|
|
||||||
function isProtectedSessionAvailable() {
|
function isProtectedSessionAvailable() {
|
||||||
const protectedSessionId = getProtectedSessionId();
|
const protectedSessionId = getProtectedSessionId();
|
||||||
|
|
||||||
@@ -71,6 +75,7 @@ function decryptString(cipherText) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
setDataKey,
|
setDataKey,
|
||||||
getDataKey,
|
getDataKey,
|
||||||
|
resetDataKey,
|
||||||
isProtectedSessionAvailable,
|
isProtectedSessionAvailable,
|
||||||
encrypt,
|
encrypt,
|
||||||
decrypt,
|
decrypt,
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ function lex(str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function finishWord(endIndex) {
|
function finishWord(endIndex, createAlsoForEmptyWords = false) {
|
||||||
if (currentWord === '') {
|
if (currentWord === '' && !createAlsoForEmptyWords) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ function lex(str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (quotes === chr) {
|
else if (quotes === chr) {
|
||||||
finishWord(i - 1);
|
finishWord(i - 1, true);
|
||||||
|
|
||||||
quotes = false;
|
quotes = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,9 +245,7 @@ async function checkContentHash(syncContext) {
|
|||||||
const failedChecks = contentHashService.checkContentHashes(resp.entityHashes);
|
const failedChecks = contentHashService.checkContentHashes(resp.entityHashes);
|
||||||
|
|
||||||
for (const {entityName, sector} of failedChecks) {
|
for (const {entityName, sector} of failedChecks) {
|
||||||
const entityPrimaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName;
|
entityChangesService.addEntityChangesForSector(entityName, sector);
|
||||||
|
|
||||||
entityChangesService.addEntityChangesForSector(entityName, entityPrimaryKey, sector);
|
|
||||||
|
|
||||||
await syncRequest(syncContext, 'POST', `/api/sync/queue-sector/${entityName}/${sector}`);
|
await syncRequest(syncContext, 'POST', `/api/sync/queue-sector/${entityName}/${sector}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ function updateEntity(entityChange, entity, sourceId) {
|
|||||||
? updateNoteReordering(entityChange, entity, sourceId)
|
? updateNoteReordering(entityChange, entity, sourceId)
|
||||||
: updateNormalEntity(entityChange, entity, sourceId);
|
: updateNormalEntity(entityChange, entity, sourceId);
|
||||||
|
|
||||||
// currently making exception for protected notes and note revisions because here
|
if (updated && !entityChange.isErased) {
|
||||||
// the title and content are not available decrypted as listeners would expect
|
|
||||||
if (updated && !entity.isProtected && !entityChange.isErased) {
|
|
||||||
eventService.emit(eventService.ENTITY_SYNCED, {
|
eventService.emit(eventService.ENTITY_SYNCED, {
|
||||||
entityName: entityChange.entityName,
|
entityName: entityChange.entityName,
|
||||||
entity
|
entity
|
||||||
@@ -44,7 +42,7 @@ function updateNormalEntity(remoteEntityChange, entity, sourceId) {
|
|||||||
|
|
||||||
if (localEntityChange && !localEntityChange.isErased && remoteEntityChange.isErased) {
|
if (localEntityChange && !localEntityChange.isErased && remoteEntityChange.isErased) {
|
||||||
sql.transactional(() => {
|
sql.transactional(() => {
|
||||||
const primaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName;
|
const primaryKey = entityConstructor.getEntityFromEntityName(remoteEntityChange.entityName).primaryKeyName;
|
||||||
|
|
||||||
sql.execute(`DELETE FROM ${remoteEntityChange.entityName} WHERE ${primaryKey} = ?`, remoteEntityChange.entityId);
|
sql.execute(`DELETE FROM ${remoteEntityChange.entityName} WHERE ${primaryKey} = ?`, remoteEntityChange.entityId);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
|
const log = require('./log');
|
||||||
const repository = require('./repository');
|
const repository = require('./repository');
|
||||||
const Branch = require('../entities/branch');
|
const Branch = require('../entities/branch');
|
||||||
const entityChangesService = require('./entity_changes.js');
|
const entityChangesService = require('./entity_changes.js');
|
||||||
@@ -139,7 +140,12 @@ function sortNotesByTitle(parentNoteId, foldersFirst = false, reverse = false) {
|
|||||||
sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?",
|
sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?",
|
||||||
[position, note.branchId]);
|
[position, note.branchId]);
|
||||||
|
|
||||||
|
if (note.branchId in noteCache.branches) {
|
||||||
noteCache.branches[note.branchId].notePosition = position;
|
noteCache.branches[note.branchId].notePosition = position;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.info(`Branch "${note.branchId}" was not found in note cache.`);
|
||||||
|
}
|
||||||
|
|
||||||
position += 10;
|
position += 10;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/src/public" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src/public" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/src/public/app-dist" />
|
<excludeFolder url="file://$MODULE_DIR$/src/public/app-dist" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/libraries" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/docs" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/bin/better-sqlite3" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
|||||||
Reference in New Issue
Block a user