Compare commits

...

16 Commits

Author SHA1 Message Date
zadam
0a0de7312c release 0.40.7 2020-03-28 20:58:20 +01:00
zadam
b4b22d9353 workaround for overflowing component wrapper in landscape mobile frontend, fixes #933 2020-03-28 19:39:14 +01:00
zadam
d3eb640aa2 fix upload image from mobile frontend, closes #931 2020-03-28 19:25:19 +01:00
zadam
8ccc48c25d images in include note should have max 100% width, #922 2020-03-21 09:52:13 +01:00
zadam
9a1a76605a fix OPML export of book notes, closes #919 2020-03-19 09:42:41 +01:00
zadam
a717ee00fb release 0.40.6 2020-03-15 11:21:43 +01:00
zadam
f5e27278ab fix migration script to preserve sync IDs 2020-03-15 11:19:30 +01:00
zadam
20c24e26cc added entity constructors for "content" tables 2020-03-14 21:09:07 +01:00
zadam
3bafc396fc fix consistency checks 2020-03-14 13:13:27 +01:00
zadam
3fa3e912a4 not sending ping to clients after every sync addition, only after commit which significantly speeds up imports 2020-03-14 12:39:55 +01:00
zadam
44219e7ccc do not load the note content during ENEX import again since it's already available 2020-03-13 22:23:44 +01:00
zadam
5b67854cbe clear history in the code mirror after setting a value, closes #766 2020-03-10 22:48:21 +01:00
zadam
c6d912dcb7 sync only changes with isSynced = true 2020-03-09 21:34:03 +01:00
zadam
da53c1eaa8 updated schema.sql 2020-03-09 21:23:11 +01:00
zadam
73bf2dcb02 added isSynced to sync table to allow forward compatibility with 0.41 2020-03-09 20:56:43 +01:00
zadam
719f10981e fix sync 2020-03-08 22:00:12 +01:00
20 changed files with 89 additions and 48 deletions

View File

@@ -635,20 +635,26 @@ parentNoteId</ColNames>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="145" parent="18" name="utcSyncDate">
<column id="145" parent="18" name="isSynced">
<Position>5</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="146" parent="18" name="utcSyncDate">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="146" parent="18" name="IDX_sync_entityName_entityId">
<index id="147" parent="18" name="IDX_sync_entityName_entityId">
<ColNames>entityName
entityId</ColNames>
<Unique>1</Unique>
</index>
<index id="147" parent="18" name="IDX_sync_utcSyncDate">
<index id="148" parent="18" name="IDX_sync_utcSyncDate">
<ColNames>utcSyncDate</ColNames>
</index>
<key id="148" parent="18">
<key id="149" parent="18">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>

View File

@@ -0,0 +1,22 @@
CREATE TABLE IF NOT EXISTS "sync_mig" (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`entityName` TEXT NOT NULL,
`entityId` TEXT NOT NULL,
`sourceId` TEXT NOT NULL,
`isSynced` INTEGER default 0 not null,
`utcSyncDate` TEXT NOT NULL);
INSERT INTO sync_mig (id, entityName, entityId, sourceId, isSynced, utcSyncDate)
SELECT id, entityName, entityId, sourceId, 1, utcSyncDate FROM sync;
DROP TABLE sync;
ALTER TABLE sync_mig RENAME TO sync;
CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` (
`entityName`,
`entityId`
);
CREATE INDEX `IDX_sync_utcSyncDate` ON `sync` (
`utcSyncDate`
);

View File

@@ -1,16 +1,8 @@
CREATE TABLE IF NOT EXISTS "sync" (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`entityName` TEXT NOT NULL,
`entityId` TEXT NOT NULL,
`sourceId` TEXT NOT NULL,
`utcSyncDate` TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS "source_ids" (
`sourceId` TEXT NOT NULL,
`utcDateCreated` TEXT NOT NULL,
PRIMARY KEY(`sourceId`)
);
CREATE INDEX IDX_source_ids_utcDateCreated
on source_ids (utcDateCreated);
CREATE TABLE IF NOT EXISTS "api_tokens"
(
apiTokenId TEXT PRIMARY KEY NOT NULL,
@@ -27,13 +19,6 @@ CREATE TABLE IF NOT EXISTS "options"
utcDateCreated TEXT not null,
utcDateModified TEXT NOT NULL
);
CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` (
`entityName`,
`entityId`
);
CREATE INDEX `IDX_sync_utcSyncDate` ON `sync` (
`utcSyncDate`
);
CREATE TABLE IF NOT EXISTS "note_contents" (
`noteId` TEXT NOT NULL,
`content` TEXT NULL DEFAULT NULL,
@@ -72,6 +57,8 @@ CREATE INDEX `IDX_note_revisions_utcDateCreated` ON `note_revisions` (`utcDateCr
CREATE INDEX `IDX_note_revisions_utcDateLastEdited` ON `note_revisions` (`utcDateLastEdited`);
CREATE INDEX `IDX_note_revisions_dateCreated` ON `note_revisions` (`dateCreated`);
CREATE INDEX `IDX_note_revisions_dateLastEdited` ON `note_revisions` (`dateLastEdited`);
CREATE INDEX IDX_source_ids_utcDateCreated
on source_ids (utcDateCreated);
CREATE TABLE IF NOT EXISTS "notes" (
`noteId` TEXT NOT NULL,
`title` TEXT NOT NULL DEFAULT "note",
@@ -130,3 +117,17 @@ CREATE INDEX IDX_attributes_noteId_index
on attributes (noteId);
CREATE INDEX IDX_attributes_value_index
on attributes (value);
CREATE TABLE IF NOT EXISTS "sync" (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`entityName` TEXT NOT NULL,
`entityId` TEXT NOT NULL,
`sourceId` TEXT NOT NULL,
`isSynced` INTEGER default 0 not null,
`utcSyncDate` TEXT NOT NULL);
CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` (
`entityName`,
`entityId`
);
CREATE INDEX `IDX_sync_utcSyncDate` ON `sync` (
`utcSyncDate`
);

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.40.5",
"version": "0.40.7",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {

View File

@@ -11,7 +11,9 @@ const ENTITY_NAME_TO_ENTITY = {
"attributes": Attribute,
"branches": Branch,
"notes": Note,
"note_contents": Note,
"note_revisions": NoteRevision,
"note_revision_contents": NoteRevision,
"recent_notes": RecentNote,
"options": Option,
"api_tokens": ApiToken,

View File

@@ -9,9 +9,16 @@ import utils from "./services/utils.js";
import treeUtils from "./services/tree_utils.js";
import linkService from "./services/link.js";
import noteContentRenderer from "./services/note_content_renderer.js";
import server from "./services/server.js";
window.glob.isDesktop = utils.isDesktop;
window.glob.isMobile = utils.isMobile;
// required for CKEditor image upload plugin
window.glob.getActiveNode = treeService.getActiveNode;
window.glob.getHeaders = server.getHeaders;
window.glob.noteChanged = noteDetailService.noteChanged;
window.glob.refreshTree = treeService.reload;
window.glob.showAddLinkDialog = () => import('./dialogs/add_link.js').then(d => d.showDialog());
window.glob.showIncludeNoteDialog = cb => import('./dialogs/include_note.js').then(d => d.showDialog(cb));
window.glob.loadIncludedNote = async (noteId, el) => {

View File

@@ -20,7 +20,9 @@ async function getRenderedContent(note) {
$rendered = $("<pre>").text(fullNote.content);
}
else if (type === 'image') {
$rendered = $("<img>").attr("src", `api/images/${note.noteId}/${note.title}`);
$rendered = $("<img>")
.attr("src", `api/images/${note.noteId}/${note.title}`)
.css("max-width", "100%");
}
else if (type === 'file') {
function getFileUrl() {

View File

@@ -60,6 +60,7 @@ class NoteDetailCode {
// CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check)
// we provide fallback
this.codeEditor.setValue(this.ctx.note.content || "");
this.codeEditor.clearHistory();
const info = CodeMirror.findModeByMIME(this.ctx.note.mime);

View File

@@ -115,4 +115,5 @@ span.fancytree-expander {
/* large left padding is necessary for ckeditor gutter in detail-only (smartphone) layout */
padding-left: 35px;
padding-top: 10px;
min-height: 150px !important;
}

View File

@@ -1,9 +1,9 @@
"use strict";
const app_info = require('../../services/app_info');
const appInfo = require('../../services/app_info');
async function getAppInfo() {
return app_info;
return appInfo;
}
module.exports = {

View File

@@ -49,7 +49,7 @@ async function loginSync(req) {
return {
sourceId: sourceIdService.getCurrentSourceId(),
maxSyncId: await sql.getValue("SELECT MAX(id) FROM sync")
maxSyncId: await sql.getValue("SELECT MAX(id) FROM sync WHERE isSynced = 1")
};
}

View File

@@ -50,7 +50,7 @@ async function getStats() {
async function checkSync() {
return {
entityHashes: await contentHashService.getEntityHashes(),
maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync')
maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync WHERE isSynced = 1')
};
}
@@ -116,11 +116,11 @@ async function forceNoteSync(req) {
async function getChanged(req) {
const lastSyncId = parseInt(req.query.lastSyncId);
const syncs = await sql.getRows("SELECT * FROM sync WHERE id > ? LIMIT 1000", [lastSyncId]);
const syncs = await sql.getRows("SELECT * FROM sync WHERE isSynced = 1 AND id > ? LIMIT 1000", [lastSyncId]);
return {
syncs: await syncService.getSyncRecords(syncs),
maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync')
maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync WHERE isSynced = 1')
};
}

View File

@@ -4,7 +4,7 @@ const build = require('./build');
const packageJson = require('../../package');
const {TRILIUM_DATA_DIR} = require('./data_dir');
const APP_DB_VERSION = 157;
const APP_DB_VERSION = 158;
const SYNC_VERSION = 14;
const CLIPPER_PROTOCOL_VERSION = "1.0";

View File

@@ -1 +1 @@
module.exports = { buildDate:"2020-03-08T21:05:52+01:00", buildRevision: "e4039ea5e1c6ac87e0947bc77b6bdcbb29a23092" };
module.exports = { buildDate:"2020-03-28T20:58:20+01:00", buildRevision: "b4b22d9353bdc6ad4d3bab7cdb33bd0b844cc36d" };

View File

@@ -51,7 +51,7 @@ class ConsistencyChecks {
childToParents[childNoteId].push(parentNoteId);
}
function checkTreeCycle(noteId, path) {
const checkTreeCycle = (noteId, path) => {
if (noteId === 'root') {
return;
}
@@ -75,7 +75,7 @@ class ConsistencyChecks {
checkTreeCycle(parentNoteId, newPath);
}
}
}
};
const noteIds = Object.keys(childToParents);
@@ -546,7 +546,7 @@ class ConsistencyChecks {
${entityName}
LEFT JOIN sync ON sync.entityName = '${entityName}' AND entityId = ${key}
WHERE
sync.id IS NULL AND ` + (entityName === 'options' ? 'isSynced = 1' : '1'),
sync.id IS NULL AND ` + (entityName === 'options' ? 'options.isSynced = 1' : '1'),
async ({entityId}) => {
if (this.autoFix) {
await syncTableService.addEntitySync(entityName, entityId);

View File

@@ -16,7 +16,7 @@ async function exportToOpml(taskContext, branch, version, res) {
const branch = await repository.getBranch(branchId);
const note = await branch.getNote();
if (!note.isStringNote() || await note.hasOwnedLabel('excludeFromExport')) {
if (await note.hasOwnedLabel('excludeFromExport')) {
return;
}
@@ -24,13 +24,13 @@ async function exportToOpml(taskContext, branch, version, res) {
if (opmlVersion === 1) {
const preparedTitle = escapeXmlAttribute(title);
const preparedContent = prepareText(await note.getContent());
const preparedContent = note.isStringNote() ? prepareText(await note.getContent()) : '';
res.write(`<outline title="${preparedTitle}" text="${preparedContent}">\n`);
}
else if (opmlVersion === 2) {
const preparedTitle = escapeXmlAttribute(title);
const preparedContent = escapeXmlAttribute(await note.getContent());
const preparedContent = note.isStringNote() ? escapeXmlAttribute(await note.getContent()) : '';
res.write(`<outline text="${preparedTitle}" _note="${preparedContent}">\n`);
}

View File

@@ -235,8 +235,6 @@ async function importEnex(taskContext, file, parentNote) {
taskContext.increaseProgressCount();
let noteContent = await noteEntity.getContent();
for (const resource of resources) {
const hash = utils.md5(resource.content);
@@ -268,7 +266,7 @@ async function importEnex(taskContext, file, parentNote) {
const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`;
noteContent = noteContent.replace(mediaRegex, resourceLink);
content = content.replace(mediaRegex, resourceLink);
};
if (["image/jpeg", "image/png", "image/gif", "image/webp"].includes(resource.mime)) {
@@ -281,12 +279,12 @@ async function importEnex(taskContext, file, parentNote) {
const imageLink = `<img src="${url}">`;
noteContent = noteContent.replace(mediaRegex, imageLink);
content = content.replace(mediaRegex, imageLink);
if (!noteContent.includes(imageLink)) {
if (!content.includes(imageLink)) {
// if there wasn't any match for the reference, we'll add the image anyway
// otherwise image would be removed since no note would include it
noteContent += imageLink;
content += imageLink;
}
} catch (e) {
log.error("error when saving image from ENEX file: " + e);
@@ -298,7 +296,7 @@ async function importEnex(taskContext, file, parentNote) {
}
// save updated content with links to files/images
await noteEntity.setContent(noteContent);
await noteEntity.setContent(content);
await noteService.scanForLinks(noteEntity.noteId);

View File

@@ -209,6 +209,8 @@ async function transactional(func) {
transactionActive = false;
resolve();
setTimeout(() => require('./ws').sendPingToAllClients(), 50);
}
catch (e) {
if (transactionActive) {

View File

@@ -176,7 +176,7 @@ async function pushSync(syncContext) {
let lastSyncedPush = await getLastSyncedPush();
while (true) {
const syncs = await sql.getRows('SELECT * FROM sync WHERE id > ? LIMIT 1000', [lastSyncedPush]);
const syncs = await sql.getRows('SELECT * FROM sync WHERE isSynced = 1 AND id > ? LIMIT 1000', [lastSyncedPush]);
if (syncs.length === 0) {
log.info("Nothing to push");
@@ -236,7 +236,7 @@ async function checkContentHash(syncContext) {
return true;
}
const notPushedSyncs = await sql.getValue("SELECT EXISTS(SELECT 1 FROM sync WHERE id > ?)", [await getLastSyncedPush()]);
const notPushedSyncs = await sql.getValue("SELECT EXISTS(SELECT 1 FROM sync WHERE isSynced = 1 AND id > ?)", [await getLastSyncedPush()]);
if (notPushedSyncs) {
log.info(`There's ${notPushedSyncs} outstanding pushes, skipping content check.`);
@@ -353,7 +353,7 @@ async function updatePushStats() {
if (await syncOptions.isSyncSetup()) {
const lastSyncedPush = await optionService.getOption('lastSyncedPush');
stats.outstandingPushes = await sql.getValue("SELECT COUNT(1) FROM sync WHERE id > ?", [lastSyncedPush]);
stats.outstandingPushes = await sql.getValue("SELECT COUNT(1) FROM sync WHERE isSynced = 1 AND id > ?", [lastSyncedPush]);
}
}

View File

@@ -11,7 +11,8 @@ async function insertEntitySync(entityName, entityId, sourceId) {
entityName: entityName,
entityId: entityId,
utcSyncDate: dateUtils.utcNowDateTime(),
sourceId: sourceId || cls.getSourceId() || sourceIdService.getCurrentSourceId()
sourceId: sourceId || cls.getSourceId() || sourceIdService.getCurrentSourceId(),
isSynced: 1
};
sync.id = await sql.replace("sync", sync);
@@ -23,8 +24,6 @@ async function addEntitySync(entityName, entityId, sourceId) {
const sync = await insertEntitySync(entityName, entityId, sourceId);
syncs.push(sync);
setTimeout(() => require('./ws').sendPingToAllClients(), 50);
}
async function addEntitySyncsForSector(entityName, entityPrimaryKey, sector) {