1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Electron: Fixes #623: Improved handling of text selection and fixed infinite loop

This commit is contained in:
Laurent Cozic 2018-06-17 02:44:37 +01:00
parent 553b086ba2
commit cf4331c5af
4 changed files with 59 additions and 31 deletions

View File

@ -66,6 +66,7 @@ class NoteTextComponent extends React.Component {
this.restoreScrollTop_ = null;
this.lastSetHtml_ = '';
this.lastSetMarkers_ = [];
this.selectionRange_ = null;
// Complicated but reliable method to get editor content height
// https://github.com/ajaxorg/ace/issues/2046
@ -127,11 +128,12 @@ class NoteTextComponent extends React.Component {
}
const updateSelectionRange = () => {
const ranges = this.rawEditor().getSelection().getAllRanges();
if (!ranges || !ranges.length || !this.state.note) {
this.setState({ selectionRange: null });
this.selectionRange_ = null;
} else {
this.setState({ selectionRange: ranges[0] });
this.selectionRange_ = ranges[0];
}
}
@ -144,18 +146,23 @@ class NoteTextComponent extends React.Component {
}
}
// Note:
// - What's called "cursor position" is expressed as { row: x, column: y } and is how Ace Editor get/set the cursor position
// - A "range" defines a selection with a start and end cusor position, expressed as { start: <CursorPos>, end: <CursorPos> }
// - A "text offset" below is the absolute position of the cursor in the string, as would be used in the indexOf() function.
// The functions below are used to convert between the different types.
rangeToTextOffsets(range, body) {
return {
start: this.cursorPositionToTextOffsets(range.start, body),
end: this.cursorPositionToTextOffsets(range.end, body),
start: this.cursorPositionToTextOffset(range.start, body),
end: this.cursorPositionToTextOffset(range.end, body),
};
}
cursorPosition() {
return this.cursorPositionToTextOffsets(this.editor_.editor.getCursorPosition(), this.state.note.body);
currentTextOffset() {
return this.cursorPositionToTextOffset(this.editor_.editor.getCursorPosition(), this.state.note.body);
}
cursorPositionToTextOffsets(cursorPos, body) {
cursorPositionToTextOffset(cursorPos, body) {
if (!this.editor_ || !this.editor_.editor || !this.state.note || !this.state.note.body) return 0;
const noteLines = body.split('\n');
@ -175,6 +182,24 @@ class NoteTextComponent extends React.Component {
return pos;
}
textOffsetToCursorPosition(offset, body) {
const lines = body.split('\n');
let row = 0;
let currentOffset = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (currentOffset + line.length >= offset) {
return {
row: row,
column: offset - currentOffset,
}
}
row++;
currentOffset += line.length + 1;
}
}
mdToHtml() {
if (this.mdToHtml_) return this.mdToHtml_;
this.mdToHtml_ = new MdToHtml({
@ -719,7 +744,7 @@ class NoteTextComponent extends React.Component {
await this.saveIfNeeded(true);
let note = await Note.load(this.state.note.id);
const position = this.cursorPosition();
const position = this.currentTextOffset();
for (let i = 0; i < filePaths.length; i++) {
const filePath = filePaths[i];
@ -781,21 +806,21 @@ class NoteTextComponent extends React.Component {
}
selectionRangePreviousLine() {
if (!this.state.selectionRange) return '';
const row = this.state.selectionRange.start.row;
if (!this.selectionRange_) return '';
const row = this.selectionRange_.start.row;
return this.lineAtRow(row - 1);
}
selectionRangeCurrentLine() {
if (!this.state.selectionRange) return '';
const row = this.state.selectionRange.start.row;
if (!this.selectionRange_) return '';
const row = this.selectionRange_.start.row;
return this.lineAtRow(row);
}
wrapSelectionWithStrings(string1, string2 = '', defaultText = '') {
if (!this.rawEditor() || !this.state.note) return;
const selection = this.state.selectionRange ? this.rangeToTextOffsets(this.state.selectionRange, this.state.note.body) : null;
const selection = this.selectionRange_ ? this.rangeToTextOffsets(this.selectionRange_, this.state.note.body) : null;
let newBody = this.state.note.body;
@ -805,7 +830,7 @@ class NoteTextComponent extends React.Component {
const s3 = this.state.note.body.substr(selection.end);
newBody = s1 + string1 + s2 + string2 + s3;
const r = this.state.selectionRange;
const r = this.selectionRange_;
const newRange = {
start: { row: r.start.row, column: r.start.column + string1.length},
@ -813,27 +838,29 @@ class NoteTextComponent extends React.Component {
};
this.updateEditorWithDelay((editor) => {
const range = this.state.selectionRange;
const range = this.selectionRange_;
range.setStart(newRange.start.row, newRange.start.column);
range.setEnd(newRange.end.row, newRange.end.column);
editor.getSession().getSelection().setSelectionRange(range, false);
editor.focus();
});
} else {
const cursorPos = this.cursorPosition();
const s1 = this.state.note.body.substr(0, cursorPos);
const s2 = this.state.note.body.substr(cursorPos);
const textOffset = this.currentTextOffset();
const s1 = this.state.note.body.substr(0, textOffset);
const s2 = this.state.note.body.substr(textOffset);
newBody = s1 + string1 + defaultText + string2 + s2;
const r = this.state.selectionRange;
const newRange = !r ? null : {
start: { row: r.start.row, column: r.start.column + string1.length },
end: { row: r.end.row, column: r.start.column + string1.length + defaultText.length },
const p = this.textOffsetToCursorPosition(textOffset + string1.length, newBody);
const newRange = {
start: { row: p.row, column: p.column },
end: { row: p.row, column: p.column + defaultText.length },
};
console.info('DDDDD', defaultText, newRange);
this.updateEditorWithDelay((editor) => {
if (defaultText && newRange) {
const range = this.state.selectionRange;
const range = this.selectionRange_;
range.setStart(newRange.start.row, newRange.start.column);
range.setEnd(newRange.end.row, newRange.end.column);
editor.getSession().getSelection().setSelectionRange(range, false);
@ -848,6 +875,7 @@ class NoteTextComponent extends React.Component {
shared.noteComponent_change(this, 'body', newBody);
this.scheduleHtmlUpdate();
this.scheduleSave();
}
commandTextBold() {
@ -862,26 +890,26 @@ class NoteTextComponent extends React.Component {
this.wrapSelectionWithStrings('`', '`');
}
addListItem(item) {
addListItem(string1, string2 = '', defaultText = '') {
const currentLine = this.selectionRangeCurrentLine();
let newLine = '\n'
if (!currentLine) newLine = '';
this.wrapSelectionWithStrings(newLine + item);
this.wrapSelectionWithStrings(newLine + string1, string2, defaultText);
}
commandTextCheckbox() {
this.addListItem('- [ ] ');
this.addListItem('- [ ] ', '', _('List item'));
}
commandTextListUl() {
this.addListItem('- ');
this.addListItem('- ', '', _('List item'));
}
commandTextListOl() {
let bulletNumber = markdownUtils.olLineNumber(this.selectionRangeCurrentLine());
if (!bulletNumber) bulletNumber = markdownUtils.olLineNumber(this.selectionRangePreviousLine());
if (!bulletNumber) bulletNumber = 0;
this.addListItem((bulletNumber + 1) + '. ');
this.addListItem((bulletNumber + 1) + '. ', '', _('List item'));
}
commandTextHeading() {

View File

@ -53,7 +53,7 @@ class SyncTargetWebDAV extends BaseSyncTarget {
try {
const result = await fileApi.stat('');
if (!result) throw new Error('WebDAV directory not found: ' + options.path);
if (!result) throw new Error('WebDAV directory not found: ' + options.path());
output.ok = true;
} catch (error) {
output.errorMessage = error.message;

View File

@ -256,7 +256,7 @@
<p><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=E8JMYD2LQ8MMA&lc=GB&item_name=Joplin+Development&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"><img src="https://joplin.cozic.net/images/PayPalDonate.png" height="60px"/></a></p>
<h2 id="patreon">Patreon</h2>
<p>Or you may support the project on Patreon:</p>
<p><a href="https://www.patreon.com/joplin"><img src="https://joplin.cozic.net/images/Patreon.png"/></a></p>
<p><a href="https://www.patreon.com/joplin"><img src="https://joplin.cozic.net/images/badges/Patreon.png"/></a></p>
<h2 id="bitcoin">Bitcoin</h2>
<p>Bitcoins are also accepted:</p>
<p><a href="bitcoin:1AnbeRd5NZT1ssG93jXzaDoHwzgjQAHX3R?message=Joplin%20development">1AnbeRd5NZT1ssG93jXzaDoHwzgjQAHX3R</a></p>

View File

@ -14,7 +14,7 @@ To donate via Paypal, please follow this link:
Or you may support the project on Patreon:
<a href="https://www.patreon.com/joplin"><img src="https://joplin.cozic.net/images/Patreon.png"/></a>
<a href="https://www.patreon.com/joplin"><img src="https://joplin.cozic.net/images/badges/Patreon.png"/></a>
## Bitcoin