Compare commits

...

29 Commits

Author SHA1 Message Date
azivner
08e062ab34 release 0.24.4-beta 2018-11-21 23:47:09 +01:00
azivner
3a06493459 partial workaround for the broken in page search (next & previous don't work, but at least highlighting works) 2018-11-21 23:39:19 +01:00
azivner
8159564885 fix .desktop + allow to turn this feature off 2018-11-21 11:19:33 +01:00
azivner
8ce3c1a480 generate local .desktop file so it shows among apps in linux desktop environments 2018-11-21 11:01:03 +01:00
azivner
dbc93f4a79 mitigations for tooltip flickering 2018-11-20 23:50:19 +01:00
azivner
92ffe321aa smaller context menu 2018-11-20 22:55:07 +01:00
azivner
6cb7d0098e changed layout a little bit to fix broken children overview in the electron/chrome 2018-11-20 22:49:10 +01:00
azivner
bdcb4361b2 simplification of note autocomplete result ordering by depth #240 2018-11-20 22:22:26 +01:00
azivner
15366d37d7 ordering results in autocomplete based on depth, closes #240 2018-11-20 21:22:20 +01:00
azivner
acd001501b release 0.24.3-beta 2018-11-20 13:01:41 +01:00
azivner
0019865807 disabling broken in page search 2018-11-20 12:54:03 +01:00
azivner
137ffcc4e3 enable line wrap for code editor 2018-11-20 10:49:03 +01:00
azivner
585398ad5c fix bug when saving non-text notes 2018-11-19 23:11:36 +01:00
azivner
50401954d1 disable cache for attribute name autocomplete 2018-11-19 22:17:08 +01:00
azivner
32a9df8489 fixes (and refactoring) for "empty attribute name" check 2018-11-19 22:06:51 +01:00
azivner
5bf5d1cac4 attribute dialog doesn't allow to (attempt to) save relation without target note 2018-11-19 21:58:52 +01:00
azivner
3608857f25 small fix to demo export 2018-11-19 21:09:59 +01:00
azivner
16a1dc12df jsdoc now doesn't include date into files to avoid unnecessary noise 2018-11-19 21:03:43 +01:00
azivner
9c834229b9 release 0.24.2-beta 2018-11-19 17:17:08 +01:00
azivner
3fd45b15e7 set icon and app name, closes #201 2018-11-19 17:16:22 +01:00
azivner
f20ab45576 fix absolute image paths to relative paths as part of parsing links 2018-11-19 15:00:49 +01:00
azivner
77a89d85c8 fix "copy image to clipboard" button 2018-11-19 12:12:58 +01:00
azivner
30249a353e renamed "mirror" relation to "inverse" relation 2018-11-19 12:07:33 +01:00
azivner
eb9bae9010 updated relation map in demo document 2018-11-19 11:47:40 +01:00
azivner
0c7ae527c5 allow dragging only one note at a time 2018-11-19 11:19:56 +01:00
azivner
fef4705e2f center button changed to "reset pan & zoom" 2018-11-19 11:14:55 +01:00
azivner
568c2c997f possibility to export single code note as markdown. UI fix of disabled buttons 2018-11-19 09:54:33 +01:00
azivner
d6b5cd6ead support saving files and images in markdown tar export 2018-11-19 09:46:24 +01:00
azivner
00ce379962 excludeFromExport support also in OPML 2018-11-19 09:34:05 +01:00
70 changed files with 467 additions and 216 deletions

View File

@@ -18,6 +18,7 @@ See other pictures in [screenshot tour](https://github.com/zadam/trilium/wiki/Sc
* Note [attributes](https://github.com/zadam/trilium/wiki/Attributes) can be used for note organization, querying and advanced [scripting](https://github.com/zadam/trilium/wiki/Scripts)
* [Synchronization](https://github.com/zadam/trilium/wiki/Synchronization) with self-hosted sync server
* Strong [note encryption](https://github.com/zadam/trilium/wiki/Protected-notes)
* [Relation maps](https://github.com/zadam/trilium/wiki/Relation-map) for visualizing notes and their relations
* [Scripting](https://github.com/zadam/trilium/wiki/Scripts) - see [Advanced showcases](https://github.com/zadam/trilium/wiki/Advanced-showcases)
* Scales well in both usability and performance upwards of 100 000 notes
* [Night theme](https://github.com/zadam/trilium/wiki/Themes)
@@ -31,10 +32,6 @@ Trilium is provided as either desktop application ([Electron](https://electronjs
* If you want to install Trilium on server, follow [this page](https://github.com/zadam/trilium/wiki/Server-installation).
* Currently only recent Chrome and Firefox are supported (tested) browsers.
## Status
Trilium is beta quality software. While it is reasonably feature complete and is tested by its author, it lacks proper testing by more users. It's not yet recommended for daily use, but testing and experimentation is encouraged.
## Documentation
[See wiki for complete list of documentation pages.](https://github.com/zadam/trilium/wiki/)

View File

@@ -11,15 +11,21 @@ rm -r dist/*
echo "Rebuilding binaries for linux-ia32"
./node_modules/.bin/electron-rebuild --arch=ia32
./node_modules/.bin/electron-packager . --out=dist --platform=linux --arch=ia32 --overwrite
./node_modules/.bin/electron-packager . --out=dist --executable-name=trilium --platform=linux --arch=ia32 --overwrite
./node_modules/.bin/electron-packager . --out=dist --platform=win32 --arch=x64 --overwrite
mv "./dist/Trilium Notes-linux-ia32" ./dist/trilium-linux-ia32
./node_modules/.bin/electron-packager . --out=dist --executable-name=trilium --platform=win32 --arch=x64 --overwrite --icon=src/public/images/app-icons/win/icon.ico
mv "./dist/Trilium Notes-win32-x64" ./dist/trilium-win32-x64
# we build x64 as second so that we keep X64 binaries in node_modules for local development and server build
echo "Rebuilding binaries for linux-x64"
./node_modules/.bin/electron-rebuild --arch=x64
./node_modules/.bin/electron-packager . --out=dist --platform=linux --arch=x64 --overwrite
./node_modules/.bin/electron-packager . --out=dist --executable-name=trilium --platform=linux --arch=x64 --overwrite
mv "./dist/Trilium Notes-linux-x64" ./dist/trilium-linux-x64
echo "Copying required windows binaries"

Binary file not shown.

View File

@@ -0,0 +1 @@
UPDATE attributes SET value = replace(value, 'mirrorRelation', 'inverseRelation') WHERE type = 'relation-definition';

View File

@@ -288,7 +288,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -730,7 +730,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -3814,7 +3814,7 @@ transactional by default.
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -511,7 +511,7 @@ Each note can have multiple (at least one) branches, meaning it can be placed in
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -216,7 +216,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -358,7 +358,7 @@ this is different concept than attribute/relation.</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -7297,7 +7297,7 @@ Cache is note instance scoped.
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -403,7 +403,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -311,7 +311,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -288,7 +288,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -75,7 +75,7 @@ module.exports = ApiToken;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -151,7 +151,7 @@ module.exports = Attribute;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -105,7 +105,7 @@ module.exports = Branch;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -93,7 +93,7 @@ module.exports = Entity;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -92,7 +92,7 @@ module.exports = Link;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -651,7 +651,7 @@ module.exports = Note;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -91,7 +91,7 @@ module.exports = NoteRevision;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -78,7 +78,7 @@ module.exports = Option;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -75,7 +75,7 @@ module.exports = RecentNote;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -594,7 +594,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -56,7 +56,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -278,7 +278,7 @@ module.exports = BackendScriptApi;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:27 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -719,7 +719,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -2846,7 +2846,7 @@ Internally this serializes the anonymous function into string and sends it to ba
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -279,7 +279,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -1316,7 +1316,7 @@ Its notable omission is the note content.</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -76,7 +76,7 @@ export default Branch;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -64,7 +64,7 @@ export default NoteFull;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -128,7 +128,7 @@ export default NoteShort;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -339,7 +339,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -56,7 +56,7 @@
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -271,7 +271,7 @@ export default FrontendScriptApi;</code></pre>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Thu Nov 15 2018 13:33:28 GMT+0100 (Central European Standard Time)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script> prettyPrint(); </script>

View File

@@ -6,6 +6,7 @@ const log = require('./src/services/log');
const cls = require('./src/services/cls');
const url = require("url");
const port = require('./src/services/port');
const appIconService = require('./src/services/app_icon');
const app = electron.app;
const globalShortcut = electron.globalShortcut;
@@ -13,6 +14,8 @@ const globalShortcut = electron.globalShortcut;
// Adds debug features like hotkeys for triggering dev tools and reload
require('electron-debug')();
appIconService.installLocalAppIcon();
// Prevent window being garbage collected
let mainWindow;
@@ -70,6 +73,8 @@ app.on('activate', () => {
});
app.on('ready', async () => {
app.setAppUserModelId('com.github.zadam.trilium');
mainWindow = await createMainWindow();
const result = globalShortcut.register('CommandOrControl+Alt+P', cls.wrap(async () => {

7
jsdoc-conf.json Normal file
View File

@@ -0,0 +1,7 @@
{
"templates": {
"default": {
"includeDate": false
}
}
}

8
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "trilium",
"version": "0.24.0-beta",
"version": "0.24.3-beta",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -10821,9 +10821,9 @@
}
},
"ws": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.0.tgz",
"integrity": "sha512-H3dGVdGvW2H8bnYpIDc3u3LH8Wue3Qh+Zto6aXXFzvESkTVT6rAfKR6tR/+coaUvxs8yHtmNV0uioBF62ZGSTg==",
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz",
"integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==",
"requires": {
"async-limiter": "~1.0.0"
}

View File

@@ -1,7 +1,8 @@
{
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.24.1-beta",
"version": "0.24.4-beta",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -13,12 +14,9 @@
},
"scripts": {
"start": "node ./src/www",
"test-electron": "xo",
"rebuild-electron": "electron-rebuild",
"start-electron": "electron . --disable-gpu",
"build-electron": "electron-packager . --out=dist --asar --overwrite --platform=win32,linux --arch=ia32,x64 --app-version= --icon=src/public/app-icons/win/icon.ico",
"build-backend-docs": "jsdoc -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js",
"build-frontend-docs": "jsdoc -d ./docs/frontend_api src/public/javascripts/entities/*.js src/public/javascripts/services/frontend_script_api.js",
"build-backend-docs": "jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js",
"build-frontend-docs": "jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/javascripts/entities/*.js src/public/javascripts/services/frontend_script_api.js",
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs"
},
"dependencies": {
@@ -64,7 +62,7 @@
"tar-stream": "1.6.2",
"turndown": "5.0.1",
"unescape": "1.0.1",
"ws": "6.1.0",
"ws": "6.1.2",
"xml2js": "0.4.19"
},
"devDependencies": {

View File

@@ -72,7 +72,7 @@ function AttributesModel() {
attr.relationDefinition = (attr.type === 'relation-definition' && attr.value) ? attr.value : {
multiplicityType: "singlevalue",
mirrorRelation: "",
inverseRelation: "",
isPromoted: true
};
@@ -114,7 +114,7 @@ function AttributesModel() {
function isValid() {
for (let attributes = self.ownedAttributes(), i = 0; i < attributes.length; i++) {
if (self.isEmptyName(i)) {
if (self.isEmptyName(i) || self.isEmptyRelationTarget(i)) {
return false;
}
}
@@ -191,7 +191,7 @@ function AttributesModel() {
},
relationDefinition: {
multiplicityType: "singlevalue",
mirrorRelation: "",
inverseRelation: "",
isPromoted: true
}
}));
@@ -209,7 +209,35 @@ function AttributesModel() {
this.isEmptyName = function(index) {
const cur = self.ownedAttributes()[index]();
return cur.name.trim() === "" && !cur.isDeleted && (cur.attributeId !== "" || cur.labelValue !== "" || cur.relationValue);
if (cur.name.trim() || cur.isDeleted) {
return false;
}
if (cur.attributeId) {
// name is empty and attribute already exists so this is NO-GO
return true;
}
if (cur.type === 'relation-definition' || cur.type === 'label-definition') {
// for definitions there's no possible empty value so we always require name
return true;
}
if (cur.type === 'label' && cur.labelValue) {
return true;
}
if (cur.type === 'relation' && cur.relationValue) {
return true;
}
return false;
};
this.isEmptyRelationTarget = function(index) {
const cur = self.ownedAttributes()[index]();
return cur.type === "relation" && !cur.isDeleted && cur.name && !cur.relationValue;
};
this.getTargetAttribute = function(target) {

View File

@@ -15,6 +15,8 @@ function initAttributeNameAutocomplete({ $el, attributeType, open }) {
minLength: 0
}, [{
displayKey: 'name',
// disabling cache is important here because otherwise cache can stay intact when switching between attribute type which will lead to autocomplete displaying attribute names for incorrect attribute type
cache: false,
source: async (term, cb) => {
const type = typeof attributeType === "function" ? attributeType() : attributeType;

View File

@@ -103,7 +103,13 @@ if (utils.isElectron()) {
});
}
$("#export-note-to-markdown-button").click(() => exportService.exportSubtree(noteDetailService.getCurrentNoteId(), 'markdown-single'));
$("#export-note-to-markdown-button").click(function () {
if ($(this).hasClass("disabled")) {
return;
}
exportService.exportSubtree(noteDetailService.getCurrentNoteId(), 'markdown-single')
});
treeService.showTree();

View File

@@ -10,15 +10,13 @@ const dragAndDropSetup = {
node.setSelected(true);
const selectedNodes = treeService.getSelectedNodes().map(node => {
return {
noteId: node.data.noteId,
title: node.title
}
});
// this is for dragging notes into relation map
data.dataTransfer.setData("text", JSON.stringify(selectedNodes));
// we allow to drag only one note at a time because it multi-drag conflicts with multiple single drags
// in UX and single drag is probably more useful
data.dataTransfer.setData("text", JSON.stringify({
noteId: node.data.noteId,
title: node.title
}));
// This function MUST be defined to enable dragging for the tree.
// Return false to cancel dragging of node.

View File

@@ -25,9 +25,21 @@ function registerEntrypoints() {
$("#jump-to-note-dialog-button").click(jumpToNoteDialog.showDialog);
utils.bindShortcut('ctrl+j', jumpToNoteDialog.showDialog);
$("#show-note-revisions-button").click(noteRevisionsDialog.showCurrentNoteRevisions);
$("#show-note-revisions-button").click(function() {
if ($(this).hasClass("disabled")) {
return;
}
$("#show-source-button").click(noteSourceDialog.showDialog);
noteRevisionsDialog.showCurrentNoteRevisions();
});
$("#show-source-button").click(function() {
if ($(this).hasClass("disabled")) {
return;
}
noteSourceDialog.showDialog();
});
$("#recent-changes-button").click(recentChangesDialog.showDialog);
@@ -85,10 +97,17 @@ function registerEntrypoints() {
$(document).bind('keydown', 'ctrl+f', () => {
if (utils.isElectron()) {
const searchInPage = require('electron-in-page-search').default;
const remote = require('electron').remote;
const $searchWindowWebview = $(".electron-in-page-search-window");
$searchWindowWebview.show();
const inPageSearch = searchInPage(remote.getCurrentWebContents());
const searchInPage = require('electron-in-page-search').default;
const {remote} = require('electron');
const inPageSearch = searchInPage(remote.getCurrentWebContents(), {
searchWindowWebview: $searchWindowWebview[0],
//openDevToolsOfSearchWindow: true,
customCssPath: '/libraries/electron-in-page-search/default-style.css'
});
inPageSearch.openSearchWindow();

View File

@@ -92,7 +92,7 @@ function initNoteAutocomplete($el, options) {
$el.on('autocomplete:selected', (event, suggestion) => $el.setSelectedPath(suggestion.path));
$el.on('autocomplete:closed', () => {
if (!$el.val().trim()) {
$el.setSelectedPath("");
clearText($el);
}
});
}

View File

@@ -32,7 +32,10 @@ async function show() {
lint: true,
gutters: ["CodeMirror-lint-markers"],
lineNumbers: true,
tabindex: 100
tabindex: 100,
// we linewrap partly also because without it horizontal scrollbar displays only when you scroll
// all the way to the bottom of the note. With line wrap there's no horizontal scrollbar so no problem
lineWrapping: true
});
onNoteChange(noteDetailService.noteChanged);
@@ -43,7 +46,9 @@ async function show() {
const currentNote = noteDetailService.getCurrentNote();
// this needs to happen after the element is shown, otherwise the editor won't be refreshed
codeEditor.setValue(currentNote.content);
// CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check)
// we provide fallback
codeEditor.setValue(currentNote.content || "");
const info = CodeMirror.findModeByMIME(currentNote.mime);

View File

@@ -5,6 +5,7 @@ import infoService from "./info.js";
import server from "./server.js";
const $component = $('#note-detail-image');
const $imageWrapper = $('#note-detail-image-wrapper');
const $imageView = $('#note-detail-image-view');
const $imageDownloadButton = $("#image-download");
@@ -39,10 +40,10 @@ function selectImage(element) {
}
$copyToClipboardButton.click(() => {
$component.attr('contenteditable','true');
$imageWrapper.attr('contenteditable','true');
try {
selectImage($component.get(0));
selectImage($imageWrapper.get(0));
const success = document.execCommand('copy');
@@ -55,7 +56,7 @@ $copyToClipboardButton.click(() => {
}
finally {
window.getSelection().removeAllRanges();
$component.removeAttr('contenteditable');
$imageWrapper.removeAttr('contenteditable');
}
});

View File

@@ -15,7 +15,7 @@ const $relationMapContainer = $("#relation-map-container");
const $createChildNote = $("#relation-map-create-child-note");
const $zoomInButton = $("#relation-map-zoom-in");
const $zoomOutButton = $("#relation-map-zoom-out");
const $centerButton = $("#relation-map-center");
const $resetPanZoomButton = $("#relation-map-reset-pan-zoom");
let mapData;
let jsPlumbInstance;
@@ -50,7 +50,7 @@ const biDirectionalOverlays = [
} ]
];
const mirrorOverlays = [
const inverseRelationsOverlays = [
[ "Arrow", {
location: 1,
id: "arrow",
@@ -134,12 +134,12 @@ async function loadNotesAndRelations() {
for (const relation of data.relations) {
const match = relations.find(rel =>
rel.name === data.mirrorRelations[relation.name]
rel.name === data.inverseRelations[relation.name]
&& ((rel.sourceNoteId === relation.sourceNoteId && rel.targetNoteId === relation.targetNoteId)
|| (rel.sourceNoteId === relation.targetNoteId && rel.targetNoteId === relation.sourceNoteId)));
if (match) {
match.type = relation.type = relation.name === data.mirrorRelations[relation.name] ? 'biDirectional' : 'mirror';
match.type = relation.type = relation.name === data.inverseRelations[relation.name] ? 'biDirectional' : 'inverse';
relation.render = false; // don't render second relation
} else {
relation.type = 'uniDirectional';
@@ -173,9 +173,9 @@ async function loadNotesAndRelations() {
connection.id = relation.attributeId;
if (relation.type === 'mirror') {
if (relation.type === 'inverse') {
connection.getOverlay("label-source").setLabel(relation.name);
connection.getOverlay("label-target").setLabel(data.mirrorRelations[relation.name]);
connection.getOverlay("label-target").setLabel(data.inverseRelations[relation.name]);
}
else {
connection.getOverlay("label").setLabel(relation.name);
@@ -240,6 +240,10 @@ function initPanZoom() {
pzInstance.moveTo(mapData.transform.x, mapData.transform.y);
}
else {
// set to initial coordinates
pzInstance.moveTo(0, 0);
}
$zoomInButton.click(() => pzInstance.zoomTo(0, 0, 1.2));
$zoomOutButton.click(() => pzInstance.zoomTo(0, 0, 0.8));
@@ -286,7 +290,7 @@ function initJsPlumbInstance () {
jsPlumbInstance.registerConnectionType("biDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: biDirectionalOverlays });
jsPlumbInstance.registerConnectionType("mirror", { anchor:"Continuous", connector:"StateMachine", overlays: mirrorOverlays });
jsPlumbInstance.registerConnectionType("inverse", { anchor:"Continuous", connector:"StateMachine", overlays: inverseRelationsOverlays });
jsPlumbInstance.registerConnectionType("link", { anchor:"Continuous", connector:"StateMachine", overlays: linkOverlays });
@@ -518,43 +522,20 @@ function getZoom() {
async function dropNoteOntoRelationMapHandler(ev) {
ev.preventDefault();
const notes = JSON.parse(ev.originalEvent.dataTransfer.getData("text"));
const note = JSON.parse(ev.originalEvent.dataTransfer.getData("text"));
let {x, y} = getMousePosition(ev);
// modifying position so that cursor is on the top-center of the box
const startX = x -= 80;
y -= 15;
const exists = mapData.notes.some(n => n.noteId === note.noteId);
const currentNoteId = treeService.getCurrentNode().data.noteId;
if (exists) {
await infoDialog.info(`Note "${note.title}" is already placed into the diagram`);
for (const note of notes) {
if (note.noteId === currentNoteId) {
// we don't allow placing current (relation map) into itself
// the reason is that when dragging notes from the tree, the relation map is always selected
// since it's focused.
continue;
}
const exists = mapData.notes.some(n => n.noteId === note.noteId);
if (exists) {
await infoDialog.info(`Note "${note.title}" is already placed into the diagram`);
continue;
}
mapData.notes.push({noteId: note.noteId, x, y});
if (x - startX > 1000) {
x = startX;
y += 200;
}
else {
x += 200;
}
return;
}
mapData.notes.push({noteId: note.noteId, x, y});
saveData();
await refresh();
@@ -571,40 +552,10 @@ function getMousePosition(evt) {
};
}
$centerButton.click(() => {
if (mapData.notes.length === 0) {
return; // nothing to recenter on
}
let totalX = 0, totalY = 0;
for (const note of mapData.notes) {
totalX += note.x;
totalY += note.y;
}
let averageX = totalX / mapData.notes.length;
let averageY = totalY / mapData.notes.length;
// find note with smallest X, Y difference from the average (most central note)
const {noteId} = mapData.notes.map(note => {
return {
noteId: note.noteId,
diff: Math.abs(note.x - averageX) + Math.abs(note.y - averageY)
}
}).reduce((min, val) => min.diff <= val.min ? min : val, { diff: 9999999999 });
const $noteBox = $("#" + noteIdToId(noteId));
const clientRect = $noteBox[0].getBoundingClientRect();
const cx = clientRect.left + clientRect.width / 2;
const cy = clientRect.top + clientRect.height / 2;
const container = $component[0].getBoundingClientRect();
const dx = container.width / 2 - cx;
const dy = container.height / 2 - cy;
pzInstance.moveBy(dx, dy, true);
$resetPanZoomButton.click(() => {
// reset to initial pan & zoom state
pzInstance.zoomTo(0, 0, 1 / getZoom());
pzInstance.moveTo(0, 0);
});
$component.on("drop", dropNoteOntoRelationMapHandler);

View File

@@ -44,7 +44,7 @@ function setupTooltip() {
container: 'body',
placement: 'auto',
trigger: 'manual',
boundariesElement: 'window',
boundary: 'window',
title: html,
html: true
});

View File

@@ -0,0 +1,57 @@
html, body {
margin: 0;
width: 100%;
height: 100%;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Meiryo", sans-serif;
overflow: hidden;
}
.inpage-search-body {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin: 8px;
padding: 10px;
border: solid #aaaaaa 1px;
border-radius: 10px;
background-color: #fafafa;
}
.inpage-search-input {
width: 200px;
}
.inpage-search-matches {
color: #999;
font-size: 0.8em;
}
.inpage-search-back {
margin-left: 2px;
padding-left: 6px;
padding-right: 2px;
cursor: pointer;
}
.inpage-search-forward {
padding-left: 2px;
padding-right: 6px;
cursor: pointer;
}
.inpage-search-close {
margin-left: 4px;
padding: 0 2px;
cursor: pointer;
}
.inpage-search-back:hover,
.inpage-search-forward:hover,
.inpage-search-close:hover {
background-color: #e2e0e2;
border-radius: 0.2em;
}

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes" />
<link href="/libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="inpage-search-body">
<input class="inpage-search-input form-control form-control-sm" type="search" placeholder="Search..." autocomplete="off" autofocus/>
<div class="inpage-search-matches">0/0</div>
<div class="inpage-search-back" title="Previous result">&lt;</div>
<div class="inpage-search-forward" title="Next result">&gt;</div>
<div class="inpage-search-close" title="Close search"></div>
</div>
</body>
<script>var exports = {}</script>
</html>

View File

@@ -77,9 +77,12 @@ body {
overflow: auto;
flex-basis: content;
height: 100%;
display: flex;
flex-direction: column;
}
.note-detail-component {
flex-grow: 100;
display: none;
}
@@ -211,12 +214,12 @@ div.ui-tooltip {
*/
.electron-in-page-search-window {
position: fixed;
top: 50px;
right: 0;
border: solid grey 1px;
background-color: white;
width: 300px;
height: 36px;
top: 45px;
right: 10px;
width: 360px;
height: 55px;
display: none;
z-index: 1001;
}
/*
@@ -344,11 +347,11 @@ div.ui-tooltip {
#children-overview {
flex-grow: 1000;
flex-shrink: 1000;
flex-basis: 0px;
flex-basis: 0;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
height: 100px;
height: 110px;
overflow: auto;
}
@@ -528,12 +531,12 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
padding: 20px;
}
.context-menu {
.context-menu-container {
font-size: small;
}
.context-menu .dropdown-item {
padding: 2px 10px 2px 10px;
#context-menu-container .dropdown-item {
padding: 0 7px 0 10px;
}
/* if modal height overflows, then only modal body scrolls */
@@ -542,6 +545,11 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
overflow-y: auto;
}
/* this should help with tooltip flickering */
.tooltip {
pointer-events: none;
}
.tooltip-inner {
background-color: #fbfbfb !important;
max-width: 400px;
@@ -558,6 +566,10 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
max-height: 250px;
}
.tooltip-inner figure.image-style-side {
float: right;
}
.tooltip.show {
opacity: 1;
}

View File

@@ -117,8 +117,8 @@ async function getRelationMap(req) {
// noteId => title
noteTitles: {},
relations: [],
// relation name => mirror relation name
mirrorRelations: {},
// relation name => inverse relation name
inverseRelations: {},
links: []
};
@@ -143,8 +143,8 @@ async function getRelationMap(req) {
}; }));
for (const relationDefinition of await note.getRelationDefinitions()) {
if (relationDefinition.value.mirrorRelation) {
resp.mirrorRelations[relationDefinition.name] = relationDefinition.value.mirrorRelation;
if (relationDefinition.value.inverseRelation) {
resp.inverseRelations[relationDefinition.name] = relationDefinition.value.inverseRelation;
}
}
}

64
src/services/app_icon.js Normal file
View File

@@ -0,0 +1,64 @@
"use strict";
const path = require('path');
const {APP_PNG_ICON_DIR, ELECTRON_APP_ROOT_DIR} = require("./resource_dir");
const log = require("./log");
const os = require('os');
const fs = require('fs');
const config = require('./config');
const template = `[Desktop Entry]
Type=Application
Name=Trilium Notes
Icon=#APP_PNG_ICON_DIR#/128x128.png
Exec=#EXE_PATH#
Categories=Office
Terminal=false
`;
/**
* Installs .desktop icon into standard ~/.local/share/applications directory.
* We overwrite this file during every run as it might have been updated.
*/
function installLocalAppIcon() {
if (["win32", "darwin"].includes(os.platform()) || (config.General && config.General.noDesktopIcon)) {
return;
}
const desktopDir = path.resolve(os.homedir(), '.local/share/applications');
fs.stat(desktopDir, function (err, stats) {
if (err) {
// Directory doesn't exist so we won't attempt to create the .desktop file
return;
}
if (stats.isDirectory()) {
const desktopFilePath = path.resolve(desktopDir, "trilium-notes.desktop");
fs.writeFile(desktopFilePath, getDesktopFileContent(), function (err) {
if (err) {
log.error("Desktop icon installation to ~/.local/share/applications failed.");
}
});
}
});
}
function getDesktopFileContent() {
return template
.replace("#APP_PNG_ICON_DIR#", escapePath(APP_PNG_ICON_DIR))
.replace("#EXE_PATH#", escapePath(getExePath()));
}
function escapePath(path) {
return path.replace(" ", "\\ ");
}
function getExePath() {
return path.resolve(ELECTRON_APP_ROOT_DIR, 'trilium');
}
module.exports = {
installLocalAppIcon
};

View File

@@ -3,7 +3,7 @@
const build = require('./build');
const packageJson = require('../../package');
const APP_DB_VERSION = 118;
const APP_DB_VERSION = 119;
const SYNC_VERSION = 2;
module.exports = {

View File

@@ -1 +1 @@
module.exports = { buildDate:"2018-11-19T00:06:44+01:00", buildRevision: "ad6cb6ba347f0396cbf79b76ab62ee3e4a4e8566" };
module.exports = { buildDate:"2018-11-21T23:47:09+01:00", buildRevision: "3a064934598b70878f6da4c11c0ceb84ef18db57" };

View File

@@ -209,6 +209,16 @@ async function runAllChecks() {
AND type != 'relation-map'`,
"Note has invalid type", errorList);
await runCheck(`
SELECT
noteId
FROM
notes
WHERE
isDeleted = 0
AND content IS NULL`,
"Note content is null even though it is not deleted", errorList);
await runCheck(`
SELECT
parentNoteId

View File

@@ -4,8 +4,20 @@ const sanitize = require("sanitize-filename");
const TurndownService = require('turndown');
async function exportSingleMarkdown(note, res) {
const turndownService = new TurndownService();
const markdown = turndownService.turndown(note.content);
if (note.type !== 'text' && note.type !== 'code') {
return [400, `Note type ${note.type} cannot be exported as single markdown file.`];
}
let markdown;
if (note.type === 'code') {
markdown = '```\n' + note.content + "\n```";
}
else if (note.type === 'text') {
const turndownService = new TurndownService();
markdown = turndownService.turndown(note.content);
}
const name = sanitize(note.title);
res.setHeader('Content-Disposition', 'file; filename="' + name + '.md"');

View File

@@ -25,7 +25,7 @@ async function exportToMarkdown(branch, res) {
return;
}
saveDataFile(childFileName, note);
saveNote(childFileName, note);
const childNotes = await note.getChildNotes();
@@ -40,11 +40,7 @@ async function exportToMarkdown(branch, res) {
return childFileName;
}
function saveDataFile(childFileName, note) {
if (note.type !== 'text' && note.type !== 'code') {
return;
}
function saveTextNote(childFileName, note) {
if (note.content.trim().length === 0) {
return;
}
@@ -65,6 +61,19 @@ async function exportToMarkdown(branch, res) {
pack.entry({name: childFileName + ".md", size: markdown.length}, markdown);
}
function saveFileNote(childFileName, note) {
pack.entry({name: childFileName, size: note.content.length}, note.content);
}
function saveNote(childFileName, note) {
if (note.type === 'text' || note.type === 'code') {
saveTextNote(childFileName, note);
}
else if (note.type === 'image' || note.type === 'file') {
saveFileNote(childFileName, note);
}
}
function saveDirectory(childFileName) {
pack.entry({name: childFileName, type: 'directory'});
}

View File

@@ -12,6 +12,11 @@ async function exportToOpml(branch, res) {
async function exportNoteInner(branchId) {
const branch = await repository.getBranch(branchId);
const note = await branch.getNote();
if (await note.hasLabel('excludeFromExport')) {
return;
}
const title = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title;
const preparedTitle = prepareText(title);

View File

@@ -59,7 +59,7 @@ eventService.subscribe(eventService.CHILD_NOTE_CREATED, async ({ parentNote, chi
await runAttachedRelations(parentNote, 'runOnChildNoteCreation', childNote);
});
async function processMirrorRelations(entityName, entity, handler) {
async function processInverseRelations(entityName, entity, handler) {
if (entityName === 'attributes' && entity.type === 'relation') {
const note = await entity.getNote();
const attributes = (await note.getAttributes(entity.name)).filter(relation => relation.type === 'relation-definition');
@@ -67,7 +67,7 @@ async function processMirrorRelations(entityName, entity, handler) {
for (const attribute of attributes) {
const definition = attribute.value;
if (definition.mirrorRelation && definition.mirrorRelation.trim()) {
if (definition.inverseRelation && definition.inverseRelation.trim()) {
const targetNote = await entity.getTargetNote();
await handler(definition, note, targetNote);
@@ -77,17 +77,17 @@ async function processMirrorRelations(entityName, entity, handler) {
}
eventService.subscribe(eventService.ENTITY_CHANGED, async ({ entityName, entity }) => {
await processMirrorRelations(entityName, entity, async (definition, note, targetNote) => {
// we need to make sure that also target's mirror attribute exists and if note, then create it
// mirror attribute has to target our note as well
const hasMirrorAttribute = (await targetNote.getRelations(definition.mirrorRelation))
await processInverseRelations(entityName, entity, async (definition, note, targetNote) => {
// we need to make sure that also target's inverse attribute exists and if note, then create it
// inverse attribute has to target our note as well
const hasInverseAttribute = (await targetNote.getRelations(definition.inverseRelation))
.some(attr => attr.value === note.noteId);
if (!hasMirrorAttribute) {
if (!hasInverseAttribute) {
await new Attribute({
noteId: targetNote.noteId,
type: 'relation',
name: definition.mirrorRelation,
name: definition.inverseRelation,
value: note.noteId,
isInheritable: entity.isInheritable
}).save();
@@ -98,9 +98,9 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({ entityName, entity
});
eventService.subscribe(eventService.ENTITY_DELETED, async ({ entityName, entity }) => {
await processMirrorRelations(entityName, entity, async (definition, note, targetNote) => {
// if one mirror attribute is deleted then the other should be deleted as well
const relations = await targetNote.getRelations(definition.mirrorRelation);
await processInverseRelations(entityName, entity, async (definition, note, targetNote) => {
// if one inverse attribute is deleted then the other should be deleted as well
const relations = await targetNote.getRelations(definition.inverseRelation);
let deletedSomething = false;
for (const relation of relations) {

View File

@@ -80,7 +80,7 @@ function findNotes(query) {
continue;
}
// for leaf note it doesn't matter if "archived" label inheritable or not
// for leaf note it doesn't matter if "archived" label is inheritable or not
if (noteId in archived) {
continue;
}
@@ -113,11 +113,28 @@ function findNotes(query) {
}
}
results.sort((a, b) => a.title < b.title ? -1 : 1);
// sort results by depth of the note. This is based on the assumption that more important results
// are closer to the note root.
results.sort((a, b) => {
if (a.pathArray.length === b.pathArray.length) {
return a.title < b.title ? -1 : 1;
}
highlightResults(results, allTokens);
return a.pathArray.length < b.pathArray.length ? -1 : 1;
});
return results;
const apiResults = results.slice(0, 200).map(res => {
return {
noteId: res.noteId,
branchId: res.branchId,
path: res.pathArray.join('/'),
title: res.titleArray.join(' / ')
};
});
highlightResults(apiResults, allTokens);
return apiResults;
}
function search(noteId, tokens, path, results) {
@@ -125,15 +142,14 @@ function search(noteId, tokens, path, results) {
const retPath = getSomePath(noteId, path);
if (retPath) {
const noteTitle = getNoteTitleForPath(retPath);
const thisNoteId = retPath[retPath.length - 1];
const thisParentNoteId = retPath[retPath.length - 2];
results.push({
noteId: thisNoteId,
branchId: childParentToBranchId[`${thisNoteId}-${thisParentNoteId}`],
title: noteTitle,
path: retPath.join('/')
pathArray: retPath,
titleArray: getNoteTitleArrayForPath(retPath)
});
}
@@ -146,10 +162,6 @@ function search(noteId, tokens, path, results) {
}
for (const parentNoteId of parents) {
if (results.length >= 200) {
return;
}
// archived must be inheritable
if (archived[parentNoteId] === 1) {
continue;
@@ -192,12 +204,12 @@ function getNoteTitle(noteId, parentNoteId) {
return (prefix ? (prefix + ' - ') : '') + title;
}
function getNoteTitleForPath(path) {
function getNoteTitleArrayForPath(path) {
const titles = [];
if (path[0] === 'root') {
if (path.length === 1) {
return getNoteTitle('root');
return [ getNoteTitle('root') ];
}
else {
path = path.slice(1);
@@ -213,6 +225,12 @@ function getNoteTitleForPath(path) {
parentNoteId = noteId;
}
return titles;
}
function getNoteTitleForPath(path) {
const titles = getNoteTitleArrayForPath(path);
return titles.join(' / ');
}

View File

@@ -177,7 +177,7 @@ async function protectNoteRevisions(note) {
}
function findImageLinks(content, foundLinks) {
const re = /src="\/api\/images\/([a-zA-Z0-9]+)\//g;
const re = /src="[^"]*\/api\/images\/([a-zA-Z0-9]+)\//g;
let match;
while (match = re.exec(content)) {
@@ -186,11 +186,13 @@ function findImageLinks(content, foundLinks) {
targetNoteId: match[1]
});
}
return match;
// removing absolute references to server to keep it working between instances
return content.replace(/src="[^"]*\/api\/images\//g, 'src="/api/images/');
}
function findHyperLinks(content, foundLinks) {
const re = /href="#root[a-zA-Z0-9\/]*\/([a-zA-Z0-9]+)\/?"/g;
const re = /href="[^"]*#root[a-zA-Z0-9\/]*\/([a-zA-Z0-9]+)\/?"/g;
let match;
while (match = re.exec(content)) {
@@ -200,7 +202,8 @@ function findHyperLinks(content, foundLinks) {
});
}
return match;
// removing absolute references to server to keep it working between instances
return content.replace(/href="[^"]*#root/g, 'href="#root');
}
function findRelationMapLinks(content, foundLinks) {
@@ -214,19 +217,19 @@ function findRelationMapLinks(content, foundLinks) {
}
}
async function saveLinks(note) {
async function saveLinks(note, content) {
if (note.type !== 'text' && note.type !== 'relation-map') {
return;
return content;
}
const foundLinks = [];
if (note.type === 'text') {
findImageLinks(note.content, foundLinks);
findHyperLinks(note.content, foundLinks);
content = findImageLinks(content, foundLinks);
content = findHyperLinks(content, foundLinks);
}
else if (note.type === 'relation-map') {
findRelationMapLinks(note.content, foundLinks);
findRelationMapLinks(content, foundLinks);
}
else {
throw new Error("Unrecognized type " + note.type);
@@ -262,6 +265,8 @@ async function saveLinks(note) {
unusedLink.isDeleted = true;
await unusedLink.save();
}
return content;
}
async function saveNoteRevision(note) {
@@ -310,6 +315,8 @@ async function updateNote(noteId, noteUpdates) {
const noteTitleChanged = note.title !== noteUpdates.title;
noteUpdates.content = await saveLinks(note, noteUpdates.content);
note.title = noteUpdates.title;
note.setContent(noteUpdates.content);
note.isProtected = noteUpdates.isProtected;
@@ -319,8 +326,6 @@ async function updateNote(noteId, noteUpdates) {
await triggerNoteTitleChanged(note);
}
await saveLinks(note);
await protectNoteRevisions(note);
}

View File

@@ -4,7 +4,10 @@ const fs = require('fs');
const RESOURCE_DIR = path.resolve(__dirname, "../..");
// where "trilium" executable is
const ELECTRON_APP_ROOT_DIR = path.resolve(RESOURCE_DIR, "../..");
const DB_INIT_DIR = path.resolve(RESOURCE_DIR, "db");
const APP_PNG_ICON_DIR = path.resolve(RESOURCE_DIR, "src/public/images/app-icons/png");
if (!fs.existsSync(DB_INIT_DIR)) {
log.error("Could not find DB initialization directory: " + DB_INIT_DIR);
@@ -21,5 +24,7 @@ if (!fs.existsSync(MIGRATIONS_DIR)) {
module.exports = {
RESOURCE_DIR,
MIGRATIONS_DIR,
DB_INIT_DIR
DB_INIT_DIR,
ELECTRON_APP_ROOT_DIR,
APP_PNG_ICON_DIR
};

View File

@@ -18,9 +18,9 @@
<% include image.ejs %>
<% include relation_map.ejs %>
</div>
<div id="children-overview"></div>
<div id="children-overview"></div>
</div>
<div id="attribute-list">
<button class="btn btn-sm show-attributes-button">Attributes:</button>

View File

@@ -22,5 +22,7 @@
<br/><br/>
<img id="note-detail-image-view" />
<div id="note-detail-image-wrapper">
<img id="note-detail-image-view" />
</div>
</div>

View File

@@ -7,9 +7,9 @@
</button>
<button type="button"
class="btn icon-button floating-button jam jam-align-center"
title="Re-center view on notes"
id="relation-map-center" style="right: 100px;"></button>
class="btn icon-button floating-button jam jam-crop"
title="Reset pan & zoom to initial coordinates and magnification"
id="relation-map-reset-pan-zoom" style="right: 100px;"></button>
<div class="btn-group floating-button" style="right: 20px;">
<button type="button"

View File

@@ -48,6 +48,10 @@
<input class="form-control relation-target-note-id"
placeholder="search for note by its name"
data-bind="noteAutocomplete, value: relationValue, valueUpdate: 'blur', event: { blur: $parent.attributeChanged }">
<div style="color: red" data-bind="if: $parent.isEmptyRelationTarget($index())">Relation target note
can't be empty.
</div>
</div>
<div data-bind="visible: type == 'label-definition'">
@@ -72,9 +76,9 @@
</label>
<br/>
<label>
Mirror relation:
Inverse relation:
<input type="text" value="true" class="attribute-name" data-bind="value: relationDefinition.mirrorRelation"/>
<input type="text" value="true" class="attribute-name" data-bind="value: relationDefinition.inverseRelation"/>
</label>
</div>
</td>

View File

@@ -159,7 +159,7 @@
<a class="dropdown-item show-attributes-button"><kbd>Alt+A</kbd> Attributes</a>
<a class="dropdown-item" id="show-source-button" data-bind="css: { disabled: type() != 'text' }">HTML source</a>
<a class="dropdown-item" id="upload-file-button">Upload file</a>
<a class="dropdown-item" id="export-note-to-markdown-button" data-bind="css: { disabled: type() != 'text' }">Export as markdown</a>
<a class="dropdown-item" id="export-note-to-markdown-button" data-bind="css: { disabled: type() != 'text' && type() != 'code' }">Export as markdown</a>
</div>
</div>
</div>
@@ -187,6 +187,8 @@
<% include dialogs/confirm.ejs %>
</div>
<webview class="electron-in-page-search-window" nodeintegration disablewebsecurity src="/libraries/electron-in-page-search/search-window.html"></webview>
<script type="text/javascript">
window.baseApiUrl = 'api/';
window.glob = {