mirror of
https://github.com/zadam/trilium.git
synced 2025-11-05 04:45:47 +01:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9a2cacb5b | ||
|
|
17085e5578 | ||
|
|
fc7da015fe | ||
|
|
adf222b5e8 | ||
|
|
ade22ea825 | ||
|
|
2c8fb90ecb | ||
|
|
c67644a2e3 | ||
|
|
346f6edd7e | ||
|
|
92f586486f | ||
|
|
e7c6d912a4 | ||
|
|
290f7e2101 | ||
|
|
5e0fbea3b3 | ||
|
|
7b2c3afe4c | ||
|
|
2e181d0fb1 | ||
|
|
68a03211ce | ||
|
|
f2a19c56b1 | ||
|
|
a98fd509c6 | ||
|
|
a3149aecf4 | ||
|
|
ef825371cf | ||
|
|
b567775129 |
BIN
db/demo.zip
BIN
db/demo.zip
Binary file not shown.
@@ -7,6 +7,6 @@ module.exports = () => {
|
||||
beccaLoader.load();
|
||||
// make sure the hidden subtree exists since the subsequent migrations we will move some existing notes into it (share...)
|
||||
// in previous releases hidden subtree was created lazily
|
||||
hiddenSubtreeService.checkHiddenSubtree();
|
||||
hiddenSubtreeService.checkHiddenSubtree(true);
|
||||
});
|
||||
};
|
||||
|
||||
5
package-lock.json
generated
5
package-lock.json
generated
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.58.5",
|
||||
"version": "0.58.7",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "trilium",
|
||||
"version": "0.58.5",
|
||||
"version": "0.58.7",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.58.6",
|
||||
"version": "0.58.8",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
|
||||
@@ -152,6 +152,10 @@ class Becca {
|
||||
.replace('_', '')
|
||||
);
|
||||
|
||||
if (!(camelCaseEntityName in this)) {
|
||||
throw new Error(`Unknown entity name '${camelCaseEntityName}' (original argument '${entityName}')`);
|
||||
}
|
||||
|
||||
return this[camelCaseEntityName][entityId];
|
||||
}
|
||||
|
||||
|
||||
@@ -83,10 +83,8 @@ function getNoteTitleArrayForPath(notePathArray) {
|
||||
throw new Error(`${notePathArray} is not an array.`);
|
||||
}
|
||||
|
||||
const hoistedNoteId = cls.getHoistedNoteId();
|
||||
|
||||
if (notePathArray.length === 1 && notePathArray[0] === hoistedNoteId) {
|
||||
return [getNoteTitle(hoistedNoteId)];
|
||||
if (notePathArray.length === 1) {
|
||||
return [getNoteTitle(notePathArray[0])];
|
||||
}
|
||||
|
||||
const titles = [];
|
||||
@@ -95,6 +93,7 @@ function getNoteTitleArrayForPath(notePathArray) {
|
||||
let hoistedNotePassed = false;
|
||||
|
||||
// this is a notePath from outside of hoisted subtree so full title path needs to be returned
|
||||
const hoistedNoteId = cls.getHoistedNoteId();
|
||||
const outsideOfHoistedSubtree = !notePathArray.includes(hoistedNoteId);
|
||||
|
||||
for (const noteId of notePathArray) {
|
||||
|
||||
@@ -80,14 +80,14 @@ class AbstractEntity {
|
||||
*
|
||||
* @returns {this}
|
||||
*/
|
||||
save() {
|
||||
save(opts = {}) {
|
||||
const entityName = this.constructor.entityName;
|
||||
const primaryKeyName = this.constructor.primaryKeyName;
|
||||
|
||||
const isNewEntity = !this[primaryKeyName];
|
||||
|
||||
if (this.beforeSaving) {
|
||||
this.beforeSaving();
|
||||
this.beforeSaving(opts);
|
||||
}
|
||||
|
||||
const pojo = this.getPojoToSave();
|
||||
|
||||
@@ -176,8 +176,10 @@ class Attribute extends AbstractEntity {
|
||||
return !(this.attributeId in this.becca.attributes);
|
||||
}
|
||||
|
||||
beforeSaving() {
|
||||
beforeSaving(opts = {}) {
|
||||
if (!opts.skipValidation) {
|
||||
this.validate();
|
||||
}
|
||||
|
||||
this.name = sanitizeAttributeName(this.name);
|
||||
|
||||
|
||||
@@ -79,11 +79,12 @@ export default class TabManager extends Component {
|
||||
filteredTabs = filteredTabs.filter(tab => tab.active);
|
||||
}
|
||||
|
||||
if (filteredTabs.length === 0) {
|
||||
const [notePath] = treeService.getHashValueFromAddress();
|
||||
// resolve before opened tabs can change this
|
||||
const [notePathInUrl, ntxIdInUrl] = treeService.getHashValueFromAddress();
|
||||
|
||||
if (filteredTabs.length === 0) {
|
||||
filteredTabs.push({
|
||||
notePath: notePath || 'root',
|
||||
notePath: notePathInUrl || 'root',
|
||||
active: true,
|
||||
hoistedNoteId: glob.extraHoistedNoteId || 'root'
|
||||
});
|
||||
@@ -95,17 +96,14 @@ export default class TabManager extends Component {
|
||||
|
||||
await this.tabsUpdate.allowUpdateWithoutChange(async () => {
|
||||
for (const tab of filteredTabs) {
|
||||
|
||||
await this.openContextWithNote(tab.notePath, tab.active, tab.ntxId, tab.hoistedNoteId, tab.mainNtxId);
|
||||
}
|
||||
});
|
||||
|
||||
// if there's notePath in the URL, make sure it's open and active
|
||||
// (useful, for e.g. opening clipped notes from clipper or opening link in an extra window)
|
||||
if (treeService.isNotePathInAddress()) {
|
||||
const [notePath, ntxId] = treeService.getHashValueFromAddress();
|
||||
|
||||
await appContext.tabManager.switchToNoteContext(ntxId, notePath);
|
||||
if (notePathInUrl) {
|
||||
await appContext.tabManager.switchToNoteContext(ntxIdInUrl, notePathInUrl);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
@@ -688,7 +688,7 @@ class NoteShort {
|
||||
return promotedAttrs;
|
||||
}
|
||||
|
||||
hasAncestor(ancestorNoteId, visitedNoteIds = null) {
|
||||
hasAncestor(ancestorNoteId, followTemplates = false, visitedNoteIds = null) {
|
||||
if (this.noteId === ancestorNoteId) {
|
||||
return true;
|
||||
}
|
||||
@@ -702,14 +702,16 @@ class NoteShort {
|
||||
|
||||
visitedNoteIds.add(this.noteId);
|
||||
|
||||
if (followTemplates) {
|
||||
for (const templateNote of this.getTemplateNotes()) {
|
||||
if (templateNote.hasAncestor(ancestorNoteId, visitedNoteIds)) {
|
||||
if (templateNote.hasAncestor(ancestorNoteId, followTemplates, visitedNoteIds)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const parentNote of this.getParentNotes()) {
|
||||
if (parentNote.hasAncestor(ancestorNoteId, visitedNoteIds)) {
|
||||
if (parentNote.hasAncestor(ancestorNoteId, followTemplates, visitedNoteIds)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ function isAffecting(attrRow, affectedNote) {
|
||||
|
||||
if (this.isInheritable) {
|
||||
for (const owningNote of owningNotes) {
|
||||
if (owningNote.hasAncestor(attrNote.noteId)) {
|
||||
if (owningNote.hasAncestor(attrNote.noteId, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,6 +242,7 @@ const ATTR_HELP = {
|
||||
"keepCurrentHoisting": "Opening this link won't change hoisting even if the note is not displayable in the current hoisted subtree.",
|
||||
"executeButton": "Title of the button which will execute the current code note",
|
||||
"executeDescription": "Longer description of the current code note displayed together with the execute button",
|
||||
"excludeFromNoteMap": "Notes with this label will be hidden from the Note Map"
|
||||
},
|
||||
"relation": {
|
||||
"runOnNoteCreation": "executes when note is created on backend. Use this relation if you want to run the script for all notes created under a specific subtree. In that case, create it on the subtree root note and make it inheritable. A new note created within the subtree (any depth) will trigger the script.",
|
||||
|
||||
@@ -303,7 +303,7 @@ export default class GlobalMenuWidget extends BasicWidget {
|
||||
const resp = await fetch(RELEASES_API_URL);
|
||||
const data = await resp.json();
|
||||
|
||||
return data.tag_name.substring(1);
|
||||
return data?.tag_name?.substring(1);
|
||||
}
|
||||
|
||||
downloadLatestVersionCommand() {
|
||||
|
||||
@@ -4,7 +4,6 @@ const TPL = `
|
||||
<div class="dropdown right-dropdown-widget dropright">
|
||||
<style>
|
||||
.right-dropdown-widget {
|
||||
width: 53px;
|
||||
height: 53px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import options from "../../services/options.js";
|
||||
import FlexContainer from "./flex_container.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
|
||||
export default class LeftPaneContainer extends FlexContainer {
|
||||
constructor() {
|
||||
@@ -16,7 +17,15 @@ export default class LeftPaneContainer extends FlexContainer {
|
||||
|
||||
entitiesReloadedEvent({loadResults}) {
|
||||
if (loadResults.isOptionReloaded("leftPaneVisible")) {
|
||||
this.toggleInt(this.isEnabled());
|
||||
const visible = this.isEnabled();
|
||||
this.toggleInt(visible);
|
||||
|
||||
if (visible) {
|
||||
this.triggerEvent('focusTree');
|
||||
} else {
|
||||
const activeNoteContext = appContext.tabManager.getActiveContext();
|
||||
this.triggerEvent('focusOnDetail', {ntxId: activeNoteContext.ntxId});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -832,6 +832,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
});
|
||||
|
||||
await this.filterHoistedBranch();
|
||||
|
||||
const activeNode = await this.getNodeFromPath(appContext.tabManager.getActiveContextNotePath());
|
||||
|
||||
if (activeNode) {
|
||||
@@ -887,6 +889,11 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
}
|
||||
|
||||
async focusTreeEvent() {
|
||||
this.tree.$container.focus();
|
||||
this.tree.setFocus(true);
|
||||
}
|
||||
|
||||
/** @returns {FancytreeNode} */
|
||||
async getNodeFromPath(notePath, expand = false, logErrors = true) {
|
||||
utils.assertArguments(notePath);
|
||||
@@ -1074,6 +1081,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
|
||||
this.filterHoistedBranch();
|
||||
}, 600 * 1000);
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ class ImageTypeWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
async doRefresh(note) {
|
||||
this.$imageView.prop("src", `api/images/${note.noteId}/${note.title}`);
|
||||
this.$imageView.prop("src", `api/images/${note.noteId}/${encodeURIComponent(note.title)}`);
|
||||
}
|
||||
|
||||
copyImageReferenceToClipboardEvent({ntxId}) {
|
||||
|
||||
@@ -871,12 +871,11 @@ body {
|
||||
#launcher-pane .launcher-button {
|
||||
font-size: 150%;
|
||||
display: inline-block;
|
||||
padding: 15px 15px;
|
||||
padding: 13px 13px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
color: var(--launcher-pane-text-color);
|
||||
background-color: var(--launcher-pane-background-color);
|
||||
width: 53px;
|
||||
height: 53px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { buildDate:"2023-01-16T22:39:28+01:00", buildRevision: "9fd0b85ff2be264be35ec2052c956b654f0dac9e" };
|
||||
module.exports = { buildDate:"2023-02-13T21:50:54+01:00", buildRevision: "17085e5578d2a20a77a6ade058f74e6d5b798ecc" };
|
||||
|
||||
@@ -148,13 +148,28 @@ class ConsistencyChecks {
|
||||
AND notes.noteId IS NULL`,
|
||||
({branchId, parentNoteId}) => {
|
||||
if (this.autoFix) {
|
||||
const branch = becca.getBranch(branchId);
|
||||
branch.parentNoteId = 'root';
|
||||
branch.save();
|
||||
// Delete the old branch and recreate it with root as parent.
|
||||
const oldBranch = becca.getBranch(branchId);
|
||||
const noteId = oldBranch.noteId;
|
||||
oldBranch.markAsDeleted("missing-parent");
|
||||
|
||||
let message = `Branch '${branchId}' was was missing parent note '${parentNoteId}', so it was deleted. `;
|
||||
|
||||
if (becca.getNote(noteId).getParentBranches().length === 0) {
|
||||
const newBranch = new Branch({
|
||||
parentNoteId: 'root',
|
||||
noteId: noteId,
|
||||
prefix: 'recovered'
|
||||
}).save();
|
||||
|
||||
message += `${newBranch.branchId} was created in the root instead.`;
|
||||
} else {
|
||||
message += `There is one or more valid branches, so no new one will be created as a replacement.`;
|
||||
}
|
||||
|
||||
this.reloadNeeded = true;
|
||||
|
||||
logFix(`Branch '${branchId}' was set to root parent since it was referencing missing parent note '${parentNoteId}'`);
|
||||
logFix(message);
|
||||
} else {
|
||||
logError(`Branch '${branchId}' references missing parent note '${parentNoteId}'`);
|
||||
}
|
||||
@@ -428,10 +443,17 @@ class ConsistencyChecks {
|
||||
const branches = branchIds.map(branchId => becca.getBranch(branchId));
|
||||
|
||||
for (const branch of branches) {
|
||||
branch.parentNoteId = 'root';
|
||||
branch.save();
|
||||
// delete the old wrong branch
|
||||
branch.markAsDeleted("parent-is-search");
|
||||
|
||||
logFix(`Child branch '${branch.branchId}' has been moved to root since it was a child of a search note '${parentNoteId}'`)
|
||||
// create a replacement branch in root parent
|
||||
new Branch({
|
||||
parentNoteId: 'root',
|
||||
noteId: branch.noteId,
|
||||
prefix: 'recovered'
|
||||
}).save();
|
||||
|
||||
logFix(`Note '${branch.noteId}' has been moved to root since it was a child of a search note '${parentNoteId}'`)
|
||||
}
|
||||
|
||||
this.reloadNeeded = true;
|
||||
|
||||
@@ -100,15 +100,38 @@ function fillEntityChanges(entityName, entityPrimaryKey, condition = '') {
|
||||
if (existingRows === 0) {
|
||||
createdCount++;
|
||||
|
||||
let hash;
|
||||
let utcDateChanged;
|
||||
let isSynced;
|
||||
|
||||
if (entityName.endsWith("_contents")) {
|
||||
// FIXME: hacky, not sure if it might cause some problems
|
||||
hash = "fake value";
|
||||
utcDateChanged = dateUtils.utcNowDateTime();
|
||||
isSynced = true; // contents are always synced
|
||||
} else {
|
||||
const entity = becca.getEntity(entityName, entityId);
|
||||
|
||||
if (entity) {
|
||||
hash = entity?.generateHash() || "|deleted";
|
||||
utcDateChanged = entity?.getUtcDateChanged() || dateUtils.utcNowDateTime();
|
||||
isSynced = entityName !== 'options' || !!entity?.isSynced;
|
||||
} else {
|
||||
// entity might be null (not present in becca) when it's deleted
|
||||
// FIXME: hacky, not sure if it might cause some problems
|
||||
hash = "deleted";
|
||||
utcDateChanged = dateUtils.utcNowDateTime();
|
||||
isSynced = true; // deletable (the ones with isDeleted) entities are synced
|
||||
}
|
||||
}
|
||||
|
||||
addEntityChange({
|
||||
entityName,
|
||||
entityId,
|
||||
hash: entity.generateHash(),
|
||||
hash: hash,
|
||||
isErased: false,
|
||||
utcDateChanged: entity.getUtcDateChanged(),
|
||||
isSynced: entityName !== 'options' || !!entity.isSynced
|
||||
utcDateChanged: utcDateChanged,
|
||||
isSynced: isSynced
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,8 +234,8 @@ const HIDDEN_SUBTREE_DEFINITION = {
|
||||
]
|
||||
};
|
||||
|
||||
function checkHiddenSubtree() {
|
||||
if (!migrationService.isDbUpToDate()) {
|
||||
function checkHiddenSubtree(force = false) {
|
||||
if (!force && !migrationService.isDbUpToDate()) {
|
||||
// on-delete hook might get triggered during some future migration and cause havoc
|
||||
log.info("Will not check hidden subtree until migration is finished.");
|
||||
return;
|
||||
|
||||
@@ -661,7 +661,8 @@ function undeleteBranch(branchId, deleteId, taskContext) {
|
||||
OR (type = 'relation' AND value = ?))`, [deleteId, note.noteId, note.noteId]);
|
||||
|
||||
for (const attribute of attributes) {
|
||||
new Attribute(attribute).save();
|
||||
// relation might point to a note which hasn't been undeleted yet and would thus throw up
|
||||
new Attribute(attribute).save({skipValidation: true});
|
||||
}
|
||||
|
||||
const childBranchIds = sql.getColumn(`
|
||||
|
||||
Reference in New Issue
Block a user