diff --git a/CliClient/tests/ArrayUtils.js b/CliClient/tests/ArrayUtils.js
index bef63a8b8..fd2a5fbde 100644
--- a/CliClient/tests/ArrayUtils.js
+++ b/CliClient/tests/ArrayUtils.js
@@ -44,4 +44,13 @@ describe('ArrayUtils', function() {
done();
});
+ it('should compare arrays', async (done) => {
+ expect(ArrayUtils.contentEquals([], [])).toBe(true);
+ expect(ArrayUtils.contentEquals(['a'], ['a'])).toBe(true);
+ expect(ArrayUtils.contentEquals(['b', 'a'], ['a', 'b'])).toBe(true);
+ expect(ArrayUtils.contentEquals(['b'], ['a', 'b'])).toBe(false);
+
+ done();
+ });
+
});
\ No newline at end of file
diff --git a/ElectronClient/app/gui/NoteText.jsx b/ElectronClient/app/gui/NoteText.jsx
index ee0aa504c..2c1afbcb2 100644
--- a/ElectronClient/app/gui/NoteText.jsx
+++ b/ElectronClient/app/gui/NoteText.jsx
@@ -23,6 +23,8 @@ const fs = require('fs-extra');
const {clipboard} = require('electron')
const md5 = require('md5');
const mimeUtils = require('lib/mime-utils.js').mime;
+const NoteBodyViewer = require('./NoteBodyViewer.min.js');
+const ArrayUtils = require('lib/ArrayUtils');
require('brace/mode/markdown');
// https://ace.c9.io/build/kitchen-sink.html
@@ -50,6 +52,7 @@ class NoteTextComponent extends React.Component {
// changed by the user, this variable contains that note ID. Used
// to automatically set the title.
newAndNoTitleChangeNoteId: null,
+ bodyHtml: '',
};
this.lastLoadedNoteId_ = null;
@@ -58,6 +61,8 @@ class NoteTextComponent extends React.Component {
this.ignoreNextEditorScroll_ = false;
this.scheduleSaveTimeout_ = null;
this.restoreScrollTop_ = null;
+ this.lastSetHtml_ = '';
+ this.lastSetMarkers_ = [];
// Complicated but reliable method to get editor content height
// https://github.com/ajaxorg/ace/issues/2046
@@ -302,7 +307,12 @@ class NoteTextComponent extends React.Component {
newState.newAndNoTitleChangeNoteId = null;
}
+ this.lastSetHtml_ = '';
+ this.lastSetMarkers_ = [];
+
this.setState(newState);
+
+ this.updateHtml(newState.note ? newState.note.body : '');
}
async componentWillReceiveProps(nextProps) {
@@ -542,9 +552,52 @@ class NoteTextComponent extends React.Component {
aceEditor_change(body) {
shared.noteComponent_change(this, 'body', body);
+ this.scheduleHtmlUpdate();
this.scheduleSave();
}
+ scheduleHtmlUpdate(timeout = 500) {
+ if (this.scheduleHtmlUpdateIID_) {
+ clearTimeout(this.scheduleHtmlUpdateIID_);
+ this.scheduleHtmlUpdateIID_ = null;
+ }
+
+ if (timeout) {
+ this.scheduleHtmlUpdateIID_ = setTimeout(() => {
+ this.updateHtml();
+ }, timeout);
+ } else {
+ this.updateHtml();
+ }
+ }
+
+ updateHtml(body = null) {
+ const mdOptions = {
+ onResourceLoaded: () => {
+ this.updateHtml();
+ this.forceUpdate();
+ },
+ postMessageSyntax: 'ipcRenderer.sendToHost',
+ };
+
+ const theme = themeStyle(this.props.theme);
+
+ let bodyToRender = body;
+ if (bodyToRender === null) bodyToRender = this.state.note && this.state.note.body ? this.state.note.body : '';
+ let bodyHtml = '';
+
+ const visiblePanes = this.props.visiblePanes || ['editor', 'viewer'];
+
+ if (!bodyToRender.trim() && visiblePanes.indexOf('viewer') >= 0 && visiblePanes.indexOf('editor') < 0) {
+ // Fixes https://github.com/laurent22/joplin/issues/217
+ bodyToRender = '*' + _('This note has no content. Click on "%s" to toggle the editor and edit the note.', _('Layout')) + '*';
+ }
+
+ bodyHtml = this.mdToHtml().render(bodyToRender, theme, mdOptions);
+
+ this.setState({ bodyHtml: bodyHtml });
+ }
+
async doCommand(command) {
if (!command) return;
@@ -601,6 +654,8 @@ class NoteTextComponent extends React.Component {
note: Object.assign({}, note),
lastSavedNote: Object.assign({}, note),
});
+
+ this.updateHtml(note.body);
} catch (error) {
reg.logger().error(error);
bridge().showErrorMessageBox(error.message);
@@ -748,25 +803,21 @@ class NoteTextComponent extends React.Component {
}
if (this.state.webviewReady) {
- const mdOptions = {
- onResourceLoaded: () => {
- this.forceUpdate();
- },
- postMessageSyntax: 'ipcRenderer.sendToHost',
- };
+ let html = this.state.bodyHtml;
- let bodyToRender = body;
- if (!bodyToRender.trim() && visiblePanes.indexOf('viewer') >= 0 && visiblePanes.indexOf('editor') < 0) {
- // Fixes https://github.com/laurent22/joplin/issues/217
- bodyToRender = '*' + _('This note has no content. Click on "%s" to toggle the editor and edit the note.', _('Layout')) + '*';
+ const htmlHasChanged = this.lastSetHtml_ !== html;
+ if (htmlHasChanged) {
+ this.webview_.send('setHtml', html);
+ this.lastSetHtml_ = html;
}
- const html = this.mdToHtml().render(bodyToRender, theme, mdOptions);
- this.webview_.send('setHtml', html);
-
const search = BaseModel.byId(this.props.searches, this.props.selectedSearchId);
const keywords = search ? Search.keywords(search.query_pattern) : [];
- this.webview_.send('setMarkers', keywords);
+
+ if (htmlHasChanged || !ArrayUtils.contentEquals(this.lastSetMarkers_, keywords)) {
+ this.lastSetMarkers_ = [];
+ this.webview_.send('setMarkers', keywords);
+ }
}
const toolbarItems = [];
@@ -812,7 +863,7 @@ class NoteTextComponent extends React.Component {
const titleBarDate = {time.formatMsToLocal(note.user_updated_time)}
- const viewer = ';
@@ -29,6 +42,8 @@ class MdToHtml_Katex {
}
async loadAssets() {
+ if (this.assetsLoaded_) return;
+
// In node, the fonts are simply copied using copycss to where Katex expects to find them, which is under app/gui/note-viewer/fonts
// In React Native, it's more complicated and we need to download and copy them to the right directory. Ideally, we should embed
@@ -43,6 +58,8 @@ class MdToHtml_Katex {
await shim.fetchBlob('https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/fonts/KaTeX_Math-Italic.woff2', { overwrite: false, path: baseDir + '/fonts/KaTeX_Math-Italic.woff2' });
await shim.fetchBlob('https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/fonts/KaTeX_Size1-Regular.woff2', { overwrite: false, path: baseDir + '/fonts/KaTeX_Size1-Regular.woff2' });
}
+
+ this.assetsLoaded_ = true;
}
}