mirror of https://github.com/laurent22/joplin.git
Desktop: Fixed various bugs and regressions following note editor refactoring
Squashed commit of the following: commit 5fde36f5c3fa7c7efbce6d81f48fe841c823e88c Author: Laurent Cozic <laurent@cozic.net> Date: Sun May 3 18:43:20 2020 +0100 Cannot fix for now commit 251284db3c8b7da6db83f3e06fd19bfdc8b8dd3f Author: Laurent Cozic <laurent@cozic.net> Date: Sun May 3 18:31:08 2020 +0100 Fixed print to multiple PDFs logic commit 00d9557996fb984b400fe650594150ae2681e03f Author: Laurent Cozic <laurent@cozic.net> Date: Sun May 3 17:49:20 2020 +0100 Fixed local search in TinyMCE commit 46778bf0a79f3bba9ddbc27389fadce4f8944507 Author: Laurent Cozic <laurent@cozic.net> Date: Sun May 3 17:37:31 2020 +0100 Restored note toolbar buttons commit 3e623c98f0a1cf08bf7d0136f0c8982c5e1ddcd8 Author: Laurent Cozic <laurent@cozic.net> Date: Sun May 3 12:30:57 2020 +0100 Various fixes and moved note toolbar to left of title commit 790262fe9df5b08d4a619e5244d2906047b39855 Author: Laurent Cozic <laurent@cozic.net> Date: Sun May 3 11:21:11 2020 +0100 Clean up commit cea30f42e69014ecabda6fa5083199a1ba7b7510 Author: Laurent Cozic <laurent@cozic.net> Date: Sun May 3 11:08:23 2020 +0100 Fixed note loading in TinyMCEpull/3165/head
parent
f51873d877
commit
51732a5adb
|
@ -3,7 +3,6 @@ const { bridge } = require('electron').remote.require('./bridge');
|
||||||
const InteropService = require('lib/services/InteropService');
|
const InteropService = require('lib/services/InteropService');
|
||||||
const Setting = require('lib/models/Setting');
|
const Setting = require('lib/models/Setting');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const { friendlySafeFilename } = require('lib/path-utils');
|
const { friendlySafeFilename } = require('lib/path-utils');
|
||||||
const md5 = require('md5');
|
const md5 = require('md5');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
|
@ -70,7 +69,10 @@ class InteropServiceHelper {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: it is crashing at this point
|
// TODO: it is crashing at this point :(
|
||||||
|
// Appears to be a Chromium bug: https://github.com/electron/electron/issues/19946
|
||||||
|
// Maybe can be fixed by doing everything from main process?
|
||||||
|
// i.e. creating a function `print()` that takes the `htmlFile` variable as input.
|
||||||
|
|
||||||
win.webContents.print(options, (success, reason) => {
|
win.webContents.print(options, (success, reason) => {
|
||||||
// TODO: This is correct but broken in Electron 4. Need to upgrade to 5+
|
// TODO: This is correct but broken in Electron 4. Need to upgrade to 5+
|
||||||
|
@ -105,32 +107,13 @@ class InteropServiceHelper {
|
||||||
return this.exportNoteTo_('printer', noteId, options);
|
return this.exportNoteTo_('printer', noteId, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async defaultFilename(noteIds, fileExtension) {
|
static async defaultFilename(noteId, fileExtension) {
|
||||||
if (!noteIds) {
|
if (!noteId) return '';
|
||||||
return '';
|
const note = await Note.load(noteId);
|
||||||
}
|
|
||||||
|
|
||||||
const note = await Note.load(noteIds[0]);
|
|
||||||
// In a rare case the passed not will be null, use the id for filename
|
// In a rare case the passed not will be null, use the id for filename
|
||||||
if (note === null) {
|
const filename = friendlySafeFilename(note ? note.title : noteId, 100);
|
||||||
const filename = friendlySafeFilename(noteIds[0], 100);
|
|
||||||
|
|
||||||
return `${filename}.${fileExtension}`;
|
return `${filename}.${fileExtension}`;
|
||||||
}
|
}
|
||||||
const folder = await Folder.load(note.parent_id);
|
|
||||||
|
|
||||||
const filename = friendlySafeFilename(note.title, 100);
|
|
||||||
|
|
||||||
// In a less rare case the folder will be null, just ignore it
|
|
||||||
if (folder === null) {
|
|
||||||
return `${filename}.${fileExtension}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const foldername = friendlySafeFilename(folder.title, 100);
|
|
||||||
|
|
||||||
// friendlySafeFilename assumes that the file extension is added after
|
|
||||||
return `${foldername} - ${filename}.${fileExtension}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async export(dispatch, module, options = null) {
|
static async export(dispatch, module, options = null) {
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
|
@ -138,9 +121,10 @@ class InteropServiceHelper {
|
||||||
let path = null;
|
let path = null;
|
||||||
|
|
||||||
if (module.target === 'file') {
|
if (module.target === 'file') {
|
||||||
|
const noteId = options.sourceNoteIds && options.sourceNoteIds.length ? options.sourceNoteIds[0] : null;
|
||||||
path = bridge().showSaveDialog({
|
path = bridge().showSaveDialog({
|
||||||
filters: [{ name: module.description, extensions: module.fileExtensions }],
|
filters: [{ name: module.description, extensions: module.fileExtensions }],
|
||||||
defaultPath: await this.defaultFilename(options.sourceNoteIds, module.fileExtensions[0]),
|
defaultPath: await this.defaultFilename(noteId, module.fileExtensions[0]),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
path = bridge().showOpenDialog({
|
path = bridge().showOpenDialog({
|
||||||
|
|
|
@ -535,7 +535,7 @@ class MainScreenComponent extends React.Component {
|
||||||
if (noteIds.length === 1) {
|
if (noteIds.length === 1) {
|
||||||
path = bridge().showSaveDialog({
|
path = bridge().showSaveDialog({
|
||||||
filters: [{ name: _('PDF File'), extensions: ['pdf'] }],
|
filters: [{ name: _('PDF File'), extensions: ['pdf'] }],
|
||||||
defaultPath: await InteropServiceHelper.defaultFilename(noteIds, 'pdf'),
|
defaultPath: await InteropServiceHelper.defaultFilename(noteIds[0], 'pdf'),
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -548,14 +548,20 @@ class MainScreenComponent extends React.Component {
|
||||||
|
|
||||||
for (let i = 0; i < noteIds.length; i++) {
|
for (let i = 0; i < noteIds.length; i++) {
|
||||||
const note = await Note.load(noteIds[i]);
|
const note = await Note.load(noteIds[i]);
|
||||||
const folder = Folder.byId(this.props.folders, note.parent_id);
|
|
||||||
|
|
||||||
const pdfPath = (noteIds.length === 1) ? path :
|
let pdfPath = '';
|
||||||
await shim.fsDriver().findUniqueFilename(`${path}/${this.pdfFileName_(note, folder)}`);
|
|
||||||
|
if (noteIds.length === 1) {
|
||||||
|
pdfPath = path;
|
||||||
|
} else {
|
||||||
|
const n = await InteropServiceHelper.defaultFilename(note.id, 'pdf');
|
||||||
|
pdfPath = await shim.fsDriver().findUniqueFilename(`${path}/${n}`);
|
||||||
|
}
|
||||||
|
|
||||||
await this.printTo_('pdf', { path: pdfPath, noteId: note.id });
|
await this.printTo_('pdf', { path: pdfPath, noteId: note.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
bridge().showErrorMessageBox(error.message);
|
bridge().showErrorMessageBox(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -852,7 +858,6 @@ class MainScreenComponent extends React.Component {
|
||||||
const shareNoteDialogOptions = this.state.shareNoteDialogOptions;
|
const shareNoteDialogOptions = this.state.shareNoteDialogOptions;
|
||||||
|
|
||||||
const bodyEditor = this.props.settingEditorCodeView ? 'AceEditor' : 'TinyMCE';
|
const bodyEditor = this.props.settingEditorCodeView ? 'AceEditor' : 'TinyMCE';
|
||||||
const noteTextComp = <NoteEditor bodyEditor={bodyEditor} style={styles.noteText} />;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={style}>
|
<div style={style}>
|
||||||
|
@ -870,7 +875,7 @@ class MainScreenComponent extends React.Component {
|
||||||
<VerticalResizer style={styles.verticalResizer} onDrag={this.sidebar_onDrag} />
|
<VerticalResizer style={styles.verticalResizer} onDrag={this.sidebar_onDrag} />
|
||||||
<NoteList style={styles.noteList} />
|
<NoteList style={styles.noteList} />
|
||||||
<VerticalResizer style={styles.verticalResizer} onDrag={this.noteList_onDrag} />
|
<VerticalResizer style={styles.verticalResizer} onDrag={this.noteList_onDrag} />
|
||||||
{noteTextComp}
|
<NoteEditor bodyEditor={bodyEditor} style={styles.noteText} />
|
||||||
{pluginDialog}
|
{pluginDialog}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -251,6 +251,11 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) {
|
||||||
editor.clearSelection();
|
editor.clearSelection();
|
||||||
editor.moveCursorTo(0, 0);
|
editor.moveCursorTo(0, 0);
|
||||||
},
|
},
|
||||||
|
supportsCommand: (/* name:string*/) => {
|
||||||
|
// TODO: not implemented, currently only used for "search" command
|
||||||
|
// which is not directly supported by Ace Editor.
|
||||||
|
return false;
|
||||||
|
},
|
||||||
execCommand: async (cmd: EditorCommand) => {
|
execCommand: async (cmd: EditorCommand) => {
|
||||||
if (!editor) return false;
|
if (!editor) return false;
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ function styles_(props:ToolbarProps) {
|
||||||
return buildStyle('AceEditorToolbar', props.theme, (/* theme:any*/) => {
|
return buildStyle('AceEditorToolbar', props.theme, (/* theme:any*/) => {
|
||||||
return {
|
return {
|
||||||
root: {
|
root: {
|
||||||
// marginTop: 4,
|
flex: 1,
|
||||||
marginBottom: 0,
|
marginBottom: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
'use strict';
|
|
||||||
Object.defineProperty(exports, '__esModule', { value: true });
|
|
||||||
const React = require('react');
|
|
||||||
const react_1 = require('react');
|
|
||||||
const PlainEditor = (props, ref) => {
|
|
||||||
const editorRef = react_1.useRef();
|
|
||||||
react_1.useImperativeHandle(ref, () => {
|
|
||||||
return {
|
|
||||||
content: () => '',
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
react_1.useEffect(() => {
|
|
||||||
if (!editorRef.current) { return; }
|
|
||||||
editorRef.current.value = props.defaultEditorState.value;
|
|
||||||
}, [props.defaultEditorState]);
|
|
||||||
const onChange = react_1.useCallback((event) => {
|
|
||||||
props.onChange({ changeId: null, content: event.target.value });
|
|
||||||
}, [props.onWillChange, props.onChange]);
|
|
||||||
return (React.createElement('div', { style: props.style },
|
|
||||||
React.createElement('textarea', { ref: editorRef, style: { width: '100%', height: '100%' }, defaultValue: props.defaultEditorState.value, onChange: onChange }),
|
|
||||||
';'));
|
|
||||||
};
|
|
||||||
exports.default = react_1.forwardRef(PlainEditor);
|
|
||||||
// # sourceMappingURL=PlainEditor.js.map
|
|
|
@ -145,6 +145,8 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||||
const markupToHtml = useRef(null);
|
const markupToHtml = useRef(null);
|
||||||
markupToHtml.current = props.markupToHtml;
|
markupToHtml.current = props.markupToHtml;
|
||||||
|
|
||||||
|
const lastOnChangeEventContent = useRef<string>('');
|
||||||
|
|
||||||
const rootIdRef = useRef<string>(`tinymce-${Date.now()}${Math.round(Math.random() * 10000)}`);
|
const rootIdRef = useRef<string>(`tinymce-${Date.now()}${Math.round(Math.random() * 10000)}`);
|
||||||
const editorRef = useRef<any>(null);
|
const editorRef = useRef<any>(null);
|
||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
|
@ -170,15 +172,7 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||||
|
|
||||||
if (nodeName === 'A' && (event.ctrlKey || event.metaKey)) {
|
if (nodeName === 'A' && (event.ctrlKey || event.metaKey)) {
|
||||||
const href = event.target.getAttribute('href');
|
const href = event.target.getAttribute('href');
|
||||||
// const joplinUrl = href.indexOf('joplin://') === 0 ? href : null;
|
|
||||||
|
|
||||||
// if (joplinUrl) {
|
|
||||||
// props.onMessage({
|
|
||||||
// name: 'openInternal',
|
|
||||||
// args: {
|
|
||||||
// url: joplinUrl,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
if (href.indexOf('#') === 0) {
|
if (href.indexOf('#') === 0) {
|
||||||
const anchorName = href.substr(1);
|
const anchorName = href.substr(1);
|
||||||
const anchor = editor.getDoc().getElementById(anchorName);
|
const anchor = editor.getDoc().getElementById(anchorName);
|
||||||
|
@ -188,12 +182,7 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||||
reg.logger().warn('TinyMce: could not find anchor with ID ', anchorName);
|
reg.logger().warn('TinyMce: could not find anchor with ID ', anchorName);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
props.onMessage({
|
props.onMessage({ channel: href });
|
||||||
name: 'openUrl',
|
|
||||||
args: {
|
|
||||||
url: href,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [editor, props.onMessage]);
|
}, [editor, props.onMessage]);
|
||||||
|
@ -216,6 +205,10 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||||
clearState: () => {
|
clearState: () => {
|
||||||
console.warn('TinyMCE::clearState - not implemented');
|
console.warn('TinyMCE::clearState - not implemented');
|
||||||
},
|
},
|
||||||
|
supportsCommand: (name:string) => {
|
||||||
|
// TODO: should also handle commands that are not in this map (insertText, focus, etc);
|
||||||
|
return !!joplinCommandToTinyMceCommands[name];
|
||||||
|
},
|
||||||
execCommand: async (cmd:EditorCommand) => {
|
execCommand: async (cmd:EditorCommand) => {
|
||||||
if (!editor) return false;
|
if (!editor) return false;
|
||||||
|
|
||||||
|
@ -398,6 +391,10 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||||
.tox .tox-dialog__footer {
|
.tox .tox-dialog__footer {
|
||||||
border-color: ${theme.dividerColor} !important;
|
border-color: ${theme.dividerColor} !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tox-tinymce {
|
||||||
|
border-top: none !important;
|
||||||
|
}
|
||||||
`));
|
`));
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -627,15 +624,16 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
||||||
const loadContent = async () => {
|
const loadContent = async () => {
|
||||||
|
if (lastOnChangeEventContent.current === props.content) return;
|
||||||
|
|
||||||
const result = await props.markupToHtml(props.contentMarkupLanguage, props.content, markupRenderOptions({ resourceInfos: props.resourceInfos }));
|
const result = await props.markupToHtml(props.contentMarkupLanguage, props.content, markupRenderOptions({ resourceInfos: props.resourceInfos }));
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
|
|
||||||
|
lastOnChangeEventContent.current = props.content;
|
||||||
editor.setContent(result.html);
|
editor.setContent(result.html);
|
||||||
|
|
||||||
await loadDocumentAssets(editor, await props.allAssets(props.contentMarkupLanguage));
|
await loadDocumentAssets(editor, await props.allAssets(props.contentMarkupLanguage));
|
||||||
|
|
||||||
editor.getDoc().addEventListener('click', onEditorContentClick);
|
|
||||||
|
|
||||||
// Need to clear UndoManager to avoid this problem:
|
// Need to clear UndoManager to avoid this problem:
|
||||||
// - Load note 1
|
// - Load note 1
|
||||||
// - Make a change
|
// - Make a change
|
||||||
|
@ -650,9 +648,17 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [editor, props.markupToHtml, props.allAssets, props.content, props.resourceInfos]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!editor) return () => {};
|
||||||
|
|
||||||
|
editor.getDoc().addEventListener('click', onEditorContentClick);
|
||||||
|
return () => {
|
||||||
editor.getDoc().removeEventListener('click', onEditorContentClick);
|
editor.getDoc().removeEventListener('click', onEditorContentClick);
|
||||||
};
|
};
|
||||||
}, [editor, props.markupToHtml, props.allAssets, onEditorContentClick, props.resourceInfos]);
|
}, [editor, onEditorContentClick]);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------
|
||||||
// Handle onChange event
|
// Handle onChange event
|
||||||
|
@ -685,6 +691,8 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||||
|
|
||||||
if (!editor) return;
|
if (!editor) return;
|
||||||
|
|
||||||
|
lastOnChangeEventContent.current = contentMd;
|
||||||
|
|
||||||
props_onChangeRef.current({
|
props_onChangeRef.current({
|
||||||
changeId: changeId,
|
changeId: changeId,
|
||||||
content: contentMd,
|
content: contentMd,
|
||||||
|
|
|
@ -34,6 +34,8 @@ const NoteRevisionViewer = require('../NoteRevisionViewer.min');
|
||||||
const TagList = require('../TagList.min.js');
|
const TagList = require('../TagList.min.js');
|
||||||
|
|
||||||
function NoteEditor(props: NoteTextProps) {
|
function NoteEditor(props: NoteTextProps) {
|
||||||
|
const theme = themeStyle(props.theme);
|
||||||
|
|
||||||
const [showRevisions, setShowRevisions] = useState(false);
|
const [showRevisions, setShowRevisions] = useState(false);
|
||||||
const [titleHasBeenManuallyChanged, setTitleHasBeenManuallyChanged] = useState(false);
|
const [titleHasBeenManuallyChanged, setTitleHasBeenManuallyChanged] = useState(false);
|
||||||
const [scrollWhenReady, setScrollWhenReady] = useState<ScrollOptions>(null);
|
const [scrollWhenReady, setScrollWhenReady] = useState<ScrollOptions>(null);
|
||||||
|
@ -268,7 +270,7 @@ function NoteEditor(props: NoteTextProps) {
|
||||||
});
|
});
|
||||||
}, [formNote, handleProvisionalFlag]);
|
}, [formNote, handleProvisionalFlag]);
|
||||||
|
|
||||||
const onMessage = useMessageHandler(scrollWhenReady, setScrollWhenReady, editorRef, setLocalSearchResultCount, props.dispatch);
|
const onMessage = useMessageHandler(scrollWhenReady, setScrollWhenReady, editorRef, setLocalSearchResultCount, props.dispatch, formNote);
|
||||||
|
|
||||||
const introductionPostLinkClick = useCallback(() => {
|
const introductionPostLinkClick = useCallback(() => {
|
||||||
bridge().openExternal('https://www.patreon.com/posts/34246624');
|
bridge().openExternal('https://www.patreon.com/posts/34246624');
|
||||||
|
@ -379,17 +381,13 @@ function NoteEditor(props: NoteTextProps) {
|
||||||
|
|
||||||
function renderNoteToolbar() {
|
function renderNoteToolbar() {
|
||||||
const toolbarStyle = {
|
const toolbarStyle = {
|
||||||
// marginTop: 4,
|
|
||||||
marginBottom: 0,
|
marginBottom: 0,
|
||||||
flex: 1,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return <NoteToolbar
|
return <NoteToolbar
|
||||||
theme={props.theme}
|
theme={props.theme}
|
||||||
note={formNote}
|
note={formNote}
|
||||||
dispatch={props.dispatch}
|
|
||||||
style={toolbarStyle}
|
style={toolbarStyle}
|
||||||
watchedNoteFiles={props.watchedNoteFiles}
|
|
||||||
onButtonClick={noteToolbar_buttonClick}
|
onButtonClick={noteToolbar_buttonClick}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
@ -414,7 +412,7 @@ function NoteEditor(props: NoteTextProps) {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
theme: props.theme,
|
theme: props.theme,
|
||||||
dispatch: props.dispatch,
|
dispatch: props.dispatch,
|
||||||
noteToolbar: renderNoteToolbar(),
|
noteToolbar: null,// renderNoteToolbar(),
|
||||||
onScroll: onScroll,
|
onScroll: onScroll,
|
||||||
searchMarkers: searchMarkers,
|
searchMarkers: searchMarkers,
|
||||||
visiblePanes: props.noteVisiblePanes || ['editor', 'viewer'],
|
visiblePanes: props.noteVisiblePanes || ['editor', 'viewer'],
|
||||||
|
@ -432,7 +430,7 @@ function NoteEditor(props: NoteTextProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const wysiwygBanner = props.bodyEditor !== 'TinyMCE' ? null : (
|
const wysiwygBanner = props.bodyEditor !== 'TinyMCE' ? null : (
|
||||||
<div style={{ ...styles.warningBanner, marginBottom: 10 }}>
|
<div style={{ ...styles.warningBanner }}>
|
||||||
This is an experimental WYSIWYG editor for evaluation only. Please do not use with important notes as you may lose some data! See the <a style={styles.urlColor} onClick={introductionPostLinkClick} href="#">introduction post</a> for more information.
|
This is an experimental WYSIWYG editor for evaluation only. Please do not use with important notes as you may lose some data! See the <a style={styles.urlColor} onClick={introductionPostLinkClick} href="#">introduction post</a> for more information.
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -511,13 +509,12 @@ function NoteEditor(props: NoteTextProps) {
|
||||||
return (
|
return (
|
||||||
<div style={styles.root} onDrop={onDrop}>
|
<div style={styles.root} onDrop={onDrop}>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
{wysiwygBanner}
|
|
||||||
{tagList}
|
{tagList}
|
||||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', paddingBottom: 5, borderBottomWidth: 1, borderBottomColor: theme.dividerColor, borderBottomStyle: 'solid' }}>
|
||||||
|
{renderNoteToolbar()}
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
ref={titleInputRef}
|
ref={titleInputRef}
|
||||||
// disabled={waitingToSaveNote}
|
|
||||||
placeholder={props.isProvisional ? _('Creating new %s...', formNote.is_todo ? _('to-do') : _('note')) : ''}
|
placeholder={props.isProvisional ? _('Creating new %s...', formNote.is_todo ? _('to-do') : _('note')) : ''}
|
||||||
style={styles.titleInput}
|
style={styles.titleInput}
|
||||||
onChange={onTitleChange}
|
onChange={onTitleChange}
|
||||||
|
@ -532,6 +529,7 @@ function NoteEditor(props: NoteTextProps) {
|
||||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||||
{renderSearchBar()}
|
{renderSearchBar()}
|
||||||
</div>
|
</div>
|
||||||
|
{wysiwygBanner}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,7 @@ export default function styles(props: NoteTextProps) {
|
||||||
...props.style,
|
...props.style,
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
paddingLeft: 10,
|
paddingLeft: 10,
|
||||||
paddingTop: 10,
|
paddingTop: 5,
|
||||||
borderLeftWidth: 1,
|
borderLeftWidth: 1,
|
||||||
borderLeftColor: theme.dividerColor,
|
borderLeftColor: theme.dividerColor,
|
||||||
borderLeftStyle: 'solid',
|
borderLeftStyle: 'solid',
|
||||||
|
@ -23,7 +23,7 @@ export default function styles(props: NoteTextProps) {
|
||||||
paddingRight: 8,
|
paddingRight: 8,
|
||||||
marginRight: theme.paddingLeft,
|
marginRight: theme.paddingLeft,
|
||||||
color: theme.textStyle.color,
|
color: theme.textStyle.color,
|
||||||
fontSize: theme.textStyle.fontSize * 1.25 * 1.5,
|
fontSize: theme.textStyle.fontSize * 1.25,
|
||||||
backgroundColor: theme.backgroundColor,
|
backgroundColor: theme.backgroundColor,
|
||||||
border: '1px solid',
|
border: '1px solid',
|
||||||
borderColor: theme.dividerColor,
|
borderColor: theme.dividerColor,
|
||||||
|
@ -33,6 +33,8 @@ export default function styles(props: NoteTextProps) {
|
||||||
fontFamily: theme.fontFamily,
|
fontFamily: theme.fontFamily,
|
||||||
padding: 10,
|
padding: 10,
|
||||||
fontSize: theme.fontSize,
|
fontSize: theme.fontSize,
|
||||||
|
marginTop: 5,
|
||||||
|
marginBottom: 5,
|
||||||
},
|
},
|
||||||
tinyMCE: {
|
tinyMCE: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
|
|
@ -49,6 +49,7 @@ export interface NoteBodyEditorProps {
|
||||||
visiblePanes: string[],
|
visiblePanes: string[],
|
||||||
keyboardMode: string,
|
keyboardMode: string,
|
||||||
resourceInfos: ResourceInfos,
|
resourceInfos: ResourceInfos,
|
||||||
|
showLocalSearch: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FormNote {
|
export interface FormNote {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
import { FormNote } from './types';
|
||||||
const BaseItem = require('lib/models/BaseItem');
|
const BaseItem = require('lib/models/BaseItem');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
|
@ -15,7 +15,7 @@ const { clipboard } = require('electron');
|
||||||
const { toSystemSlashes } = require('lib/path-utils');
|
const { toSystemSlashes } = require('lib/path-utils');
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
|
|
||||||
export default function useMessageHandler(scrollWhenReady:any, setScrollWhenReady:Function, editorRef:any, setLocalSearchResultCount:Function, dispatch:Function) {
|
export default function useMessageHandler(scrollWhenReady:any, setScrollWhenReady:Function, editorRef:any, setLocalSearchResultCount:Function, dispatch:Function, formNote:FormNote) {
|
||||||
return useCallback(async (event: any) => {
|
return useCallback(async (event: any) => {
|
||||||
const msg = event.channel ? event.channel : '';
|
const msg = event.channel ? event.channel : '';
|
||||||
const args = event.args;
|
const args = event.args;
|
||||||
|
@ -128,10 +128,10 @@ export default function useMessageHandler(scrollWhenReady:any, setScrollWhenRead
|
||||||
folderId: item.parent_id,
|
folderId: item.parent_id,
|
||||||
noteId: item.id,
|
noteId: item.id,
|
||||||
hash: resourceUrlInfo.hash,
|
hash: resourceUrlInfo.hash,
|
||||||
// historyNoteAction: {
|
historyNoteAction: {
|
||||||
// id: this.state.note.id,
|
id: formNote.id,
|
||||||
// parent_id: this.state.note.parent_id,
|
parent_id: formNote.parent_id,
|
||||||
// },
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Unsupported item type: ${item.type_}`);
|
throw new Error(`Unsupported item type: ${item.type_}`);
|
||||||
|
@ -148,5 +148,5 @@ export default function useMessageHandler(scrollWhenReady:any, setScrollWhenRead
|
||||||
} else {
|
} else {
|
||||||
bridge().showErrorMessageBox(_('Unsupported link or message: %s', msg));
|
bridge().showErrorMessageBox(_('Unsupported link or message: %s', msg));
|
||||||
}
|
}
|
||||||
}, [dispatch, setLocalSearchResultCount, scrollWhenReady]);
|
}, [dispatch, setLocalSearchResultCount, scrollWhenReady, formNote]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
'use strict';
|
|
||||||
const __awaiter = (this && this.__awaiter) || function(thisArg, _arguments, P, generator) {
|
|
||||||
function adopt(value) { return value instanceof P ? value : new P(function(resolve) { resolve(value); }); }
|
|
||||||
return new (P || (P = Promise))(function(resolve, reject) {
|
|
||||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
||||||
function rejected(value) { try { step(generator['throw'](value)); } catch (e) { reject(e); } }
|
|
||||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
||||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, '__esModule', { value: true });
|
|
||||||
const react_1 = require('react');
|
|
||||||
const resourceHandling_1 = require('./resourceHandling');
|
|
||||||
const ResourceFetcher = require('lib/services/ResourceFetcher.js');
|
|
||||||
const DecryptionWorker = require('lib/services/DecryptionWorker.js');
|
|
||||||
const Note = require('lib/models/Note');
|
|
||||||
function useResourceInfos(dependencies) {
|
|
||||||
const { noteBody } = dependencies;
|
|
||||||
const [resourceInfos, setResourceInfos] = react_1.useState({});
|
|
||||||
function installResourceHandling(refreshResourceHandler) {
|
|
||||||
ResourceFetcher.instance().on('downloadComplete', refreshResourceHandler);
|
|
||||||
ResourceFetcher.instance().on('downloadStarted', refreshResourceHandler);
|
|
||||||
DecryptionWorker.instance().on('resourceDecrypted', refreshResourceHandler);
|
|
||||||
}
|
|
||||||
function uninstallResourceHandling(refreshResourceHandler) {
|
|
||||||
ResourceFetcher.instance().off('downloadComplete', refreshResourceHandler);
|
|
||||||
ResourceFetcher.instance().off('downloadStarted', refreshResourceHandler);
|
|
||||||
DecryptionWorker.instance().off('resourceDecrypted', refreshResourceHandler);
|
|
||||||
}
|
|
||||||
const refreshResource = react_1.useCallback(function(event) {
|
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
|
||||||
const resourceIds = yield Note.linkedResourceIds(noteBody);
|
|
||||||
if (resourceIds.indexOf(event.id) >= 0) {
|
|
||||||
resourceHandling_1.clearResourceCache();
|
|
||||||
setResourceInfos(yield resourceHandling_1.attachedResources(noteBody));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [noteBody]);
|
|
||||||
react_1.useEffect(() => {
|
|
||||||
installResourceHandling(refreshResource);
|
|
||||||
return () => {
|
|
||||||
uninstallResourceHandling(refreshResource);
|
|
||||||
};
|
|
||||||
}, [refreshResource]);
|
|
||||||
return { resourceInfos };
|
|
||||||
}
|
|
||||||
exports.default = useResourceInfos;
|
|
||||||
// # sourceMappingURL=useResourceRefresher.js.map
|
|
|
@ -59,8 +59,12 @@ export default function useWindowCommandHandler(dependencies:HookDependencies) {
|
||||||
editorCmd.name = 'insertText',
|
editorCmd.name = 'insertText',
|
||||||
editorCmd.value = time.formatMsToLocal(new Date().getTime());
|
editorCmd.value = time.formatMsToLocal(new Date().getTime());
|
||||||
} else if (command.name === 'showLocalSearch') {
|
} else if (command.name === 'showLocalSearch') {
|
||||||
|
if (editorRef.current && editorRef.current.supportsCommand('search')) {
|
||||||
|
editorCmd.name = 'search';
|
||||||
|
} else {
|
||||||
setShowLocalSearch(true);
|
setShowLocalSearch(true);
|
||||||
if (noteSearchBarRef.current) noteSearchBarRef.current.wrappedInstance.focus();
|
if (noteSearchBarRef.current) noteSearchBarRef.current.wrappedInstance.focus();
|
||||||
|
}
|
||||||
} else if (command.name === 'insertTemplate') {
|
} else if (command.name === 'insertTemplate') {
|
||||||
editorCmd.name = 'insertText',
|
editorCmd.name = 'insertText',
|
||||||
editorCmd.value = time.formatMsToLocal(new Date().getTime());
|
editorCmd.value = time.formatMsToLocal(new Date().getTime());
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,17 +1,12 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
const { connect } = require('react-redux');
|
||||||
const { buildStyle } = require('../../theme.js');
|
const { buildStyle } = require('../../theme.js');
|
||||||
const Toolbar = require('../Toolbar.min.js');
|
const Toolbar = require('../Toolbar.min.js');
|
||||||
const Note = require('lib/models/Note');
|
const Note = require('lib/models/Note');
|
||||||
|
const Folder = require('lib/models/Folder');
|
||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
|
const { substrWithEllipsis } = require('lib/string-utils');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// const { substrWithEllipsis } = require('lib/string-utils');
|
|
||||||
// const Folder = require('lib/models/Folder');
|
|
||||||
// const { MarkupToHtml } = require('lib/joplin-renderer');
|
|
||||||
|
|
||||||
interface ButtonClickEvent {
|
interface ButtonClickEvent {
|
||||||
name: string,
|
name: string,
|
||||||
|
@ -20,10 +15,14 @@ interface ButtonClickEvent {
|
||||||
interface NoteToolbarProps {
|
interface NoteToolbarProps {
|
||||||
theme: number,
|
theme: number,
|
||||||
style: any,
|
style: any,
|
||||||
|
selectedFolderId: string,
|
||||||
|
folders: any[],
|
||||||
watchedNoteFiles: string[],
|
watchedNoteFiles: string[],
|
||||||
|
notesParentType: string,
|
||||||
note: any,
|
note: any,
|
||||||
dispatch: Function,
|
dispatch: Function,
|
||||||
onButtonClick(event:ButtonClickEvent):void,
|
onButtonClick(event:ButtonClickEvent):void,
|
||||||
|
historyNotes: any[],
|
||||||
}
|
}
|
||||||
|
|
||||||
function styles_(props:NoteToolbarProps) {
|
function styles_(props:NoteToolbarProps) {
|
||||||
|
@ -31,50 +30,52 @@ function styles_(props:NoteToolbarProps) {
|
||||||
return {
|
return {
|
||||||
root: {
|
root: {
|
||||||
...props.style,
|
...props.style,
|
||||||
|
borderBottom: 'none',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function useToolbarItems(note:any, watchedNoteFiles:string[], dispatch:Function, onButtonClick:Function) {
|
function useToolbarItems(props:NoteToolbarProps) {
|
||||||
|
const { note, selectedFolderId, folders, watchedNoteFiles, notesParentType, dispatch, onButtonClick, historyNotes } = props;
|
||||||
|
|
||||||
const toolbarItems = [];
|
const toolbarItems = [];
|
||||||
|
|
||||||
// TODO: add these two items
|
const folder = Folder.byId(folders, selectedFolderId);
|
||||||
|
|
||||||
// if (props.folder && ['Search', 'Tag', 'SmartFilter'].includes(props.notesParentType)) {
|
if (folder && ['Search', 'Tag', 'SmartFilter'].includes(notesParentType)) {
|
||||||
// toolbarItems.push({
|
toolbarItems.push({
|
||||||
// title: _('In: %s', substrWithEllipsis(props.folder.title, 0, 16)),
|
title: _('In: %s', substrWithEllipsis(folder.title, 0, 16)),
|
||||||
// iconName: 'fa-book',
|
iconName: 'fa-book',
|
||||||
// onClick: () => {
|
onClick: () => {
|
||||||
// props.dispatch({
|
props.dispatch({
|
||||||
// type: 'FOLDER_AND_NOTE_SELECT',
|
type: 'FOLDER_AND_NOTE_SELECT',
|
||||||
// folderId: props.folder.id,
|
folderId: folder.id,
|
||||||
// noteId: props.formNote.id,
|
noteId: note.id,
|
||||||
// });
|
});
|
||||||
// Folder.expandTree(props.folders, props.folder.parent_id);
|
Folder.expandTree(folders, folder.parent_id);
|
||||||
// },
|
},
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (props.historyNotes.length) {
|
if (historyNotes.length) {
|
||||||
// toolbarItems.push({
|
toolbarItems.push({
|
||||||
// tooltip: _('Back'),
|
tooltip: _('Back'),
|
||||||
// iconName: 'fa-arrow-left',
|
iconName: 'fa-arrow-left',
|
||||||
// onClick: () => {
|
onClick: () => {
|
||||||
// if (!props.historyNotes.length) return;
|
if (!historyNotes.length) return;
|
||||||
|
|
||||||
// const lastItem = props.historyNotes[props.historyNotes.length - 1];
|
const lastItem = historyNotes[historyNotes.length - 1];
|
||||||
|
|
||||||
// props.dispatch({
|
dispatch({
|
||||||
// type: 'FOLDER_AND_NOTE_SELECT',
|
type: 'FOLDER_AND_NOTE_SELECT',
|
||||||
// folderId: lastItem.parent_id,
|
folderId: lastItem.parent_id,
|
||||||
// noteId: lastItem.id,
|
noteId: lastItem.id,
|
||||||
// historyNoteAction: 'pop',
|
historyNoteAction: 'pop',
|
||||||
// });
|
});
|
||||||
// },
|
},
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
|
|
||||||
if (watchedNoteFiles.indexOf(note.id) >= 0) {
|
if (watchedNoteFiles.indexOf(note.id) >= 0) {
|
||||||
toolbarItems.push({
|
toolbarItems.push({
|
||||||
|
@ -137,12 +138,20 @@ function useToolbarItems(note:any, watchedNoteFiles:string[], dispatch:Function,
|
||||||
return toolbarItems;
|
return toolbarItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NoteToolbar(props:NoteToolbarProps) {
|
function NoteToolbar(props:NoteToolbarProps) {
|
||||||
const styles = styles_(props);
|
const styles = styles_(props);
|
||||||
|
const toolbarItems = useToolbarItems(props);
|
||||||
const toolbarItems = useToolbarItems(props.note, props.watchedNoteFiles, props.dispatch, props.onButtonClick);
|
return <Toolbar style={styles.root} items={toolbarItems} />;
|
||||||
|
|
||||||
return (
|
|
||||||
<Toolbar style={styles.root} items={toolbarItems} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state:any) => {
|
||||||
|
return {
|
||||||
|
selectedFolderId: state.selectedFolderId,
|
||||||
|
folders: state.folders,
|
||||||
|
watchedNoteFiles: state.watchedNoteFiles,
|
||||||
|
historyNotes: state.historyNotes,
|
||||||
|
notesParentType: state.notesParentType,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(NoteToolbar);
|
||||||
|
|
|
@ -6,13 +6,15 @@ const ToolbarSpace = require('./ToolbarSpace.min.js');
|
||||||
|
|
||||||
class ToolbarComponent extends React.Component {
|
class ToolbarComponent extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const style = Object.assign({}, this.props.style);
|
|
||||||
const theme = themeStyle(this.props.theme);
|
const theme = themeStyle(this.props.theme);
|
||||||
style.height = theme.toolbarHeight;
|
|
||||||
style.display = 'flex';
|
const style = Object.assign({
|
||||||
style.flexDirection = 'row';
|
height: theme.toolbarHeight,
|
||||||
style.borderBottom = `1px solid ${theme.dividerColor}`;
|
display: 'flex',
|
||||||
style.boxSizing = 'border-box';
|
flexDirection: 'row',
|
||||||
|
borderBottom: `1px solid ${theme.dividerColor}`,
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
}, this.props.style);
|
||||||
|
|
||||||
const itemComps = [];
|
const itemComps = [];
|
||||||
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
'use strict';
|
|
||||||
Object.defineProperty(exports, '__esModule', { value: true });
|
|
||||||
const joplinRendererUtils = require('lib/joplin-renderer').utils;
|
|
||||||
const Resource = require('lib/models/Resource');
|
|
||||||
function resourcesStatus(resourceInfos) {
|
|
||||||
let lowestIndex = joplinRendererUtils.resourceStatusIndex('ready');
|
|
||||||
for (const id in resourceInfos) {
|
|
||||||
const s = joplinRendererUtils.resourceStatus(Resource, resourceInfos[id]);
|
|
||||||
const idx = joplinRendererUtils.resourceStatusIndex(s);
|
|
||||||
if (idx < lowestIndex) { lowestIndex = idx; }
|
|
||||||
}
|
|
||||||
return joplinRendererUtils.resourceStatusName(lowestIndex);
|
|
||||||
}
|
|
||||||
exports.resourcesStatus = resourcesStatus;
|
|
||||||
// # sourceMappingURL=NoteText.js.map
|
|
Loading…
Reference in New Issue