mirror of
https://github.com/laurent22/joplin.git
synced 2025-04-11 11:12:03 +02:00
Show editor and rendering side by side
This commit is contained in:
parent
9e3af21e66
commit
8bf85b6b88
@ -5,6 +5,7 @@ const { SideBar } = require('./SideBar.min.js');
|
||||
const { NoteList } = require('./NoteList.min.js');
|
||||
const { NoteText } = require('./NoteText.min.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const layoutUtils = require('lib/layout-utils.js');
|
||||
|
||||
class MainScreenComponent extends React.Component {
|
||||
|
||||
@ -18,22 +19,22 @@ class MainScreenComponent extends React.Component {
|
||||
|
||||
const rowHeight = style.height - theme.headerHeight;
|
||||
|
||||
const sideBarStyle = {
|
||||
width: layoutUtils.size(style.width * .2, 100, 300),
|
||||
height: rowHeight,
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
};
|
||||
|
||||
const noteListStyle = {
|
||||
width: Math.floor(style.width / 3),
|
||||
width: layoutUtils.size(style.width * .2, 100, 300),
|
||||
height: rowHeight,
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
};
|
||||
|
||||
const noteTextStyle = {
|
||||
width: noteListStyle.width,
|
||||
height: rowHeight,
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
};
|
||||
|
||||
const sideBarStyle = {
|
||||
width: style.width - (noteTextStyle.width + noteListStyle.width),
|
||||
width: layoutUtils.size(style.width - sideBarStyle.width - noteListStyle.width, 0),
|
||||
height: rowHeight,
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'top',
|
||||
|
@ -1,8 +1,11 @@
|
||||
const React = require('react');
|
||||
const { Note } = require('lib/models/note.js');
|
||||
const { connect } = require('react-redux');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const MdToHtml = require('lib/MdToHtml');
|
||||
const shared = require('lib/components/shared/note-screen-shared.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
|
||||
class NoteTextComponent extends React.Component {
|
||||
|
||||
@ -18,34 +21,45 @@ class NoteTextComponent extends React.Component {
|
||||
lastSavedNote: null,
|
||||
isLoading: true,
|
||||
webviewReady: false,
|
||||
scrollHeight: null,
|
||||
};
|
||||
|
||||
this.lastLoadedNoteId_ = null;
|
||||
|
||||
this.webviewListeners_ = null;
|
||||
this.ignoreNextEditorScroll_ = false;
|
||||
}
|
||||
|
||||
mdToHtml() {
|
||||
if (this.mdToHtml_) return this.mdToHtml_;
|
||||
this.mdToHtml_ = new MdToHtml();
|
||||
return this.mdToHtml_;
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
this.mdToHtml_ = new MdToHtml();
|
||||
|
||||
|
||||
await shared.initState(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.webview_.addEventListener('dom-ready', this.webview_domReady.bind(this));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mdToHtml_ = null;
|
||||
this.webview_.addEventListener('dom-ready', this.webview_domReady.bind(this));
|
||||
this.destroyWebview();
|
||||
}
|
||||
|
||||
async componentWillReceiveProps(nextProps) {
|
||||
if ('noteId' in nextProps) {
|
||||
this.mdToHtml_ = null;
|
||||
|
||||
const noteId = nextProps.noteId;
|
||||
this.lastLoadedNoteId_ = noteId;
|
||||
const note = noteId ? await Note.load(noteId) : null;
|
||||
if (noteId !== this.lastLoadedNoteId_) return; // Race condition - current note was changed while this one was loading
|
||||
|
||||
this.setState({ note: note });
|
||||
this.setState({
|
||||
note: note,
|
||||
mode: 'view',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,6 +77,7 @@ class NoteTextComponent extends React.Component {
|
||||
|
||||
body_changeText(text) {
|
||||
shared.noteComponent_change(this, 'body', text);
|
||||
//this.updateScrollHeight();
|
||||
}
|
||||
|
||||
async saveNoteButton_press() {
|
||||
@ -81,25 +96,137 @@ class NoteTextComponent extends React.Component {
|
||||
shared.showMetadata_onPress(this);
|
||||
}
|
||||
|
||||
webview_ipcMessage(event) {
|
||||
const msg = event.channel ? event.channel : '';
|
||||
const args = event.args;
|
||||
const arg0 = args && args.length >= 1 ? args[0] : null;
|
||||
const arg1 = args && args.length >= 2 ? args[1] : null;
|
||||
|
||||
reg.logger().info('Got ipc-message: ' + msg, args);
|
||||
|
||||
if (msg.indexOf('checkboxclick:') === 0) {
|
||||
const newBody = this.mdToHtml_.handleCheckboxClick(msg, this.state.note.body);
|
||||
this.saveOneProperty('body', newBody);
|
||||
} else if (msg.toLowerCase().indexOf('http') === 0) {
|
||||
require('electron').shell.openExternal(msg);
|
||||
} else if (msg === 'editNote') {
|
||||
const lineIndex = arg0 && arg0.length ? arg0[0] : 0;
|
||||
this.webview_ref(null);
|
||||
this.setState({
|
||||
mode: 'edit',
|
||||
webviewReady: false,
|
||||
});
|
||||
} else if (msg === 'percentScroll') {
|
||||
this.ignoreNextEditorScroll_ = true;
|
||||
this.setEditorPercentScroll(arg0);
|
||||
} else {
|
||||
bridge().showMessageBox({
|
||||
type: 'error',
|
||||
message: _('Unsupported link or message: %s', msg),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
editorMaxScroll() {
|
||||
return Math.max(0, this.editor_.scrollHeight - this.editor_.clientHeight);
|
||||
}
|
||||
|
||||
setEditorPercentScroll(p) {
|
||||
this.editor_.scrollTop = p * this.editorMaxScroll();
|
||||
}
|
||||
|
||||
setViewerPercentScroll(p) {
|
||||
this.webview_.send('setPercentScroll', p);
|
||||
}
|
||||
|
||||
editor_scroll() {
|
||||
if (this.ignoreNextEditorScroll_) {
|
||||
this.ignoreNextEditorScroll_ = false;
|
||||
return;
|
||||
}
|
||||
const m = this.editorMaxScroll();
|
||||
this.setViewerPercentScroll(m ? this.editor_.scrollTop / m : 0);
|
||||
}
|
||||
|
||||
webview_domReady() {
|
||||
if (!this.webview_) return;
|
||||
|
||||
this.setState({
|
||||
webviewReady: true,
|
||||
});
|
||||
|
||||
this.webview_.openDevTools();
|
||||
}
|
||||
|
||||
this.webview_.addEventListener('ipc-message', (event) => {
|
||||
const msg = event.channel;
|
||||
webview_ref(element) {
|
||||
if (this.webview_) {
|
||||
if (this.webview_ === element) return;
|
||||
this.destroyWebview();
|
||||
}
|
||||
|
||||
if (msg.indexOf('checkboxclick:') === 0) {
|
||||
const newBody = this.mdToHtml_.handleCheckboxClick(msg, this.state.note.body);
|
||||
this.saveOneProperty('body', newBody);
|
||||
}
|
||||
})
|
||||
if (!element) {
|
||||
this.destroyWebview();
|
||||
} else {
|
||||
this.initWebview(element);
|
||||
}
|
||||
}
|
||||
|
||||
editor_ref(element) {
|
||||
if (this.editor_ === element) return;
|
||||
this.editor_ = element;
|
||||
}
|
||||
|
||||
initWebview(wv) {
|
||||
if (!this.webviewListeners_) {
|
||||
this.webviewListeners_ = {
|
||||
'dom-ready': this.webview_domReady.bind(this),
|
||||
'ipc-message': this.webview_ipcMessage.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
for (let n in this.webviewListeners_) {
|
||||
if (!this.webviewListeners_.hasOwnProperty(n)) continue;
|
||||
const fn = this.webviewListeners_[n];
|
||||
wv.addEventListener(n, fn);
|
||||
}
|
||||
|
||||
this.webview_ = wv;
|
||||
}
|
||||
|
||||
destroyWebview() {
|
||||
if (!this.webview_) return;
|
||||
|
||||
for (let n in this.webviewListeners_) {
|
||||
if (!this.webviewListeners_.hasOwnProperty(n)) continue;
|
||||
const fn = this.webviewListeners_[n];
|
||||
this.webview_.removeEventListener(n, fn);
|
||||
}
|
||||
|
||||
this.webview_ = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const note = this.state.note;
|
||||
const body = note ? note.body : '';
|
||||
|
||||
console.info(this.state.scrollHeight);
|
||||
|
||||
const viewerStyle = {
|
||||
width: Math.floor(style.width / 2),
|
||||
height: style.height,
|
||||
overflow: 'hidden',
|
||||
float: 'left',
|
||||
verticalAlign: 'top',
|
||||
};
|
||||
|
||||
const editorStyle = {
|
||||
width: style.width - viewerStyle.width,
|
||||
height: style.height,
|
||||
overflowY: 'scroll',
|
||||
float: 'left',
|
||||
verticalAlign: 'top',
|
||||
};
|
||||
|
||||
if (this.state.webviewReady) {
|
||||
const mdOptions = {
|
||||
@ -108,21 +235,17 @@ class NoteTextComponent extends React.Component {
|
||||
},
|
||||
postMessageSyntax: 'ipcRenderer.sendToHost',
|
||||
};
|
||||
|
||||
const html = this.mdToHtml_.render(note ? note.body : '', {}, mdOptions);
|
||||
|
||||
const html = this.mdToHtml().render(body, {}, mdOptions);
|
||||
this.webview_.send('setHtml', html);
|
||||
}
|
||||
|
||||
const webviewStyle = {
|
||||
width: this.props.style.width,
|
||||
height: this.props.style.height,
|
||||
overflow: 'hidden',
|
||||
};
|
||||
const viewer = <webview style={viewerStyle} nodeintegration="1" src="note-content.html" ref={(elem) => { this.webview_ref(elem); } } />
|
||||
const editor = <textarea style={editorStyle} value={body} onScroll={() => { this.editor_scroll(); }} onChange={(text) => { this.body_changeText(text) }} ref={(elem) => { this.editor_ref(elem); } }></textarea>
|
||||
|
||||
return (
|
||||
<div style={this.props.style}>
|
||||
<webview style={webviewStyle} nodeintegration="1" src="note-content.html" ref={elem => this.webview_ = elem} />
|
||||
<div style={style}>
|
||||
{ editor }
|
||||
{ viewer }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -132,7 +255,6 @@ class NoteTextComponent extends React.Component {
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
noteId: state.selectedNoteId,
|
||||
//notes: state.notes,
|
||||
folderId: state.selectedFolderId,
|
||||
itemType: state.selectedItemType,
|
||||
folders: state.folders,
|
||||
|
@ -1,8 +1,60 @@
|
||||
<style>
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#content {
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="content"></div>
|
||||
|
||||
<script>
|
||||
const {ipcRenderer} = require('electron')
|
||||
const { ipcRenderer } = require('electron');
|
||||
const contentElement = document.getElementById('content');
|
||||
|
||||
ipcRenderer.on('setHtml', (event, html) => {
|
||||
document.getElementById('content').innerHTML = html;
|
||||
contentElement.innerHTML = html;
|
||||
});
|
||||
|
||||
let ignoreNextScroll = false;
|
||||
ipcRenderer.on('setPercentScroll', (event, percent) => {
|
||||
ignoreNextScroll = true;
|
||||
contentElement.scrollTop = percent * maxScrollTop();
|
||||
});
|
||||
|
||||
function elementMapCoordinates(element) {
|
||||
while (true) {
|
||||
if (!element) break;
|
||||
|
||||
const m = element.getAttribute('data-map');
|
||||
if (m) return m.split(':');
|
||||
element = element.parentElement;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function maxScrollTop() {
|
||||
return Math.max(0, contentElement.scrollHeight - contentElement.clientHeight);
|
||||
}
|
||||
|
||||
contentElement.addEventListener('scroll', function(e) {
|
||||
if (ignoreNextScroll) {
|
||||
ignoreNextScroll = false;
|
||||
return;
|
||||
}
|
||||
const m = maxScrollTop();
|
||||
ipcRenderer.sendToHost('percentScroll', m ? contentElement.scrollTop / m : 0);
|
||||
});
|
||||
|
||||
// document.addEventListener('click', function(e) {
|
||||
// e = e || window.event;
|
||||
// const target = e.target || e.srcElement;
|
||||
|
||||
// const coords = elementMapCoordinates(target);
|
||||
// ipcRenderer.sendToHost('editNote', coords);
|
||||
// }, false);
|
||||
</script>
|
23
ElectronClient/app/package-lock.json
generated
23
ElectronClient/app/package-lock.json
generated
@ -883,6 +883,11 @@
|
||||
"supports-color": "4.5.0"
|
||||
}
|
||||
},
|
||||
"charenc": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
|
||||
@ -1078,6 +1083,11 @@
|
||||
"which": "1.3.0"
|
||||
}
|
||||
},
|
||||
"crypt": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
|
||||
},
|
||||
"cryptiles": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
|
||||
@ -2147,8 +2157,7 @@
|
||||
"is-buffer": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
|
||||
},
|
||||
"is-builtin-module": {
|
||||
"version": "1.0.0",
|
||||
@ -2637,6 +2646,16 @@
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz",
|
||||
"integrity": "sha1-ssbGGPzOzk74bE/Gy4p8v1rtqNc="
|
||||
},
|
||||
"md5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
|
||||
"integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=",
|
||||
"requires": {
|
||||
"charenc": "0.0.2",
|
||||
"crypt": "0.0.2",
|
||||
"is-buffer": "1.1.6"
|
||||
}
|
||||
},
|
||||
"mdurl": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
|
||||
|
@ -31,6 +31,7 @@
|
||||
"lodash": "^4.17.4",
|
||||
"markdown-it": "^8.4.0",
|
||||
"marked": "^0.3.6",
|
||||
"md5": "^2.2.1",
|
||||
"moment": "^2.19.1",
|
||||
"node-fetch": "^1.7.3",
|
||||
"promise": "^8.0.1",
|
||||
|
@ -1,6 +1,7 @@
|
||||
body {
|
||||
body, textarea {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#react-root {
|
||||
|
@ -122,7 +122,6 @@ class MdToHtml {
|
||||
return '[Resource not yet supported: ' + htmlentities(text) + ']';
|
||||
} else {
|
||||
const js = options.postMessageSyntax + "(" + JSON.stringify(href) + "); return false;";
|
||||
//let output = "<a title='" + htmlentities(title) + "' href='#' onclick='" + js + "'>" + htmlentities(text) + '</a>';
|
||||
let output = "<a title='" + htmlentities(title) + "' href='#' onclick='" + js + "'>";
|
||||
return output;
|
||||
}
|
||||
|
9
ReactNativeClient/lib/layout-utils.js
Normal file
9
ReactNativeClient/lib/layout-utils.js
Normal file
@ -0,0 +1,9 @@
|
||||
const layoutUtils = {};
|
||||
|
||||
layoutUtils.size = function(prefered, min, max) {
|
||||
if (prefered < min) return min;
|
||||
if (typeof max !== 'undefined' && prefered > max) return max;
|
||||
return prefered;
|
||||
}
|
||||
|
||||
module.exports = layoutUtils;
|
Loading…
x
Reference in New Issue
Block a user