Compare commits

...

26 Commits

Author SHA1 Message Date
zadam
25ce2e4253 release 0.46.9 2021-04-22 20:50:22 +02:00
zadam
c27f573eed fix line continuation in the release script 2021-04-22 19:39:57 +02:00
zadam
1d99c4e80b fix moving notes with keyboard on Firefox, closes #1865 2021-04-21 22:03:41 +02:00
zadam
dc7c64a94d Merge remote-tracking branch 'origin/stable' into stable 2021-04-21 20:38:15 +02:00
zadam
dc6a530d8c clear note selection anytime a new note is activated 2021-04-21 20:38:07 +02:00
zadam
a674a12706 disable tar polluting build log 2021-04-19 20:56:05 +02:00
zadam
c29f1af48f use n to force node version
(cherry picked from commit 51d1d8efb8)
2021-04-19 20:51:06 +02:00
zadam
a8d72c46e4 release 0.46.8 2021-04-18 21:47:45 +02:00
zadam
ec36fbd83e release 0.46.8 2021-04-18 21:46:35 +02:00
zadam
aedb05cbab release 0.46.8 2021-04-18 21:39:01 +02:00
zadam
bb16840a72 release 0.46.8 2021-04-18 21:30:43 +02:00
zadam
7b5d44a329 use official gh client instead of github-release to execute releases 2021-04-18 21:29:10 +02:00
zadam
a53a65be1f clipper fix, closes #1840 2021-04-12 23:29:02 +02:00
zadam
44af431a93 ctrl + click / middle click now opens image in a text note in a new tab 2021-04-11 22:24:21 +02:00
zadam
caa11b8f7e fix inheriting inheritable attributes through template, closes #1828 2021-04-05 20:52:19 +02:00
zadam
41cce4dcb9 improved task manager with allowed year range, #1823 2021-04-04 23:57:55 +02:00
zadam
858072cc10 fix lexer to parse correctly quoted empty strings, #1825 2021-04-04 23:13:18 +02:00
zadam
855c5e0e67 fix unit tests 2021-04-04 22:44:22 +02:00
zadam
f0cc3d0bcd use entity changes instead of actual tables to fill in sector to sync, fixes #1809 2021-04-04 22:02:40 +02:00
zadam
7672f22ce0 release 0.46.7 2021-04-03 22:37:04 +02:00
zadam
ef37a52a06 when leaving protected session don't forget to reset note cache (titles), #1810 2021-04-03 22:02:25 +02:00
zadam
2318d615bb make note cache decryption more robust - one failure will not crash the whole process, #1810 2021-04-03 21:48:16 +02:00
zadam
bc14c3d665 backport not deforming standard top widget in small window size from master 2021-03-31 22:59:07 +02:00
zadam
b89ea9a684 check for note cache branch existence, #1808 2021-03-31 22:35:10 +02:00
zadam
496767a52b release 0.46.6 2021-03-25 20:28:57 +01:00
zadam
9139c597e5 use only one label for icon class, fixes #1791 2021-03-25 19:46:10 +01:00
34 changed files with 227 additions and 143 deletions

3
.idea/vcs.xml generated
View File

@@ -2,6 +2,5 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>
</project>

View File

@@ -10,7 +10,7 @@ fi
cd dist
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
cd ..

View File

@@ -5,7 +5,7 @@ if [[ $# -eq 0 ]] ; then
exit 1
fi
npm run webpack
n exec 12 npm run webpack
DIR=$1
@@ -27,7 +27,7 @@ cp -r electron.js $DIR/
cp webpack-* $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
rm -r $DIR/node_modules/image-q/demo

View File

@@ -55,47 +55,20 @@ echo "Creating release in GitHub"
EXTRA=
if [[ $TAG == *"beta"* ]]; then
EXTRA=--pre-release
EXTRA=--prerelease
fi
github-release release \
--tag $TAG \
--name "$TAG release" $EXTRA
echo "$GITHUB_CLI_AUTH_TOKEN" | gh auth login --with-token
echo "Uploading debian x64 package"
github-release upload \
--tag $TAG \
--name "$DEBIAN_X64_BUILD" \
--file "dist/$DEBIAN_X64_BUILD"
echo "Uploading linux x64 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"
gh release create "$TAG" \
--title "$TAG release" \
--notes "" \
$EXTRA \
"dist/$DEBIAN_X64_BUILD" \
"dist/$LINUX_X64_BUILD" \
"dist/$WINDOWS_X64_BUILD" \
"dist/$MAC_X64_BUILD" \
"dist/$SERVER_BUILD"
echo "Building docker image"
@@ -105,4 +78,4 @@ echo "Pushing docker image to dockerhub"
bin/push-docker-image.sh $VERSION
echo "Release finished!"
echo "Release finished!"

Binary file not shown.

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "trilium",
"version": "0.46.5",
"version": "0.46.7",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.46.6",
"version": "0.46.9",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -51,10 +51,11 @@
"is-animated": "^2.0.1",
"is-svg": "4.2.1",
"jimp": "0.16.1",
"joplin-turndown-plugin-gfm": "1.0.12",
"jsdom": "16.5.0",
"mime-types": "2.1.29",
"multer": "1.4.2",
"node-abi": "2.21.0",
"node-abi": "2.26.0",
"open": "7.4.2",
"portscanner": "2.2.0",
"rand-token": "1.0.1",
@@ -70,7 +71,6 @@
"striptags": "3.1.1",
"tmp": "^0.2.1",
"turndown": "7.0.0",
"joplin-turndown-plugin-gfm": "1.0.12",
"unescape": "1.0.1",
"ws": "7.4.4",
"yauzl": "2.10.0",

View File

@@ -87,14 +87,16 @@ describe("Lexer expression", () => {
.toEqual(["#label", "*=*", "text"]);
});
it("simple label operator with in quotes and without", () => {
it("simple label operator with in quotes", () => {
expect(lex("#label*=*'text'").expressionTokens)
.toEqual([
{token: "#label", inQuotes: false, startIndex: 0, endIndex: 5},
{token: "*=*", inQuotes: false, startIndex: 6, endIndex: 8},
{token: "text", inQuotes: true, startIndex: 10, endIndex: 13}
]);
});
it("simple label operator with param without quotes", () => {
expect(lex("#label*=*text").expressionTokens)
.toEqual([
{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", () => {
expect(lex(`hello fulltext note.labels.capital = Prague`).expressionTokens.map(t => t.token))
.toEqual(["note", ".", "labels", ".", "capital", "=", "prague"]);

View File

@@ -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", () => {
it("fulltext parser without content", () => {
const rootExp = parse({
@@ -29,8 +36,9 @@ describe("Parser", () => {
expect(rootExp.constructor.name).toEqual("AndExp");
expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp");
expect(rootExp.subExpressions[1].constructor.name).toEqual("NoteCacheFlatTextExp");
expect(rootExp.subExpressions[1].tokens).toEqual(["hello", "hi"]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
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", () => {
@@ -40,9 +48,12 @@ describe("Parser", () => {
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].tokens).toEqual(["hello", "hi"]);
@@ -61,10 +72,12 @@ describe("Parser", () => {
searchContext: new SearchContext()
});
expect(rootExp.constructor.name).toEqual("LabelComparisonExp");
expect(rootExp.attributeType).toEqual("label");
expect(rootExp.attributeName).toEqual("mylabel");
expect(rootExp.comparator).toBeTruthy();
expect(rootExp.constructor.name).toEqual("AndExp");
assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("LabelComparisonExp");
expect(rootExp.subExpressions[1].attributeType).toEqual("label");
expect(rootExp.subExpressions[1].attributeName).toEqual("mylabel");
expect(rootExp.subExpressions[1].comparator).toBeTruthy();
});
it("simple attribute negation", () => {
@@ -74,10 +87,12 @@ describe("Parser", () => {
searchContext: new SearchContext()
});
expect(rootExp.constructor.name).toEqual("NotExp");
expect(rootExp.subExpression.constructor.name).toEqual("AttributeExistsExp");
expect(rootExp.subExpression.attributeType).toEqual("label");
expect(rootExp.subExpression.attributeName).toEqual("mylabel");
expect(rootExp.constructor.name).toEqual("AndExp");
assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("NotExp");
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({
fulltextTokens: [],
@@ -85,10 +100,12 @@ describe("Parser", () => {
searchContext: new SearchContext()
});
expect(rootExp.constructor.name).toEqual("NotExp");
expect(rootExp.subExpression.constructor.name).toEqual("AttributeExistsExp");
expect(rootExp.subExpression.attributeType).toEqual("relation");
expect(rootExp.subExpression.attributeName).toEqual("myrelation");
expect(rootExp.constructor.name).toEqual("AndExp");
assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("NotExp");
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", () => {
@@ -99,7 +116,10 @@ describe("Parser", () => {
});
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.attributeName).toEqual("first");
@@ -116,7 +136,10 @@ describe("Parser", () => {
});
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.attributeName).toEqual("first");
@@ -132,8 +155,11 @@ describe("Parser", () => {
searchContext: new SearchContext()
});
expect(rootExp.constructor.name).toEqual("OrExp");
const [firstSub, secondSub] = rootExp.subExpressions;
expect(rootExp.constructor.name).toEqual("AndExp");
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.attributeName).toEqual("first");
@@ -155,8 +181,9 @@ describe("Parser", () => {
expect(firstSub.constructor.name).toEqual("PropertyComparisonExp");
expect(firstSub.propertyName).toEqual('isArchived');
expect(secondSub.constructor.name).toEqual("NoteCacheFlatTextExp");
expect(secondSub.tokens).toEqual(["hello"]);
expect(secondSub.constructor.name).toEqual("OrExp");
expect(secondSub.subExpressions[0].constructor.name).toEqual("NoteCacheFlatTextExp");
expect(secondSub.subExpressions[0].tokens).toEqual(["hello"]);
expect(thirdSub.constructor.name).toEqual("LabelComparisonExp");
expect(thirdSub.attributeName).toEqual("mylabel");
@@ -169,8 +196,11 @@ describe("Parser", () => {
searchContext: new SearchContext()
});
expect(rootExp.constructor.name).toEqual("OrExp");
const [firstSub, secondSub] = rootExp.subExpressions;
expect(rootExp.constructor.name).toEqual("AndExp");
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.attributeName).toEqual("first");
@@ -232,10 +262,12 @@ describe("Invalid expressions", () => {
searchContext: new SearchContext()
});
expect(rootExp.constructor.name).toEqual("LabelComparisonExp");
expect(rootExp.attributeType).toEqual("label");
expect(rootExp.attributeName).toEqual("first");
expect(rootExp.comparator).toBeTruthy();
expect(rootExp.constructor.name).toEqual("AndExp");
assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("LabelComparisonExp");
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", () => {

View File

@@ -562,8 +562,8 @@ describe("Search", () => {
expect(noteCache.notes[searchResults[0].noteId].title).toEqual("Austria");
expect(noteCache.notes[searchResults[1].noteId].title).toEqual("Italy");
searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 0', searchContext);
expect(searchResults.length).toEqual(0);
searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 1', searchContext);
expect(searchResults.length).toEqual(1);
searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 1000', searchContext);
expect(searchResults.length).toEqual(4);

View File

@@ -1,6 +1,9 @@
const {note} = require('./note_cache_mocking.js');
const ValueExtractor = require('../../src/services/search/value_extractor.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", () => {
beforeEach(() => {
@@ -10,7 +13,7 @@ describe("Value extractor", () => {
it("simple title extraction", async () => {
const europe = note("Europe").note;
const valueExtractor = new ValueExtractor(["note", "title"]);
const valueExtractor = new ValueExtractor(dsc, ["note", "title"]);
expect(valueExtractor.validate()).toBeFalsy();
expect(valueExtractor.extract(europe)).toEqual("Europe");
@@ -21,12 +24,12 @@ describe("Value extractor", () => {
.label("Capital", "Vienna")
.note;
let valueExtractor = new ValueExtractor(["note", "labels", "capital"]);
let valueExtractor = new ValueExtractor(dsc, ["note", "labels", "capital"]);
expect(valueExtractor.validate()).toBeFalsy();
expect(valueExtractor.extract(austria)).toEqual("Vienna");
valueExtractor = new ValueExtractor(["#capital"]);
valueExtractor = new ValueExtractor(dsc, ["#capital"]);
expect(valueExtractor.validate()).toBeFalsy();
expect(valueExtractor.extract(austria)).toEqual("Vienna");
@@ -38,12 +41,12 @@ describe("Value extractor", () => {
.child(note("Austria")
.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.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.extract(vienna.note)).toEqual("Europe");
@@ -56,12 +59,12 @@ describe("Value extractor", () => {
.relation('neighbor', czechRepublic.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.extract(austria.note)).toEqual("Prague");
valueExtractor = new ValueExtractor(["~neighbor", "labels", "capital"]);
valueExtractor = new ValueExtractor(dsc, ["~neighbor", "labels", "capital"]);
expect(valueExtractor.validate()).toBeFalsy();
expect(valueExtractor.extract(austria.note)).toEqual("Prague");
@@ -70,17 +73,17 @@ describe("Value extractor", () => {
describe("Invalid value extractor property path", () => {
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",
() => 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",
() => 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",
() => 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",
() => expect(new ValueExtractor(["note", "relations", "myrel"]).validate()).toBeTruthy());
() => expect(new ValueExtractor(dsc, ["note", "relations", "myrel"]).validate()).toBeTruthy());
});

View File

@@ -49,7 +49,12 @@ class Note extends Entity {
this.isContentAvailable = protectedSessionService.isProtectedSessionAvailable();
if (this.isContentAvailable) {
this.title = protectedSessionService.decryptString(this.title);
try {
this.title = protectedSessionService.decryptString(this.title);
}
catch (e) {
throw new Error(`Could not decrypt title of note ${this.noteId}: ${e.message} ${e.stack}`)
}
}
else {
this.title = "[protected]";
@@ -365,7 +370,6 @@ class Note extends Entity {
WHERE attributes.isDeleted = 0
AND attributes.type = 'relation'
AND attributes.name = 'template'
AND (treeWithAttrs.level = 0 OR attributes.isInheritable = 1)
)
SELECT attributes.* FROM attributes JOIN treeWithAttrs ON attributes.noteId = treeWithAttrs.noteId
WHERE attributes.isDeleted = 0 AND (attributes.isInheritable = 1 OR treeWithAttrs.level = 0)

View File

@@ -359,7 +359,7 @@ class NoteShort {
const workspaceIconClass = this.getWorkspaceIconClass();
if (iconClassLabels.length > 0) {
return iconClassLabels.map(l => l.value).join(' ');
return iconClassLabels[0].value;
}
else if (workspaceIconClass) {
return workspaceIconClass;

View File

@@ -1,5 +1,6 @@
import utils from "./utils.js";
import options from './options.js';
import server from "./server.js";
const PROTECTED_SESSION_ID_KEY = 'protectedSessionId';
@@ -23,11 +24,11 @@ function resetSessionCookie() {
utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, null);
}
function resetProtectedSession() {
async function resetProtectedSession() {
resetSessionCookie();
// most secure solution - guarantees nothing remained in memory
// since this expires because user doesn't use the app, it shouldn't be disruptive
await server.post("logout/protected");
utils.reloadApp();
}

View File

@@ -382,8 +382,6 @@ export default class NoteTreeWidget extends TabAwareWidget {
}
else {
node.setActive();
this.clearSelectedNodes();
}
return false;
@@ -393,6 +391,8 @@ export default class NoteTreeWidget extends TabAwareWidget {
// click event won't propagate so let's close context menu manually
contextMenu.hide();
this.clearSelectedNodes();
const notePath = treeService.getNotePath(data.node);
const activeTabContext = appContext.tabManager.getActiveTabContext();
@@ -1144,11 +1144,12 @@ export default class NoteTreeWidget extends TabAwareWidget {
}
if (node) {
node.setActive(true, {noEvents: true, noFocus: !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 {
// this is used when original note has been deleted and we want to move the focus to the note above/below

View File

@@ -5,7 +5,7 @@ const TPL = `
<td colspan="2">
<span class="bx bx-trash"></span>
Delete matched note
Delete matched notes
</td>
<td class="button-column">
<span class="bx bx-x icon-action action-conf-del"></span>

View File

@@ -14,6 +14,10 @@ const TPL = `
height: 35px;
}
.standard-top-widget > div {
flex-shrink: 0; /* fixes https://github.com/zadam/trilium/issues/1745 */
}
.standard-top-widget button.noborder {
padding: 1px 5px 1px 5px;
font-size: 90%;

View File

@@ -5,24 +5,47 @@ import linkService from "../../services/link.js";
import noteContentRenderer from "../../services/note_content_renderer.js";
export default class AbstractTextTypeWidget extends TypeWidget {
doRender() {
this.$widget.on("dblclick", "img", e => {
const $img = $(e.target);
const src = $img.prop("src");
setupImageOpening(singleClickOpens) {
this.$widget.on("dblclick", "img", e => this.openImageInCurrentTab($(e.target)));
const match = src.match(/\/api\/images\/([A-Za-z0-9]+)\//);
if (match) {
const noteId = match[1];
appContext.tabManager.getActiveTabContext().setNote(noteId);
this.$widget.on("click", "img", e => {
if ((e.which === 1 && e.ctrlKey) || e.which === 2) {
this.openImageInNewTab($(e.target));
}
else {
window.open(src, '_blank');
else if (e.which === 1 && singleClickOpens) {
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) {
const note = await treeCache.getNote(noteId);

View File

@@ -84,6 +84,8 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
keyboardActionService.setupActionsForElement('text-detail', this.$widget, this);
this.setupImageOpening(false);
super.doRender();
}

View File

@@ -34,6 +34,7 @@ const TPL = `
.note-detail-readonly-text img {
max-width: 100%;
cursor: pointer;
}
.edit-text-note-container {
@@ -66,6 +67,8 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
this.triggerEvent('textPreviewDisabled', {tabContext: this.tabContext});
});
this.setupImageOpening(true);
super.doRender();
}

View File

@@ -107,7 +107,11 @@ function processContent(images, note, content) {
const filename = path.basename(src);
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;
}

View File

@@ -78,6 +78,12 @@ function loginToProtectedSession(req) {
};
}
function logoutFromProtectedSession() {
protectedSessionService.resetDataKey();
eventService.emit(eventService.LEAVE_PROTECTED_SESSION);
}
function token(req) {
const username = req.body.username;
const password = req.body.password;
@@ -101,5 +107,6 @@ function token(req) {
module.exports = {
loginSync,
loginToProtectedSession,
logoutFromProtectedSession,
token
};

View File

@@ -200,9 +200,7 @@ function queueSector(req) {
const entityName = utils.sanitizeSqlIdentifier(req.params.entityName);
const sector = utils.sanitizeSqlIdentifier(req.params.sector);
const entityPrimaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName;
entityChangesService.addEntityChangesForSector(entityName, entityPrimaryKey, sector);
entityChangesService.addEntityChangesForSector(entityName, sector);
}
module.exports = {

View File

@@ -270,6 +270,8 @@ function register(app) {
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)
apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession);
apiRoute(POST, '/api/logout/protected', loginApiRoute.logoutFromProtectedSession);
route(POST, '/api/login/token', [], loginApiRoute.token, apiResultHandler);
// in case of local electron, local calls are allowed unauthenticated, for server they need auth

View File

@@ -1 +1 @@
module.exports = { buildDate:"2021-03-23T23:57:48+01:00", buildRevision: "1862acd1ff9261536ebc7118d35bbbd8449ba94d" };
module.exports = { buildDate:"2021-04-22T20:50:22+02:00", buildRevision: "c27f573eed8905fc1d958adf5bdee0efc3aff593" };

View File

@@ -53,22 +53,14 @@ function moveEntityChangeToTop(entityName, entityId) {
addEntityChange(entityName, entityId, hash, null, isSynced);
}
function addEntityChangesForSector(entityName, entityPrimaryKey, sector) {
function addEntityChangesForSector(entityName, sector) {
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(() => {
const entityIds = sql.getColumn(`SELECT ${entityPrimaryKey} FROM ${entityName} WHERE SUBSTR(${entityPrimaryKey}, 1, 1) = ?`, [sector]);
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);
for (const ec of entityChanges) {
insertEntityChange(entityName, ec.entityId, ec.hash, ec.isErased, ec.utcDateChanged, ec.sourceId, ec.isSynced);
}
});

View File

@@ -2,6 +2,7 @@ const log = require('./log');
const NOTE_TITLE_CHANGED = "NOTE_TITLE_CHANGED";
const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION";
const LEAVE_PROTECTED_SESSION = "LEAVE_PROTECTED_SESSION";
const ENTITY_CREATED = "ENTITY_CREATED";
const ENTITY_CHANGED = "ENTITY_CHANGED";
const ENTITY_DELETED = "ENTITY_DELETED";
@@ -47,6 +48,7 @@ module.exports = {
// event types:
NOTE_TITLE_CHANGED,
ENTER_PROTECTED_SESSION,
LEAVE_PROTECTED_SESSION,
ENTITY_CREATED,
ENTITY_CHANGED,
ENTITY_DELETED,

View File

@@ -1,6 +1,7 @@
"use strict";
const protectedSessionService = require('../../protected_session');
const log = require('../../log');
class Note {
constructor(noteCache, row) {
@@ -416,9 +417,14 @@ class Note {
decrypt() {
if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
this.title = protectedSessionService.decryptString(this.title);
try {
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}`);
}
}
}

View File

@@ -177,6 +177,10 @@ eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => {
}
});
eventService.subscribe(eventService.LEAVE_PROTECTED_SESSION, () => {
load();
});
module.exports = {
load
};

View File

@@ -5,7 +5,7 @@ const log = require('./log');
const dataEncryptionService = require('./data_encryption');
const cls = require('./cls');
const dataKeyMap = {};
let dataKeyMap = {};
function setDataKey(decryptedDataKey) {
const protectedSessionId = utils.randomSecureToken(32);
@@ -29,6 +29,10 @@ function getDataKey() {
return dataKeyMap[protectedSessionId];
}
function resetDataKey() {
dataKeyMap = {};
}
function isProtectedSessionAvailable() {
const protectedSessionId = getProtectedSessionId();
@@ -71,6 +75,7 @@ function decryptString(cipherText) {
module.exports = {
setDataKey,
getDataKey,
resetDataKey,
isProtectedSessionAvailable,
encrypt,
decrypt,

View File

@@ -21,8 +21,8 @@ function lex(str) {
}
}
function finishWord(endIndex) {
if (currentWord === '') {
function finishWord(endIndex, createAlsoForEmptyWords = false) {
if (currentWord === '' && !createAlsoForEmptyWords) {
return;
}
@@ -71,7 +71,7 @@ function lex(str) {
}
}
else if (quotes === chr) {
finishWord(i - 1);
finishWord(i - 1, true);
quotes = false;
}

View File

@@ -245,9 +245,7 @@ async function checkContentHash(syncContext) {
const failedChecks = contentHashService.checkContentHashes(resp.entityHashes);
for (const {entityName, sector} of failedChecks) {
const entityPrimaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName;
entityChangesService.addEntityChangesForSector(entityName, entityPrimaryKey, sector);
entityChangesService.addEntityChangesForSector(entityName, sector);
await syncRequest(syncContext, 'POST', `/api/sync/queue-sector/${entityName}/${sector}`);
}

View File

@@ -1,6 +1,7 @@
"use strict";
const sql = require('./sql');
const log = require('./log');
const repository = require('./repository');
const Branch = require('../entities/branch');
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 = ?",
[position, note.branchId]);
noteCache.branches[note.branchId].notePosition = position;
if (note.branchId in noteCache.branches) {
noteCache.branches[note.branchId].notePosition = position;
}
else {
log.info(`Branch "${note.branchId}" was not found in note cache.`);
}
position += 10;
}

View File

@@ -7,6 +7,9 @@
<sourceFolder url="file://$MODULE_DIR$/src/public" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/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>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />