Skip to content

Commit

Permalink
Electron: Fixes #623: Improved handling of text selection and fixed i…
Browse files Browse the repository at this point in the history
…nfinite loop
  • Loading branch information
laurent22 committed Jun 17, 2018
1 parent 553b086 commit cf4331c
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 31 deletions.
84 changes: 56 additions & 28 deletions ElectronClient/app/gui/NoteText.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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];
}
}

Expand All @@ -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');
Expand All @@ -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({
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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;

Expand All @@ -805,35 +830,37 @@ 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},
end: { row: r.end.row, column: r.end.column + string1.length},
};

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);
Expand All @@ -848,6 +875,7 @@ class NoteTextComponent extends React.Component {

shared.noteComponent_change(this, 'body', newBody);
this.scheduleHtmlUpdate();
this.scheduleSave();
}

commandTextBold() {
Expand All @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion ReactNativeClient/lib/SyncTargetWebDAV.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion docs/donate/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ <h2 id="paypal">Paypal</h2>
<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>
Expand Down
2 changes: 1 addition & 1 deletion readme/donate.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit cf4331c

Please sign in to comment.