mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-11 18:24:43 +02:00
All: Resolves #443: Various optimisations to make dealing with large notes easier and make Katex re-rendering faster
This commit is contained in:
parent
ef2ffd4e52
commit
b9118a90be
@ -44,4 +44,13 @@ describe('ArrayUtils', function() {
|
|||||||
done();
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
@ -23,6 +23,8 @@ const fs = require('fs-extra');
|
|||||||
const {clipboard} = require('electron')
|
const {clipboard} = require('electron')
|
||||||
const md5 = require('md5');
|
const md5 = require('md5');
|
||||||
const mimeUtils = require('lib/mime-utils.js').mime;
|
const mimeUtils = require('lib/mime-utils.js').mime;
|
||||||
|
const NoteBodyViewer = require('./NoteBodyViewer.min.js');
|
||||||
|
const ArrayUtils = require('lib/ArrayUtils');
|
||||||
|
|
||||||
require('brace/mode/markdown');
|
require('brace/mode/markdown');
|
||||||
// https://ace.c9.io/build/kitchen-sink.html
|
// 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
|
// changed by the user, this variable contains that note ID. Used
|
||||||
// to automatically set the title.
|
// to automatically set the title.
|
||||||
newAndNoTitleChangeNoteId: null,
|
newAndNoTitleChangeNoteId: null,
|
||||||
|
bodyHtml: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
this.lastLoadedNoteId_ = null;
|
this.lastLoadedNoteId_ = null;
|
||||||
@ -58,6 +61,8 @@ class NoteTextComponent extends React.Component {
|
|||||||
this.ignoreNextEditorScroll_ = false;
|
this.ignoreNextEditorScroll_ = false;
|
||||||
this.scheduleSaveTimeout_ = null;
|
this.scheduleSaveTimeout_ = null;
|
||||||
this.restoreScrollTop_ = null;
|
this.restoreScrollTop_ = null;
|
||||||
|
this.lastSetHtml_ = '';
|
||||||
|
this.lastSetMarkers_ = [];
|
||||||
|
|
||||||
// Complicated but reliable method to get editor content height
|
// Complicated but reliable method to get editor content height
|
||||||
// https://github.com/ajaxorg/ace/issues/2046
|
// https://github.com/ajaxorg/ace/issues/2046
|
||||||
@ -302,7 +307,12 @@ class NoteTextComponent extends React.Component {
|
|||||||
newState.newAndNoTitleChangeNoteId = null;
|
newState.newAndNoTitleChangeNoteId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.lastSetHtml_ = '';
|
||||||
|
this.lastSetMarkers_ = [];
|
||||||
|
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
|
|
||||||
|
this.updateHtml(newState.note ? newState.note.body : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentWillReceiveProps(nextProps) {
|
async componentWillReceiveProps(nextProps) {
|
||||||
@ -542,9 +552,52 @@ class NoteTextComponent extends React.Component {
|
|||||||
|
|
||||||
aceEditor_change(body) {
|
aceEditor_change(body) {
|
||||||
shared.noteComponent_change(this, 'body', body);
|
shared.noteComponent_change(this, 'body', body);
|
||||||
|
this.scheduleHtmlUpdate();
|
||||||
this.scheduleSave();
|
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) {
|
async doCommand(command) {
|
||||||
if (!command) return;
|
if (!command) return;
|
||||||
|
|
||||||
@ -601,6 +654,8 @@ class NoteTextComponent extends React.Component {
|
|||||||
note: Object.assign({}, note),
|
note: Object.assign({}, note),
|
||||||
lastSavedNote: Object.assign({}, note),
|
lastSavedNote: Object.assign({}, note),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.updateHtml(note.body);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reg.logger().error(error);
|
reg.logger().error(error);
|
||||||
bridge().showErrorMessageBox(error.message);
|
bridge().showErrorMessageBox(error.message);
|
||||||
@ -748,25 +803,21 @@ class NoteTextComponent extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.webviewReady) {
|
if (this.state.webviewReady) {
|
||||||
const mdOptions = {
|
let html = this.state.bodyHtml;
|
||||||
onResourceLoaded: () => {
|
|
||||||
this.forceUpdate();
|
|
||||||
},
|
|
||||||
postMessageSyntax: 'ipcRenderer.sendToHost',
|
|
||||||
};
|
|
||||||
|
|
||||||
let bodyToRender = body;
|
const htmlHasChanged = this.lastSetHtml_ !== html;
|
||||||
if (!bodyToRender.trim() && visiblePanes.indexOf('viewer') >= 0 && visiblePanes.indexOf('editor') < 0) {
|
if (htmlHasChanged) {
|
||||||
// Fixes https://github.com/laurent22/joplin/issues/217
|
this.webview_.send('setHtml', html);
|
||||||
bodyToRender = '*' + _('This note has no content. Click on "%s" to toggle the editor and edit the note.', _('Layout')) + '*';
|
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 search = BaseModel.byId(this.props.searches, this.props.selectedSearchId);
|
||||||
const keywords = search ? Search.keywords(search.query_pattern) : [];
|
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 = [];
|
const toolbarItems = [];
|
||||||
@ -812,7 +863,7 @@ class NoteTextComponent extends React.Component {
|
|||||||
|
|
||||||
const titleBarDate = <span style={Object.assign({}, theme.textStyle, {color: theme.colorFaded})}>{time.formatMsToLocal(note.user_updated_time)}</span>
|
const titleBarDate = <span style={Object.assign({}, theme.textStyle, {color: theme.colorFaded})}>{time.formatMsToLocal(note.user_updated_time)}</span>
|
||||||
|
|
||||||
const viewer = <webview
|
const viewer = <webview
|
||||||
style={viewerStyle}
|
style={viewerStyle}
|
||||||
nodeintegration="1"
|
nodeintegration="1"
|
||||||
src="gui/note-viewer/index.html"
|
src="gui/note-viewer/index.html"
|
||||||
|
@ -45,4 +45,17 @@ ArrayUtils.findByKey = function(array, key, value) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArrayUtils.contentEquals = function(array1, array2) {
|
||||||
|
if (array1 === array2) return true;
|
||||||
|
if (!array1.length && !array2.length) return true;
|
||||||
|
if (array1.length !== array2.length) return false;
|
||||||
|
|
||||||
|
for (let i = 0; i < array1.length; i++) {
|
||||||
|
const a1 = array1[i];
|
||||||
|
if (array2.indexOf(a1) < 0) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = ArrayUtils;
|
module.exports = ArrayUtils;
|
@ -157,9 +157,12 @@ class MdToHtml {
|
|||||||
rendererPlugin_(language) {
|
rendererPlugin_(language) {
|
||||||
if (!language) return null;
|
if (!language) return null;
|
||||||
|
|
||||||
const handlers = {};
|
if (!this.rendererPlugins_) {
|
||||||
handlers['katex'] = new MdToHtml_Katex();
|
this.rendererPlugins_ = {};
|
||||||
return language in handlers ? handlers[language] : null;
|
this.rendererPlugins_['katex'] = new MdToHtml_Katex();
|
||||||
|
}
|
||||||
|
|
||||||
|
return language in this.rendererPlugins_ ? this.rendererPlugins_[language] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseInlineCodeLanguage_(content) {
|
parseInlineCodeLanguage_(content) {
|
||||||
|
@ -5,15 +5,28 @@ const Setting = require('lib/models/Setting');
|
|||||||
|
|
||||||
class MdToHtml_Katex {
|
class MdToHtml_Katex {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.cache_ = {};
|
||||||
|
this.assetsLoaded_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
name() {
|
name() {
|
||||||
return 'katex';
|
return 'katex';
|
||||||
}
|
}
|
||||||
|
|
||||||
processContent(renderedTokens, content, tagType) {
|
processContent(renderedTokens, content, tagType) {
|
||||||
try {
|
try {
|
||||||
let renderered = katex.renderToString(content, {
|
const cacheKey = tagType + '_' + content;
|
||||||
displayMode: tagType === 'block',
|
let renderered = null;
|
||||||
});
|
|
||||||
|
if (this.cache_[cacheKey]) {
|
||||||
|
renderered = this.cache_[cacheKey];
|
||||||
|
} else {
|
||||||
|
renderered = katex.renderToString(content, {
|
||||||
|
displayMode: tagType === 'block',
|
||||||
|
});
|
||||||
|
this.cache_[cacheKey] = renderered;
|
||||||
|
}
|
||||||
|
|
||||||
if (tagType === 'block') renderered = '<p>' + renderered + '</p>';
|
if (tagType === 'block') renderered = '<p>' + renderered + '</p>';
|
||||||
|
|
||||||
@ -29,6 +42,8 @@ class MdToHtml_Katex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadAssets() {
|
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 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
|
// 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_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' });
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user