Compare commits

...

11 Commits

Author SHA1 Message Date
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
25 changed files with 149 additions and 137 deletions

View File

@@ -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"

Binary file not shown.

View File

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

View File

@@ -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
View File

@@ -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": {

View File

@@ -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"

View File

@@ -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
} }
})); }));

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(); treeService.showTree();

View File

@@ -10,15 +10,13 @@ const dragAndDropSetup = {
node.setSelected(true); 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 // 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. // 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.

View File

@@ -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);

View File

@@ -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');
} }
}); });

View File

@@ -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 exists = mapData.notes.some(n => n.noteId === note.noteId);
const startX = x -= 80;
y -= 15;
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) { return;
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;
}
} }
mapData.notes.push({noteId: note.noteId, x, y});
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);

View File

@@ -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;
} }

View File

@@ -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;
} }
} }
} }

View File

@@ -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 = {

View File

@@ -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" };

View File

@@ -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) {
const turndownService = new TurndownService(); if (note.type !== 'text' && note.type !== 'code') {
const markdown = turndownService.turndown(note.content); 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); const name = sanitize(note.title);
res.setHeader('Content-Disposition', 'file; filename="' + name + '.md"'); res.setHeader('Content-Disposition', 'file; filename="' + name + '.md"');

View File

@@ -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'});
} }

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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);
} }

View File

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

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>