mirror of
https://github.com/zadam/trilium.git
synced 2025-11-01 10:55:55 +01:00
Compare commits
11 Commits
v0.24.1-be
...
v0.24.2-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c834229b9 | ||
|
|
3fd45b15e7 | ||
|
|
f20ab45576 | ||
|
|
77a89d85c8 | ||
|
|
30249a353e | ||
|
|
eb9bae9010 | ||
|
|
0c7ae527c5 | ||
|
|
fef4705e2f | ||
|
|
568c2c997f | ||
|
|
d6b5cd6ead | ||
|
|
00ce379962 |
12
bin/build.sh
12
bin/build.sh
@@ -11,15 +11,21 @@ rm -r dist/*
|
|||||||
echo "Rebuilding binaries for linux-ia32"
|
echo "Rebuilding binaries for linux-ia32"
|
||||||
./node_modules/.bin/electron-rebuild --arch=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
|
# 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"
|
echo "Rebuilding binaries for linux-x64"
|
||||||
./node_modules/.bin/electron-rebuild --arch=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"
|
echo "Copying required windows binaries"
|
||||||
|
|
||||||
|
|||||||
BIN
db/demo.tar
BIN
db/demo.tar
Binary file not shown.
1
db/migrations/0119__rename_mirror_to_inverse.sql
Normal file
1
db/migrations/0119__rename_mirror_to_inverse.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
UPDATE attributes SET value = replace(value, 'mirrorRelation', 'inverseRelation') WHERE type = 'relation-definition';
|
||||||
@@ -70,6 +70,8 @@ app.on('activate', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.on('ready', async () => {
|
app.on('ready', async () => {
|
||||||
|
app.setAppUserModelId('com.github.zadam.trilium');
|
||||||
|
|
||||||
mainWindow = await createMainWindow();
|
mainWindow = await createMainWindow();
|
||||||
|
|
||||||
const result = globalShortcut.register('CommandOrControl+Alt+P', cls.wrap(async () => {
|
const result = globalShortcut.register('CommandOrControl+Alt+P', cls.wrap(async () => {
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"version": "0.24.0-beta",
|
"version": "0.24.1-beta",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
|
"productName": "Trilium Notes",
|
||||||
"description": "Trilium Notes",
|
"description": "Trilium Notes",
|
||||||
"version": "0.24.1-beta",
|
"version": "0.24.2-beta",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -13,10 +14,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./src/www",
|
"start": "node ./src/www",
|
||||||
"test-electron": "xo",
|
|
||||||
"rebuild-electron": "electron-rebuild",
|
|
||||||
"start-electron": "electron . --disable-gpu",
|
"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-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-frontend-docs": "jsdoc -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"
|
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs"
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ function AttributesModel() {
|
|||||||
|
|
||||||
attr.relationDefinition = (attr.type === 'relation-definition' && attr.value) ? attr.value : {
|
attr.relationDefinition = (attr.type === 'relation-definition' && attr.value) ? attr.value : {
|
||||||
multiplicityType: "singlevalue",
|
multiplicityType: "singlevalue",
|
||||||
mirrorRelation: "",
|
inverseRelation: "",
|
||||||
isPromoted: true
|
isPromoted: true
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ function AttributesModel() {
|
|||||||
},
|
},
|
||||||
relationDefinition: {
|
relationDefinition: {
|
||||||
multiplicityType: "singlevalue",
|
multiplicityType: "singlevalue",
|
||||||
mirrorRelation: "",
|
inverseRelation: "",
|
||||||
isPromoted: true
|
isPromoted: true
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|||||||
8
src/public/javascripts/services/bootstrap.js
vendored
8
src/public/javascripts/services/bootstrap.js
vendored
@@ -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();
|
treeService.showTree();
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,13 @@ const dragAndDropSetup = {
|
|||||||
|
|
||||||
node.setSelected(true);
|
node.setSelected(true);
|
||||||
|
|
||||||
const selectedNodes = treeService.getSelectedNodes().map(node => {
|
// this is for dragging notes into relation map
|
||||||
return {
|
// 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,
|
noteId: node.data.noteId,
|
||||||
title: node.title
|
title: node.title
|
||||||
}
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
// this is for dragging notes into relation map
|
|
||||||
data.dataTransfer.setData("text", JSON.stringify(selectedNodes));
|
|
||||||
|
|
||||||
// This function MUST be defined to enable dragging for the tree.
|
// This function MUST be defined to enable dragging for the tree.
|
||||||
// Return false to cancel dragging of node.
|
// Return false to cancel dragging of node.
|
||||||
|
|||||||
@@ -25,9 +25,21 @@ function registerEntrypoints() {
|
|||||||
$("#jump-to-note-dialog-button").click(jumpToNoteDialog.showDialog);
|
$("#jump-to-note-dialog-button").click(jumpToNoteDialog.showDialog);
|
||||||
utils.bindShortcut('ctrl+j', 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);
|
$("#recent-changes-button").click(recentChangesDialog.showDialog);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import infoService from "./info.js";
|
|||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
|
|
||||||
const $component = $('#note-detail-image');
|
const $component = $('#note-detail-image');
|
||||||
|
const $imageWrapper = $('#note-detail-image-wrapper');
|
||||||
const $imageView = $('#note-detail-image-view');
|
const $imageView = $('#note-detail-image-view');
|
||||||
|
|
||||||
const $imageDownloadButton = $("#image-download");
|
const $imageDownloadButton = $("#image-download");
|
||||||
@@ -39,10 +40,10 @@ function selectImage(element) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$copyToClipboardButton.click(() => {
|
$copyToClipboardButton.click(() => {
|
||||||
$component.attr('contenteditable','true');
|
$imageWrapper.attr('contenteditable','true');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
selectImage($component.get(0));
|
selectImage($imageWrapper.get(0));
|
||||||
|
|
||||||
const success = document.execCommand('copy');
|
const success = document.execCommand('copy');
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ $copyToClipboardButton.click(() => {
|
|||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
window.getSelection().removeAllRanges();
|
window.getSelection().removeAllRanges();
|
||||||
$component.removeAttr('contenteditable');
|
$imageWrapper.removeAttr('contenteditable');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const $relationMapContainer = $("#relation-map-container");
|
|||||||
const $createChildNote = $("#relation-map-create-child-note");
|
const $createChildNote = $("#relation-map-create-child-note");
|
||||||
const $zoomInButton = $("#relation-map-zoom-in");
|
const $zoomInButton = $("#relation-map-zoom-in");
|
||||||
const $zoomOutButton = $("#relation-map-zoom-out");
|
const $zoomOutButton = $("#relation-map-zoom-out");
|
||||||
const $centerButton = $("#relation-map-center");
|
const $resetPanZoomButton = $("#relation-map-reset-pan-zoom");
|
||||||
|
|
||||||
let mapData;
|
let mapData;
|
||||||
let jsPlumbInstance;
|
let jsPlumbInstance;
|
||||||
@@ -50,7 +50,7 @@ const biDirectionalOverlays = [
|
|||||||
} ]
|
} ]
|
||||||
];
|
];
|
||||||
|
|
||||||
const mirrorOverlays = [
|
const inverseRelationsOverlays = [
|
||||||
[ "Arrow", {
|
[ "Arrow", {
|
||||||
location: 1,
|
location: 1,
|
||||||
id: "arrow",
|
id: "arrow",
|
||||||
@@ -134,12 +134,12 @@ async function loadNotesAndRelations() {
|
|||||||
|
|
||||||
for (const relation of data.relations) {
|
for (const relation of data.relations) {
|
||||||
const match = relations.find(rel =>
|
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.sourceNoteId && rel.targetNoteId === relation.targetNoteId)
|
||||||
|| (rel.sourceNoteId === relation.targetNoteId && rel.targetNoteId === relation.sourceNoteId)));
|
|| (rel.sourceNoteId === relation.targetNoteId && rel.targetNoteId === relation.sourceNoteId)));
|
||||||
|
|
||||||
if (match) {
|
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
|
relation.render = false; // don't render second relation
|
||||||
} else {
|
} else {
|
||||||
relation.type = 'uniDirectional';
|
relation.type = 'uniDirectional';
|
||||||
@@ -173,9 +173,9 @@ async function loadNotesAndRelations() {
|
|||||||
|
|
||||||
connection.id = relation.attributeId;
|
connection.id = relation.attributeId;
|
||||||
|
|
||||||
if (relation.type === 'mirror') {
|
if (relation.type === 'inverse') {
|
||||||
connection.getOverlay("label-source").setLabel(relation.name);
|
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 {
|
else {
|
||||||
connection.getOverlay("label").setLabel(relation.name);
|
connection.getOverlay("label").setLabel(relation.name);
|
||||||
@@ -240,6 +240,10 @@ function initPanZoom() {
|
|||||||
|
|
||||||
pzInstance.moveTo(mapData.transform.x, mapData.transform.y);
|
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));
|
$zoomInButton.click(() => pzInstance.zoomTo(0, 0, 1.2));
|
||||||
$zoomOutButton.click(() => pzInstance.zoomTo(0, 0, 0.8));
|
$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("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 });
|
jsPlumbInstance.registerConnectionType("link", { anchor:"Continuous", connector:"StateMachine", overlays: linkOverlays });
|
||||||
|
|
||||||
@@ -518,43 +522,20 @@ function getZoom() {
|
|||||||
async function dropNoteOntoRelationMapHandler(ev) {
|
async function dropNoteOntoRelationMapHandler(ev) {
|
||||||
ev.preventDefault();
|
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);
|
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 currentNoteId = treeService.getCurrentNode().data.noteId;
|
|
||||||
|
|
||||||
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);
|
const exists = mapData.notes.some(n => n.noteId === note.noteId);
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
await infoDialog.info(`Note "${note.title}" is already placed into the diagram`);
|
await infoDialog.info(`Note "${note.title}" is already placed into the diagram`);
|
||||||
|
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapData.notes.push({noteId: note.noteId, x, y});
|
mapData.notes.push({noteId: note.noteId, x, y});
|
||||||
|
|
||||||
if (x - startX > 1000) {
|
|
||||||
x = startX;
|
|
||||||
y += 200;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
x += 200;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
saveData();
|
saveData();
|
||||||
|
|
||||||
await refresh();
|
await refresh();
|
||||||
@@ -571,40 +552,10 @@ function getMousePosition(evt) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
$centerButton.click(() => {
|
$resetPanZoomButton.click(() => {
|
||||||
if (mapData.notes.length === 0) {
|
// reset to initial pan & zoom state
|
||||||
return; // nothing to recenter on
|
pzInstance.zoomTo(0, 0, 1 / getZoom());
|
||||||
}
|
pzInstance.moveTo(0, 0);
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$component.on("drop", dropNoteOntoRelationMapHandler);
|
$component.on("drop", dropNoteOntoRelationMapHandler);
|
||||||
|
|||||||
@@ -558,6 +558,10 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
|
|||||||
max-height: 250px;
|
max-height: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip-inner figure.image-style-side {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
.tooltip.show {
|
.tooltip.show {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ async function getRelationMap(req) {
|
|||||||
// noteId => title
|
// noteId => title
|
||||||
noteTitles: {},
|
noteTitles: {},
|
||||||
relations: [],
|
relations: [],
|
||||||
// relation name => mirror relation name
|
// relation name => inverse relation name
|
||||||
mirrorRelations: {},
|
inverseRelations: {},
|
||||||
links: []
|
links: []
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,8 +143,8 @@ async function getRelationMap(req) {
|
|||||||
}; }));
|
}; }));
|
||||||
|
|
||||||
for (const relationDefinition of await note.getRelationDefinitions()) {
|
for (const relationDefinition of await note.getRelationDefinitions()) {
|
||||||
if (relationDefinition.value.mirrorRelation) {
|
if (relationDefinition.value.inverseRelation) {
|
||||||
resp.mirrorRelations[relationDefinition.name] = relationDefinition.value.mirrorRelation;
|
resp.inverseRelations[relationDefinition.name] = relationDefinition.value.inverseRelation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
const build = require('./build');
|
const build = require('./build');
|
||||||
const packageJson = require('../../package');
|
const packageJson = require('../../package');
|
||||||
|
|
||||||
const APP_DB_VERSION = 118;
|
const APP_DB_VERSION = 119;
|
||||||
const SYNC_VERSION = 2;
|
const SYNC_VERSION = 2;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
module.exports = { buildDate:"2018-11-19T00:06:44+01:00", buildRevision: "ad6cb6ba347f0396cbf79b76ab62ee3e4a4e8566" };
|
module.exports = { buildDate:"2018-11-19T17:17:08+01:00", buildRevision: "3fd45b15e7042c12f140524297b50677f9851044" };
|
||||||
|
|||||||
@@ -4,8 +4,20 @@ const sanitize = require("sanitize-filename");
|
|||||||
const TurndownService = require('turndown');
|
const TurndownService = require('turndown');
|
||||||
|
|
||||||
async function exportSingleMarkdown(note, res) {
|
async function exportSingleMarkdown(note, res) {
|
||||||
|
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();
|
const turndownService = new TurndownService();
|
||||||
const markdown = turndownService.turndown(note.content);
|
markdown = turndownService.turndown(note.content);
|
||||||
|
}
|
||||||
|
|
||||||
const name = sanitize(note.title);
|
const name = sanitize(note.title);
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'file; filename="' + name + '.md"');
|
res.setHeader('Content-Disposition', 'file; filename="' + name + '.md"');
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ async function exportToMarkdown(branch, res) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveDataFile(childFileName, note);
|
saveNote(childFileName, note);
|
||||||
|
|
||||||
const childNotes = await note.getChildNotes();
|
const childNotes = await note.getChildNotes();
|
||||||
|
|
||||||
@@ -40,11 +40,7 @@ async function exportToMarkdown(branch, res) {
|
|||||||
return childFileName;
|
return childFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveDataFile(childFileName, note) {
|
function saveTextNote(childFileName, note) {
|
||||||
if (note.type !== 'text' && note.type !== 'code') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (note.content.trim().length === 0) {
|
if (note.content.trim().length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -65,6 +61,19 @@ async function exportToMarkdown(branch, res) {
|
|||||||
pack.entry({name: childFileName + ".md", size: markdown.length}, markdown);
|
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) {
|
function saveDirectory(childFileName) {
|
||||||
pack.entry({name: childFileName, type: 'directory'});
|
pack.entry({name: childFileName, type: 'directory'});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ async function exportToOpml(branch, res) {
|
|||||||
async function exportNoteInner(branchId) {
|
async function exportNoteInner(branchId) {
|
||||||
const branch = await repository.getBranch(branchId);
|
const branch = await repository.getBranch(branchId);
|
||||||
const note = await branch.getNote();
|
const note = await branch.getNote();
|
||||||
|
|
||||||
|
if (await note.hasLabel('excludeFromExport')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const title = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title;
|
const title = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title;
|
||||||
|
|
||||||
const preparedTitle = prepareText(title);
|
const preparedTitle = prepareText(title);
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ eventService.subscribe(eventService.CHILD_NOTE_CREATED, async ({ parentNote, chi
|
|||||||
await runAttachedRelations(parentNote, 'runOnChildNoteCreation', childNote);
|
await runAttachedRelations(parentNote, 'runOnChildNoteCreation', childNote);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function processMirrorRelations(entityName, entity, handler) {
|
async function processInverseRelations(entityName, entity, handler) {
|
||||||
if (entityName === 'attributes' && entity.type === 'relation') {
|
if (entityName === 'attributes' && entity.type === 'relation') {
|
||||||
const note = await entity.getNote();
|
const note = await entity.getNote();
|
||||||
const attributes = (await note.getAttributes(entity.name)).filter(relation => relation.type === 'relation-definition');
|
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) {
|
for (const attribute of attributes) {
|
||||||
const definition = attribute.value;
|
const definition = attribute.value;
|
||||||
|
|
||||||
if (definition.mirrorRelation && definition.mirrorRelation.trim()) {
|
if (definition.inverseRelation && definition.inverseRelation.trim()) {
|
||||||
const targetNote = await entity.getTargetNote();
|
const targetNote = await entity.getTargetNote();
|
||||||
|
|
||||||
await handler(definition, note, targetNote);
|
await handler(definition, note, targetNote);
|
||||||
@@ -77,17 +77,17 @@ async function processMirrorRelations(entityName, entity, handler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
eventService.subscribe(eventService.ENTITY_CHANGED, async ({ entityName, entity }) => {
|
eventService.subscribe(eventService.ENTITY_CHANGED, async ({ entityName, entity }) => {
|
||||||
await processMirrorRelations(entityName, entity, async (definition, note, targetNote) => {
|
await processInverseRelations(entityName, entity, async (definition, note, targetNote) => {
|
||||||
// we need to make sure that also target's mirror attribute exists and if note, then create it
|
// we need to make sure that also target's inverse attribute exists and if note, then create it
|
||||||
// mirror attribute has to target our note as well
|
// inverse attribute has to target our note as well
|
||||||
const hasMirrorAttribute = (await targetNote.getRelations(definition.mirrorRelation))
|
const hasInverseAttribute = (await targetNote.getRelations(definition.inverseRelation))
|
||||||
.some(attr => attr.value === note.noteId);
|
.some(attr => attr.value === note.noteId);
|
||||||
|
|
||||||
if (!hasMirrorAttribute) {
|
if (!hasInverseAttribute) {
|
||||||
await new Attribute({
|
await new Attribute({
|
||||||
noteId: targetNote.noteId,
|
noteId: targetNote.noteId,
|
||||||
type: 'relation',
|
type: 'relation',
|
||||||
name: definition.mirrorRelation,
|
name: definition.inverseRelation,
|
||||||
value: note.noteId,
|
value: note.noteId,
|
||||||
isInheritable: entity.isInheritable
|
isInheritable: entity.isInheritable
|
||||||
}).save();
|
}).save();
|
||||||
@@ -98,9 +98,9 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({ entityName, entity
|
|||||||
});
|
});
|
||||||
|
|
||||||
eventService.subscribe(eventService.ENTITY_DELETED, async ({ entityName, entity }) => {
|
eventService.subscribe(eventService.ENTITY_DELETED, async ({ entityName, entity }) => {
|
||||||
await processMirrorRelations(entityName, entity, async (definition, note, targetNote) => {
|
await processInverseRelations(entityName, entity, async (definition, note, targetNote) => {
|
||||||
// if one mirror attribute is deleted then the other should be deleted as well
|
// if one inverse attribute is deleted then the other should be deleted as well
|
||||||
const relations = await targetNote.getRelations(definition.mirrorRelation);
|
const relations = await targetNote.getRelations(definition.inverseRelation);
|
||||||
let deletedSomething = false;
|
let deletedSomething = false;
|
||||||
|
|
||||||
for (const relation of relations) {
|
for (const relation of relations) {
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ async function protectNoteRevisions(note) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findImageLinks(content, foundLinks) {
|
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;
|
let match;
|
||||||
|
|
||||||
while (match = re.exec(content)) {
|
while (match = re.exec(content)) {
|
||||||
@@ -186,11 +186,13 @@ function findImageLinks(content, foundLinks) {
|
|||||||
targetNoteId: match[1]
|
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) {
|
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;
|
let match;
|
||||||
|
|
||||||
while (match = re.exec(content)) {
|
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) {
|
function findRelationMapLinks(content, foundLinks) {
|
||||||
@@ -214,7 +217,7 @@ function findRelationMapLinks(content, foundLinks) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveLinks(note) {
|
async function saveLinks(note, content) {
|
||||||
if (note.type !== 'text' && note.type !== 'relation-map') {
|
if (note.type !== 'text' && note.type !== 'relation-map') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -222,11 +225,11 @@ async function saveLinks(note) {
|
|||||||
const foundLinks = [];
|
const foundLinks = [];
|
||||||
|
|
||||||
if (note.type === 'text') {
|
if (note.type === 'text') {
|
||||||
findImageLinks(note.content, foundLinks);
|
content = findImageLinks(content, foundLinks);
|
||||||
findHyperLinks(note.content, foundLinks);
|
content = findHyperLinks(content, foundLinks);
|
||||||
}
|
}
|
||||||
else if (note.type === 'relation-map') {
|
else if (note.type === 'relation-map') {
|
||||||
findRelationMapLinks(note.content, foundLinks);
|
findRelationMapLinks(content, foundLinks);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new Error("Unrecognized type " + note.type);
|
throw new Error("Unrecognized type " + note.type);
|
||||||
@@ -262,6 +265,8 @@ async function saveLinks(note) {
|
|||||||
unusedLink.isDeleted = true;
|
unusedLink.isDeleted = true;
|
||||||
await unusedLink.save();
|
await unusedLink.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveNoteRevision(note) {
|
async function saveNoteRevision(note) {
|
||||||
@@ -310,6 +315,8 @@ async function updateNote(noteId, noteUpdates) {
|
|||||||
|
|
||||||
const noteTitleChanged = note.title !== noteUpdates.title;
|
const noteTitleChanged = note.title !== noteUpdates.title;
|
||||||
|
|
||||||
|
noteUpdates.content = await saveLinks(note, noteUpdates.content);
|
||||||
|
|
||||||
note.title = noteUpdates.title;
|
note.title = noteUpdates.title;
|
||||||
note.setContent(noteUpdates.content);
|
note.setContent(noteUpdates.content);
|
||||||
note.isProtected = noteUpdates.isProtected;
|
note.isProtected = noteUpdates.isProtected;
|
||||||
@@ -319,8 +326,6 @@ async function updateNote(noteId, noteUpdates) {
|
|||||||
await triggerNoteTitleChanged(note);
|
await triggerNoteTitleChanged(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
await saveLinks(note);
|
|
||||||
|
|
||||||
await protectNoteRevisions(note);
|
await protectNoteRevisions(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,5 +22,7 @@
|
|||||||
|
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
|
||||||
|
<div id="note-detail-image-wrapper">
|
||||||
<img id="note-detail-image-view" />
|
<img id="note-detail-image-view" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -7,9 +7,9 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn icon-button floating-button jam jam-align-center"
|
class="btn icon-button floating-button jam jam-crop"
|
||||||
title="Re-center view on notes"
|
title="Reset pan & zoom to initial coordinates and magnification"
|
||||||
id="relation-map-center" style="right: 100px;"></button>
|
id="relation-map-reset-pan-zoom" style="right: 100px;"></button>
|
||||||
|
|
||||||
<div class="btn-group floating-button" style="right: 20px;">
|
<div class="btn-group floating-button" style="right: 20px;">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
|
|||||||
@@ -72,9 +72,9 @@
|
|||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
<label>
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -159,7 +159,7 @@
|
|||||||
<a class="dropdown-item show-attributes-button"><kbd>Alt+A</kbd> Attributes</a>
|
<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="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="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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user