mirror of
https://github.com/zadam/trilium.git
synced 2025-11-02 11:26:15 +01:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b30291601 | ||
|
|
5193f073e9 | ||
|
|
6c7d8a9667 | ||
|
|
5e9bedd903 | ||
|
|
e712990c03 | ||
|
|
91487b338a | ||
|
|
3ff24d53e5 | ||
|
|
94c904fb40 | ||
|
|
5f258fbbbf | ||
|
|
bf9ad976b9 | ||
|
|
434d8ef48c | ||
|
|
c8ba07a4ae |
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<dataSource name="document.db">
|
||||
<database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.16">
|
||||
<database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.17">
|
||||
<root id="1">
|
||||
<ServerVersion>3.25.1</ServerVersion>
|
||||
</root>
|
||||
|
||||
@@ -20,7 +20,7 @@ SELECT noteId, title, -1, isProtected, type, mime, hash, isDeleted, isErased, da
|
||||
DROP TABLE notes;
|
||||
ALTER TABLE notes_mig RENAME TO notes;
|
||||
|
||||
UPDATE notes SET contentLength = (SELECT COALESCE(LENGTH(content), 0) FROM note_contents WHERE note_contents.noteId = notes.noteId);
|
||||
UPDATE notes SET contentLength = COALESCE((SELECT COALESCE(LENGTH(content), 0) FROM note_contents WHERE note_contents.noteId = notes.noteId), -1);
|
||||
|
||||
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (`isDeleted`);
|
||||
CREATE INDEX `IDX_notes_title` ON `notes` (`title`);
|
||||
|
||||
2
libraries/ckeditor/ckeditor.js
vendored
2
libraries/ckeditor/ckeditor.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -498,7 +498,7 @@
|
||||
t._started = false;
|
||||
onRenderStop();
|
||||
} else {
|
||||
setImmediate(step);
|
||||
requestIdleCallback(step, { timeout: 10 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.37.3",
|
||||
"version": "0.37.5",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.37.4",
|
||||
"version": "0.37.6",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
|
||||
@@ -788,6 +788,7 @@ class Note extends Entity {
|
||||
delete pojo.isContentAvailable;
|
||||
delete pojo.__attributeCache;
|
||||
delete pojo.content;
|
||||
/** zero references to contentHash, probably can be removed */
|
||||
delete pojo.contentHash;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ export default class SidebarOptions {
|
||||
this.$sidebarMinWidth.val(options.sidebarMinWidth);
|
||||
this.$sidebarWidthPercent.val(options.sidebarWidthPercent);
|
||||
|
||||
if (parseInt(options.showSidebarInNewTab)) {
|
||||
if (options.showSidebarInNewTab === 'true') {
|
||||
this.$showSidebarInNewTab.attr("checked", "checked");
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -273,7 +273,9 @@ async function filterTabs(noteId) {
|
||||
|
||||
async function noteDeleted(noteId) {
|
||||
for (const tc of tabContexts) {
|
||||
if (tc.notePath && tc.notePath.split("/").includes(noteId)) {
|
||||
// not removing active even if it contains deleted note since that one will move to another note (handled by deletion logic)
|
||||
// and we would lose tab context state (e.g. sidebar visibility)
|
||||
if (!tc.isActive() && tc.notePath && tc.notePath.split("/").includes(noteId)) {
|
||||
await tabRow.removeTab(tc.$tab[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,11 +246,15 @@ class TabContext {
|
||||
}
|
||||
|
||||
setCurrentNotePathToHash() {
|
||||
if (this.$tab[0] === this.tabRow.activeTabEl) {
|
||||
if (this.isActive()) {
|
||||
document.location.hash = (this.notePath || "") + "-" + this.tabId;
|
||||
}
|
||||
}
|
||||
|
||||
isActive() {
|
||||
return this.$tab[0] === this.tabRow.activeTabEl;
|
||||
}
|
||||
|
||||
setupClasses() {
|
||||
for (const clazz of Array.from(this.$tab[0].classList)) { // create copy to safely iterate over while removing classes
|
||||
if (clazz !== 'note-tab') {
|
||||
|
||||
@@ -42,7 +42,7 @@ class TreeContextMenu {
|
||||
|| (selNodes.length === 1 && selNodes[0] === this.node);
|
||||
|
||||
const notSearch = note.type !== 'search';
|
||||
const parentNotSearch = parentNote.type !== 'search';
|
||||
const parentNotSearch = !parentNote || parentNote.type !== 'search';
|
||||
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
|
||||
|
||||
return [
|
||||
@@ -79,7 +79,7 @@ class TreeContextMenu {
|
||||
{ title: "Paste after", cmd: "pasteAfter", uiIcon: "paste",
|
||||
enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes },
|
||||
{ title: "Duplicate note here", cmd: "duplicateNote", uiIcon: "empty",
|
||||
enabled: noSelectedNotes && parentNotSearch && (!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) },
|
||||
enabled: noSelectedNotes && parentNotSearch && isNotRoot && !isHoisted && (!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) },
|
||||
{ title: "----" },
|
||||
{ title: "Export", cmd: "export", uiIcon: "empty",
|
||||
enabled: notSearch && noSelectedNotes },
|
||||
|
||||
@@ -127,11 +127,13 @@ async function consumeSyncData() {
|
||||
}
|
||||
|
||||
function connectWebSocket() {
|
||||
const protocol = document.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const loc = window.location;
|
||||
const webSocketUri = (loc.protocol === "https:" ? "wss:" : "ws:")
|
||||
+ "//" + loc.host + loc.pathname;
|
||||
|
||||
// use wss for secure messaging
|
||||
const ws = new WebSocket(protocol + "://" + location.host);
|
||||
ws.onopen = () => console.debug(utils.now(), "Connected to server with WebSocket");
|
||||
const ws = new WebSocket(webSocketUri);
|
||||
ws.onopen = () => console.debug(utils.now(), `Connected to server ${webSocketUri} with WebSocket`);
|
||||
ws.onmessage = handleMessage;
|
||||
// we're not handling ws.onclose here because reconnection is done in sendPing()
|
||||
|
||||
|
||||
@@ -89,6 +89,11 @@ body {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
#context-menu-container {
|
||||
max-height: 100vh;
|
||||
overflow: auto; /* make it scrollable when exceeding total height of the window */
|
||||
}
|
||||
|
||||
#context-menu-container, #context-menu-container .dropdown-menu {
|
||||
padding: 3px 0 0;
|
||||
z-index: 1111;
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { buildDate:"2019-11-22T22:38:03+01:00", buildRevision: "7a2c7edd7e9b975bf64f732629e711379baecf48" };
|
||||
module.exports = { buildDate:"2019-11-26T22:50:08+01:00", buildRevision: "5193f073e9e55f5440fe2e71fbd2cdfcdb2d2c6b" };
|
||||
|
||||
@@ -311,6 +311,25 @@ async function findLogicIssues() {
|
||||
}
|
||||
});
|
||||
|
||||
await findAndFixIssues(`
|
||||
SELECT notes.noteId
|
||||
FROM notes
|
||||
LEFT JOIN note_contents USING(noteId)
|
||||
WHERE
|
||||
note_contents.noteId IS NULL`,
|
||||
async ({noteId}, autoFix) => {
|
||||
if (autoFix) {
|
||||
const note = await repository.getNote(noteId);
|
||||
// empty string might be wrong choice for some note types (and protected notes) but it's a best guess
|
||||
await note.setContent(note.isErased ? null : '');
|
||||
|
||||
logFix(`Note ${noteId} content was set to empty string since there was no corresponding row`);
|
||||
}
|
||||
else {
|
||||
logError(`Note ${noteId} content row does not exist`);
|
||||
}
|
||||
});
|
||||
|
||||
await findAndFixIssues(`
|
||||
SELECT noteId
|
||||
FROM notes
|
||||
@@ -321,6 +340,7 @@ async function findLogicIssues() {
|
||||
async ({noteId}, autoFix) => {
|
||||
if (autoFix) {
|
||||
const note = await repository.getNote(noteId);
|
||||
// empty string might be wrong choice for some note types (and protected notes) but it's a best guess
|
||||
await note.setContent('');
|
||||
|
||||
logFix(`Note ${noteId} content was set to empty string since it was null even though it is not deleted`);
|
||||
@@ -360,6 +380,25 @@ async function findLogicIssues() {
|
||||
}
|
||||
});
|
||||
|
||||
await findAndFixIssues(`
|
||||
SELECT note_revisions.noteRevisionId
|
||||
FROM note_revisions
|
||||
LEFT JOIN note_revision_contents USING(noteRevisionId)
|
||||
WHERE note_revision_contents.noteRevisionId IS NULL`,
|
||||
async ({noteRevisionId}, autoFix) => {
|
||||
if (autoFix) {
|
||||
const noteRevision = await repository.getNoteRevision(noteRevisionId);
|
||||
await noteRevision.setContent(null);
|
||||
noteRevision.isErased = true;
|
||||
await noteRevision.save();
|
||||
|
||||
logFix(`Note revision content ${noteRevisionId} was created and set to erased since it did not exist.`);
|
||||
}
|
||||
else {
|
||||
logError(`Note revision content ${noteRevisionId} does not exist`);
|
||||
}
|
||||
});
|
||||
|
||||
await findAndFixIssues(`
|
||||
SELECT noteRevisionId
|
||||
FROM note_revisions
|
||||
|
||||
@@ -3,6 +3,7 @@ const fileType = require('file-type');
|
||||
const stream = require('stream');
|
||||
const log = require("../log");
|
||||
const utils = require("../utils");
|
||||
const sql = require("../sql");
|
||||
const noteService = require("../notes");
|
||||
const imageService = require("../image");
|
||||
const protectedSessionService = require('../protected_session');
|
||||
@@ -11,7 +12,7 @@ const protectedSessionService = require('../protected_session');
|
||||
function parseDate(text) {
|
||||
// insert - and : to make it ISO format
|
||||
text = text.substr(0, 4) + "-" + text.substr(4, 2) + "-" + text.substr(6, 2)
|
||||
+ "T" + text.substr(9, 2) + ":" + text.substr(11, 2) + ":" + text.substr(13, 2) + "Z";
|
||||
+ " " + text.substr(9, 2) + ":" + text.substr(11, 2) + ":" + text.substr(13, 2) + ".000Z";
|
||||
|
||||
return text;
|
||||
}
|
||||
@@ -150,7 +151,7 @@ async function importEnex(taskContext, file, parentNote) {
|
||||
} else if (currentTag === 'created') {
|
||||
note.utcDateCreated = parseDate(text);
|
||||
} else if (currentTag === 'updated') {
|
||||
// updated is currently ignored since utcDateModified is updated automatically with each save
|
||||
note.utcDateModified = parseDate(text);
|
||||
} else if (currentTag === 'tag') {
|
||||
note.attributes.push({
|
||||
type: 'label',
|
||||
@@ -187,9 +188,27 @@ async function importEnex(taskContext, file, parentNote) {
|
||||
}
|
||||
});
|
||||
|
||||
async function updateDates(noteId, utcDateCreated, utcDateModified) {
|
||||
// it's difficult to force custom dateCreated and dateModified to Note entity so we do it post-creation with SQL
|
||||
await sql.execute(`
|
||||
UPDATE notes
|
||||
SET dateCreated = ?,
|
||||
utcDateCreated = ?,
|
||||
dateModified = ?,
|
||||
utcDateModified = ?
|
||||
WHERE noteId = ?`,
|
||||
[utcDateCreated, utcDateCreated, utcDateModified, utcDateModified, noteId]);
|
||||
|
||||
await sql.execute(`
|
||||
UPDATE note_contents
|
||||
SET utcDateModified = ?
|
||||
WHERE noteId = ?`,
|
||||
[utcDateModified, noteId]);
|
||||
}
|
||||
|
||||
async function saveNote() {
|
||||
// make a copy because stream continues with the next async call and note gets overwritten
|
||||
let {title, content, attributes, resources, utcDateCreated} = note;
|
||||
let {title, content, attributes, resources, utcDateCreated, utcDateModified} = note;
|
||||
|
||||
content = extractContent(content);
|
||||
|
||||
@@ -201,6 +220,10 @@ async function importEnex(taskContext, file, parentNote) {
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
||||
})).note;
|
||||
|
||||
utcDateCreated = utcDateCreated || noteEntity.utcDateCreated;
|
||||
// sometime date modified is not present in ENEX, then use date created
|
||||
utcDateModified = utcDateModified || utcDateCreated;
|
||||
|
||||
taskContext.increaseProgressCount();
|
||||
|
||||
let noteContent = await noteEntity.getContent();
|
||||
@@ -224,6 +247,8 @@ async function importEnex(taskContext, file, parentNote) {
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
||||
})).note;
|
||||
|
||||
await updateDates(resourceNote.noteId, utcDateCreated, utcDateModified);
|
||||
|
||||
taskContext.increaseProgressCount();
|
||||
|
||||
const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`;
|
||||
@@ -235,7 +260,9 @@ async function importEnex(taskContext, file, parentNote) {
|
||||
try {
|
||||
const originalName = "image." + resource.mime.substr(6);
|
||||
|
||||
const {url} = await imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
|
||||
const {url, note: imageNote} = await imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
|
||||
|
||||
await updateDates(imageNote.noteId, utcDateCreated, utcDateModified);
|
||||
|
||||
const imageLink = `<img src="${url}">`;
|
||||
|
||||
@@ -257,6 +284,10 @@ async function importEnex(taskContext, file, parentNote) {
|
||||
|
||||
// save updated content with links to files/images
|
||||
await noteEntity.setContent(noteContent);
|
||||
|
||||
await noteService.scanForLinks(noteEntity.noteId);
|
||||
|
||||
await updateDates(noteEntity.noteId, utcDateCreated, utcDateModified);
|
||||
}
|
||||
|
||||
saxStream.on("closetag", async tag => {
|
||||
|
||||
@@ -37,7 +37,7 @@ function isProtectedSessionAvailable() {
|
||||
function decryptNotes(notes) {
|
||||
for (const note of notes) {
|
||||
if (note.isProtected) {
|
||||
note.title = decrypt(note.title);
|
||||
note.title = decryptString(note.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user