mirror of
https://github.com/zadam/trilium.git
synced 2025-11-01 02:45:54 +01:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
535dcb6d12 | ||
|
|
4426362799 | ||
|
|
2c609e8136 | ||
|
|
11b73b79ed | ||
|
|
e70c862e72 | ||
|
|
b3e66d5a83 | ||
|
|
e8cd821e57 | ||
|
|
be7ac74235 | ||
|
|
58fa0832f6 | ||
|
|
1502b9ce66 |
@@ -33,6 +33,9 @@ find $DIR/libraries -name "*.map" -type f -delete
|
||||
|
||||
rm -r $DIR/src/public/app
|
||||
|
||||
rm -r $DIR/node_modules/sqlite3/build
|
||||
rm -r $DIR/node_modules/sqlite3/deps
|
||||
|
||||
sed -i -e 's/app\/desktop.js/app-dist\/desktop.js/g' $DIR/src/views/desktop.ejs
|
||||
sed -i -e 's/app\/mobile.js/app-dist\/mobile.js/g' $DIR/src/views/mobile.ejs
|
||||
sed -i -e 's/app\/setup.js/app-dist\/setup.js/g' $DIR/src/views/setup.ejs
|
||||
sed -i -e 's/app\/setup.js/app-dist\/setup.js/g' $DIR/src/views/setup.ejs
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.42.5",
|
||||
"version": "0.42.6",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.42.6",
|
||||
"version": "0.42.7",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
const backupService = require('./services/backup');
|
||||
const sqlInit = require('./services/sql_init');
|
||||
require('./entities/entity_constructor');
|
||||
|
||||
backupService.anonymize().then(resp => {
|
||||
if (resp.success) {
|
||||
console.log("Anonymization failed.");
|
||||
sqlInit.dbReady.then(async () => {
|
||||
try {
|
||||
console.log("Starting anonymization...");
|
||||
|
||||
const resp = await backupService.anonymize();
|
||||
|
||||
if (resp.success) {
|
||||
console.log("Anonymized file has been saved to: " + resp.anonymizedFilePath);
|
||||
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log("Anonymization failed.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("Anonymized file has been saved to: " + resp.anonymizedFilePath);
|
||||
catch (e) {
|
||||
console.error(e.message, e.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -105,7 +105,7 @@ class Attribute extends Entity {
|
||||
|
||||
// cannot be static!
|
||||
updatePojo(pojo) {
|
||||
delete pojo.__note;
|
||||
delete pojo.__note; // FIXME: probably note necessary anymore
|
||||
}
|
||||
|
||||
createClone(type, name, value) {
|
||||
|
||||
@@ -152,10 +152,10 @@ function AttributesModel() {
|
||||
attr.value = treeService.getNoteIdFromNotePath(attr.selectedPath);
|
||||
}
|
||||
else if (attr.type === 'label-definition') {
|
||||
attr.value = attr.labelDefinition;
|
||||
attr.value = JSON.stringify(attr.labelDefinition);
|
||||
}
|
||||
else if (attr.type === 'relation-definition') {
|
||||
attr.value = attr.relationDefinition;
|
||||
attr.value = JSON.stringify(attr.relationDefinition);
|
||||
}
|
||||
|
||||
delete attr.labelValue;
|
||||
|
||||
@@ -170,6 +170,16 @@ class NoteShort {
|
||||
* @returns {Attribute[]} all note's attributes, including inherited ones
|
||||
*/
|
||||
getAttributes(type, name) {
|
||||
return this.__filterAttrs(this.__getCachedAttributes([]), type, name);
|
||||
}
|
||||
|
||||
__getCachedAttributes(path) {
|
||||
// notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates
|
||||
// when template instance is a parent of template itself
|
||||
if (path.includes(this.noteId)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!(this.noteId in noteAttributeCache)) {
|
||||
const ownedAttributes = this.getOwnedAttributes();
|
||||
|
||||
@@ -177,11 +187,13 @@ class NoteShort {
|
||||
ownedAttributes
|
||||
];
|
||||
|
||||
const newPath = [...path, this.noteId];
|
||||
|
||||
for (const templateAttr of ownedAttributes.filter(oa => oa.type === 'relation' && oa.name === 'template')) {
|
||||
const templateNote = this.treeCache.notes[templateAttr.value];
|
||||
|
||||
if (templateNote) {
|
||||
attrArrs.push(templateNote.getAttributes());
|
||||
if (templateNote && templateNote.noteId !== this.noteId) {
|
||||
attrArrs.push(templateNote.__getCachedAttributes(newPath));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +201,7 @@ class NoteShort {
|
||||
for (const parentNote of this.getParentNotes()) {
|
||||
// these virtual parent-child relationships are also loaded into frontend tree cache
|
||||
if (parentNote.type !== 'search') {
|
||||
attrArrs.push(parentNote.getInheritableAttributes());
|
||||
attrArrs.push(parentNote.__getInheritableAttributes(newPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +209,7 @@ class NoteShort {
|
||||
noteAttributeCache.attributes[this.noteId] = attrArrs.flat();
|
||||
}
|
||||
|
||||
return this.__filterAttrs(noteAttributeCache.attributes[this.noteId], type, name);
|
||||
return noteAttributeCache.attributes[this.noteId];
|
||||
}
|
||||
|
||||
__filterAttrs(attributes, type, name) {
|
||||
@@ -212,8 +224,8 @@ class NoteShort {
|
||||
}
|
||||
}
|
||||
|
||||
getInheritableAttributes() {
|
||||
const attrs = this.getAttributes();
|
||||
__getInheritableAttributes(path) {
|
||||
const attrs = this.__getCachedAttributes(path);
|
||||
|
||||
return attrs.filter(attr => attr.isInheritable);
|
||||
}
|
||||
@@ -460,4 +472,4 @@ class NoteShort {
|
||||
}
|
||||
}
|
||||
|
||||
export default NoteShort;
|
||||
export default NoteShort;
|
||||
|
||||
@@ -54,8 +54,9 @@ export default class LoadResults {
|
||||
this.attributes.push({attributeId, sourceId});
|
||||
}
|
||||
|
||||
getAttributes() {
|
||||
getAttributes(sourceId = 'none') {
|
||||
return this.attributes
|
||||
.filter(row => row.sourceId !== sourceId)
|
||||
.map(row => this.treeCache.attributes[row.attributeId])
|
||||
.filter(attr => !!attr);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export default class MainTreeExecutors extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const {note} = await noteCreateService.createNote(activeNote.noteId, {
|
||||
await noteCreateService.createNote(activeNote.noteId, {
|
||||
isProtected: activeNote.isProtected,
|
||||
saveSelection: false
|
||||
});
|
||||
@@ -56,4 +56,4 @@ export default class MainTreeExecutors extends Component {
|
||||
saveSelection: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ class TabContext extends Component {
|
||||
protectedSessionHolder.touchProtectedSessionIfNecessary(this.note);
|
||||
|
||||
if (triggerSwitchEvent) {
|
||||
this.triggerEvent('tabNoteSwitched', {
|
||||
await this.triggerEvent('tabNoteSwitched', {
|
||||
tabContext: this,
|
||||
notePath: this.notePath
|
||||
});
|
||||
@@ -127,4 +127,4 @@ class TabContext extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default TabContext;
|
||||
export default TabContext;
|
||||
|
||||
@@ -203,7 +203,7 @@ export default class TabManager extends Component {
|
||||
if (activate) {
|
||||
this.activateTab(tabContext.tabId, false);
|
||||
|
||||
this.triggerEvent('tabNoteSwitchedAndActivated', {
|
||||
await this.triggerEvent('tabNoteSwitchedAndActivated', {
|
||||
tabContext,
|
||||
notePath: tabContext.notePath // resolved note path
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ const TPL = `
|
||||
<table class="note-info-widget-table">
|
||||
<style>
|
||||
.note-info-widget-table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -22,22 +21,23 @@ const TPL = `
|
||||
|
||||
<tr>
|
||||
<th>Note ID:</th>
|
||||
<td colspan="3" class="note-info-note-id"></td>
|
||||
<td class="note-info-note-id"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Created:</th>
|
||||
<td colspan="3" class="note-info-date-created"></td>
|
||||
<td class="note-info-date-created"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Modified:</th>
|
||||
<td colspan="3" class="note-info-date-modified"></td>
|
||||
<td class="note-info-date-modified"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Type:</th>
|
||||
<td class="note-info-type"></td>
|
||||
|
||||
<th>MIME:</th>
|
||||
<td class="note-info-mime"></td>
|
||||
<td>
|
||||
<span class="note-info-type"></span>
|
||||
|
||||
<span class="note-info-mime"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
`;
|
||||
@@ -69,9 +69,12 @@ export default class NoteInfoWidget extends CollapsibleWidget {
|
||||
|
||||
this.$type.text(note.type);
|
||||
|
||||
this.$mime
|
||||
.text(note.mime)
|
||||
.attr("title", note.mime);
|
||||
if (note.mime) {
|
||||
this.$mime.text('(' + note.mime + ')');
|
||||
}
|
||||
else {
|
||||
this.$mime.empty();
|
||||
}
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({loadResults}) {
|
||||
|
||||
@@ -854,8 +854,11 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
this.toggleInt(this.isEnabled());
|
||||
|
||||
const oldActiveNode = this.getActiveNode();
|
||||
let oldActiveNodeFocused = false;
|
||||
|
||||
if (oldActiveNode) {
|
||||
oldActiveNodeFocused = oldActiveNode.hasFocus();
|
||||
|
||||
oldActiveNode.setActive(false);
|
||||
oldActiveNode.setFocus(false);
|
||||
}
|
||||
@@ -868,8 +871,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
await this.expandToNote(this.tabContext.notePath);
|
||||
}
|
||||
|
||||
newActiveNode.setActive(true, {noEvents: true});
|
||||
|
||||
newActiveNode.setActive(true, {noEvents: true, noFocus: !oldActiveNodeFocused});
|
||||
newActiveNode.makeVisible({scrollIntoView: true});
|
||||
}
|
||||
}
|
||||
@@ -898,7 +900,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
|
||||
async entitiesReloadedEvent({loadResults}) {
|
||||
const activeNode = this.getActiveNode();
|
||||
const activeNodeFocused = activeNode ? activeNode.hasFocus() : false;
|
||||
const activeNodeFocused = activeNode && activeNode.hasFocus();
|
||||
const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null;
|
||||
const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
|
||||
const nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null;
|
||||
@@ -1021,7 +1023,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
}
|
||||
|
||||
if (node) {
|
||||
node.setActive(true, {noEvents: true});
|
||||
node.setActive(true, {noEvents: true, noFocus: true});
|
||||
}
|
||||
else {
|
||||
// this is used when original note has been deleted and we want to move the focus to the note above/below
|
||||
@@ -1036,7 +1038,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
|
||||
// return focus if the previously active node was also focused
|
||||
if (newActiveNode && activeNodeFocused) {
|
||||
newActiveNode.setFocus(true);
|
||||
await newActiveNode.setFocus(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1064,7 +1066,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
||||
if (activeNotePath) {
|
||||
const node = await this.getNodeFromPath(activeNotePath, true);
|
||||
|
||||
await node.setActive(true, {noEvents: true});
|
||||
await node.setActive(true, {noEvents: true, noFocus: true});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -229,7 +229,7 @@ export default class PromotedAttributesWidget extends TabAwareWidget {
|
||||
.prop("title", "Remove this attribute")
|
||||
.on('click', async () => {
|
||||
if (valueAttr.attributeId) {
|
||||
await server.remove("notes/" + this.noteId + "/attributes/" + valueAttr.attributeId);
|
||||
await server.remove("notes/" + this.noteId + "/attributes/" + valueAttr.attributeId, this.componentId);
|
||||
}
|
||||
|
||||
$tr.remove();
|
||||
@@ -263,8 +263,19 @@ export default class PromotedAttributesWidget extends TabAwareWidget {
|
||||
type: $attr.prop("attribute-type"),
|
||||
name: $attr.prop("attribute-name"),
|
||||
value: value
|
||||
});
|
||||
}, this.componentId);
|
||||
|
||||
$attr.prop("attribute-id", result.attributeId);
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({loadResults}) {console.log("loadResults", loadResults);
|
||||
// relation/label definitions are very often inherited by tree or template,
|
||||
// it's difficult to detect inheritance so we will
|
||||
if (loadResults.getAttributes(this.componentId).find(attr =>
|
||||
attr.noteId === this.noteId
|
||||
|| ['label-definition', 'relation-definition'].includes(attr.type))) {
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,13 +115,24 @@ function isAttributeType(type) {
|
||||
}
|
||||
|
||||
function isAttributeDangerous(type, name) {
|
||||
return BUILTIN_ATTRIBUTES.some(attr =>
|
||||
attr.type === attr.type &&
|
||||
return BUILTIN_ATTRIBUTES.some(attr =>
|
||||
attr.type === attr.type &&
|
||||
attr.name.toLowerCase() === name.trim().toLowerCase() &&
|
||||
attr.isDangerous
|
||||
);
|
||||
}
|
||||
|
||||
function getBuiltinAttributeNames() {
|
||||
return BUILTIN_ATTRIBUTES
|
||||
.map(attr => attr.name)
|
||||
.concat([
|
||||
'internalLink',
|
||||
'imageLink',
|
||||
'includeNoteLink',
|
||||
'relationMapLink'
|
||||
]);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getNotesWithLabel,
|
||||
getNotesWithLabels,
|
||||
@@ -131,5 +142,6 @@ module.exports = {
|
||||
createAttribute,
|
||||
getAttributeNames,
|
||||
isAttributeType,
|
||||
isAttributeDangerous
|
||||
};
|
||||
isAttributeDangerous,
|
||||
getBuiltinAttributeNames
|
||||
};
|
||||
|
||||
@@ -7,7 +7,9 @@ const dataDir = require('./data_dir');
|
||||
const log = require('./log');
|
||||
const sqlInit = require('./sql_init');
|
||||
const syncMutexService = require('./sync_mutex');
|
||||
const attributeService = require('./attributes');
|
||||
const cls = require('./cls');
|
||||
const utils = require('./utils');
|
||||
const sqlite = require('sqlite');
|
||||
const sqlite3 = require('sqlite3');
|
||||
|
||||
@@ -96,15 +98,22 @@ async function anonymize() {
|
||||
|
||||
await db.run("UPDATE api_tokens SET token = 'API token value'");
|
||||
await db.run("UPDATE notes SET title = 'title'");
|
||||
await db.run("UPDATE note_contents SET content = 'text'");
|
||||
await db.run("UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL");
|
||||
await db.run("UPDATE note_revisions SET title = 'title'");
|
||||
await db.run("UPDATE note_revision_contents SET content = 'title'");
|
||||
await db.run("UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label'");
|
||||
await db.run("UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name != 'template'");
|
||||
await db.run("UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL");
|
||||
|
||||
// we want to delete all non-builtin attributes because they can contain sensitive names and values
|
||||
// on the other hand builtin/system attrs should not contain any sensitive info
|
||||
const builtinAttrs = attributeService.getBuiltinAttributeNames().map(name => "'" + utils.sanitizeSql(name) + "'").join(', ');
|
||||
|
||||
await db.run(`UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN(${builtinAttrs})`);
|
||||
await db.run(`UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN (${builtinAttrs})`);
|
||||
await db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL");
|
||||
await db.run(`UPDATE options SET value = 'anonymized' WHERE name IN
|
||||
('documentId', 'documentSecret', 'encryptedDataKey', 'passwordVerificationHash',
|
||||
'passwordVerificationSalt', 'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')`);
|
||||
('documentId', 'documentSecret', 'encryptedDataKey',
|
||||
'passwordVerificationHash', 'passwordVerificationSalt',
|
||||
'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')
|
||||
AND value != ''`);
|
||||
await db.run("VACUUM");
|
||||
|
||||
await db.close();
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { buildDate:"2020-06-03T14:30:07+02:00", buildRevision: "c1fd9825aa6087b5061cdede5dba3f7f9dc62c31" };
|
||||
module.exports = { buildDate:"2020-06-08T10:43:12+02:00", buildRevision: "4426362799448b6228eedf20e7fc179ce4b3f860" };
|
||||
|
||||
@@ -47,7 +47,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
|
||||
|
||||
return noteIdMap[origNoteId];
|
||||
}
|
||||
|
||||
|
||||
function getMeta(filePath) {
|
||||
if (!metaFile) {
|
||||
return {};
|
||||
@@ -425,7 +425,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
|
||||
}
|
||||
|
||||
for (const noteId in createdNoteIds) { // now the noteIds are unique
|
||||
await noteService.scanForLinks(await repository.getNotes(noteId));
|
||||
await noteService.scanForLinks(await repository.getNote(noteId));
|
||||
|
||||
if (!metaFile) {
|
||||
// if there's no meta file then the notes are created based on the order in that tar file but that
|
||||
@@ -459,4 +459,4 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
|
||||
|
||||
module.exports = {
|
||||
importTar
|
||||
};
|
||||
};
|
||||
|
||||
@@ -454,7 +454,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
});
|
||||
|
||||
for (const noteId in createdNoteIds) { // now the noteIds are unique
|
||||
await noteService.scanForLinks(await repository.getNotes(noteId));
|
||||
await noteService.scanForLinks(await repository.getNote(noteId));
|
||||
|
||||
if (!metaFile) {
|
||||
// if there's no meta file then the notes are created based on the order in that tar file but that
|
||||
@@ -481,4 +481,4 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
|
||||
module.exports = {
|
||||
importZip
|
||||
};
|
||||
};
|
||||
|
||||
@@ -205,6 +205,14 @@ function formatDownloadTitle(filename, type, mime) {
|
||||
}
|
||||
}
|
||||
|
||||
if (mime === 'application/octet-stream') {
|
||||
// we didn't find any good guess for this one, it will be better to just return
|
||||
// the current name without fake extension. It's possible that the title still preserves to correct
|
||||
// extension too
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
return filename + '.' + extensions[0];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user