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

Electron: Added Bold and Italic toolbar buttons

This commit is contained in:
Laurent Cozic 2018-06-11 23:47:44 +01:00
parent a68df18cd5
commit 218b446915
3 changed files with 182 additions and 39 deletions

View File

@ -381,22 +381,42 @@ class Application extends BaseApplication {
label: _('Edit'),
submenu: [{
label: _('Copy'),
//screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
role: 'copy',
accelerator: 'CommandOrControl+C',
}, {
label: _('Cut'),
//screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
role: 'cut',
accelerator: 'CommandOrControl+X',
}, {
label: _('Paste'),
//screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'],
role: 'paste',
accelerator: 'CommandOrControl+V',
}, {
type: 'separator',
screens: ['Main'],
}, {
label: _('Bold'),
screens: ['Main'],
accelerator: 'CommandOrControl+B',
click: () => {
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'textBold',
});
},
}, {
label: _('Italic'),
screens: ['Main'],
accelerator: 'CommandOrControl+I',
click: () => {
this.dispatch({
type: 'WINDOW_COMMAND',
name: 'textItalic',
});
},
}, {
type: 'separator',
screens: ['Main'],
}, {
label: _('Search in all the notes'),
screens: ['Main'],

View File

@ -115,13 +115,41 @@ class NoteTextComponent extends React.Component {
await this.commandAttachFile(filesToAttach);
}
this.aceEditor_selectionChange = (selection) => {
const ranges = selection.getAllRanges();
if (!ranges || !ranges.length || !this.state.note) {
this.setState({
selection: null,
selectionRange: null,
});
return;
}
const range = ranges[0];
let newSelection = {
start: this.cursorPositionToTextOffsets(range.start, this.state.note.body),
end: this.cursorPositionToTextOffsets(range.end, this.state.note.body),
};
if (newSelection.start === newSelection.end) newSelection = null;
this.setState({
selection: newSelection,
selectionRange: range,
});
}
}
cursorPosition() {
return this.cursorPositionToTextOffsets(this.editor_.editor.getCursorPosition(), this.state.note.body);
}
cursorPositionToTextOffsets(cursorPos, body) {
if (!this.editor_ || !this.editor_.editor || !this.state.note || !this.state.note.body) return 0;
const cursorPos = this.editor_.editor.getCursorPosition();
const noteLines = this.state.note.body.split('\n');
const noteLines = body.split('\n');
let pos = 0;
for (let i = 0; i < noteLines.length; i++) {
@ -135,7 +163,7 @@ class NoteTextComponent extends React.Component {
}
}
return pos;
return pos;
}
mdToHtml() {
@ -635,6 +663,10 @@ class NoteTextComponent extends React.Component {
}
} else if (command.name === 'print' && this.webview_) {
this.webview_.print();
} else if (command.name === 'textBold') {
this.commandTextBold();
} else if (command.name === 'textItalic') {
this.commandTextItalic();
} else {
commandProcessed = false;
}
@ -699,6 +731,71 @@ class NoteTextComponent extends React.Component {
});
}
// Returns the actual Ace Editor instance (not the React wrapper)
rawEditor() {
return this.editor_ && this.editor_.editor ? this.editor_.editor : null;
}
updateEditorWithDelay(fn) {
setTimeout(() => {
if (!this.rawEditor()) return;
fn(this.rawEditor());
}, 10);
}
wrapSelectionWithStrings(string1, string2) {
if (!this.rawEditor() || !this.state.note) return;
const selection = this.state.selection;
let newBody = this.state.note.body;
if (selection) {
const s1 = this.state.note.body.substr(0, selection.start);
const s2 = this.state.note.body.substr(selection.start, selection.end - selection.start);
const s3 = this.state.note.body.substr(selection.end);
newBody = s1 + string1 + s2 + string2 + s3;
const r = this.state.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;
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);
newBody = s1 + string1 + string2 + s2;
this.updateEditorWithDelay((editor) => {
for (let i = 0; i < string1.length; i++) {
editor.getSession().getSelection().moveCursorRight();
}
editor.focus();
}, 10);
}
shared.noteComponent_change(this, 'body', newBody);
this.scheduleHtmlUpdate();
}
async commandTextBold() {
this.wrapSelectionWithStrings('**', '**');
}
async commandTextItalic() {
this.wrapSelectionWithStrings('*', '*');
}
itemContextMenu(event) {
const note = this.state.note;
if (!note) return;
@ -722,6 +819,57 @@ class NoteTextComponent extends React.Component {
menu.popup(bridge().window());
}
createToolbarItems(note) {
const toolbarItems = [];
if (note && this.state.folder && ['Search', 'Tag'].includes(this.props.notesParentType)) {
toolbarItems.push({
title: _('In: %s', this.state.folder.title),
iconName: 'fa-folder-o',
enabled: false,
});
}
toolbarItems.push({
tooltip: _('Bold'),
iconName: 'fa-bold',
onClick: () => { return this.commandTextBold(); },
});
toolbarItems.push({
tooltip: _('Italic'),
iconName: 'fa-italic',
onClick: () => { return this.commandTextItalic(); },
});
toolbarItems.push({
tooltip: _('Attach file'),
iconName: 'fa-paperclip',
onClick: () => { return this.commandAttachFile(); },
});
toolbarItems.push({
tooltip: _('Tags'),
iconName: 'fa-tags',
onClick: () => { return this.commandSetTags(); },
});
if (note.is_todo) {
const item = {
iconName: 'fa-clock-o',
enabled: !note.todo_completed,
onClick: () => { return this.commandSetAlarm(); },
}
if (Note.needAlarm(note)) {
item.title = time.formatMsToLocal(note.todo_due);
} else {
item.tooltip = _('Set alarm');
}
toolbarItems.push(item);
}
return toolbarItems;
}
render() {
const style = this.props.style;
const note = this.state.note;
@ -836,37 +984,7 @@ class NoteTextComponent extends React.Component {
}
}
const toolbarItems = [];
if (note
&& this.state.folder !== null
&& ['Search', 'Tag'].includes(this.props.notesParentType)) {
toolbarItems.push({
title: _('In: %s', this.state.folder.title),
iconName: 'fa-folder-o',
enabled: false,
});
}
toolbarItems.push({
title: _('Attach file'),
iconName: 'fa-paperclip',
onClick: () => { return this.commandAttachFile(); },
});
toolbarItems.push({
title: _('Tags'),
iconName: 'fa-tags',
onClick: () => { return this.commandSetTags(); },
});
if (note.is_todo) {
toolbarItems.push({
title: Note.needAlarm(note) ? time.formatMsToLocal(note.todo_due) : _('Set alarm'),
iconName: 'fa-clock-o',
enabled: !note.todo_completed,
onClick: () => { return this.commandSetAlarm(); },
});
}
const toolbarItems = this.createToolbarItems(note);
const toolbar = <Toolbar
style={toolbarStyle}
@ -932,6 +1050,7 @@ class NoteTextComponent extends React.Component {
ref={(elem) => { this.editor_ref(elem); } }
onChange={(body) => { this.aceEditor_change(body) }}
showPrintMargin={false}
onSelectionChange={this.aceEditor_selectionChange}
// Disable warning: "Automatically scrolling cursor into view after
// selection change this will be disabled in the next version set

View File

@ -24,13 +24,16 @@ class ToolbarButton extends React.Component {
justifyContent: 'center',
};
const title = this.props.title ? this.props.title : '';
const tooltip = this.props.tooltip ? this.props.tooltip : title;
let icon = null;
if (this.props.iconName) {
const iconStyle = {
fontSize: Math.round(theme.fontSize * 1.4),
color: theme.color
};
if (this.props.title) iconStyle.marginRight = 5;
if (title) iconStyle.marginRight = 5;
icon = <i style={iconStyle} className={"fa " + this.props.iconName}></i>
}
@ -46,10 +49,11 @@ class ToolbarButton extends React.Component {
<a
className={classes.join(' ')}
style={finalStyle}
title={tooltip}
href="#"
onClick={() => { if (isEnabled && this.props.onClick) this.props.onClick() }}
>
{icon}{this.props.title ? this.props.title : ''}
{icon}{title}
</a>
);
}