1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-06-18 23:07:45 +02:00
Files
.github
Assets
CliClient
Clipper
ElectronClient
Modules
ReactNativeClient
MarkdownEditor
android
ios
lib
components
screens
NoteTagsDialog.js
config.js
dropbox-login.js
encryption-config.js
folder.js
log.js
note.js
notes.js
onedrive-login.js
search.js
status.js
tags.js
shared
CameraView.js
Dropdown.js
ItemList.js
ModalDialog.js
SafeAreaView.js
SaveIcon.png
action-button.js
app-nav.js
base-screen.js
checkbox.js
global-style.js
note-body-viewer.js
note-item.js
note-list.js
screen-header.js
select-date-time-dialog.js
side-menu-content-note.js
side-menu-content.js
side-menu.js
images
joplin-renderer
migrations
models
renderers
services
vendor
ArrayUtils.js
AsyncActionQueue.ts
BaseApplication.js
BaseModel.js
BaseSyncTarget.js
Cache.js
ClipperServer.js
CssUtils.js
DropboxApi.js
EventDispatcher.js
HtmlToMd.js
JoplinError.js
JoplinServerApi.ts
ModelCache.js
ObjectUtils.js
SyncTargetDropbox.js
SyncTargetFilesystem.js
SyncTargetMemory.js
SyncTargetNextcloud.js
SyncTargetOneDrive.js
SyncTargetOneDriveDev.js
SyncTargetRegistry.js
SyncTargetWebDAV.js
TaskQueue.js
TemplateUtils.js
WebDavApi.js
WelcomeUtils.js
database-driver-node.js
database-driver-react-native.js
database.js
dialogs.js
file-api-driver-dropbox.js
file-api-driver-local.js
file-api-driver-memory.js
file-api-driver-onedrive.js
file-api-driver-webdav.js
file-api.js
folders-screen-utils.js
fs-driver-base.js
fs-driver-dummy.js
fs-driver-node.js
fs-driver-rn.js
geolocation-node.js
geolocation-react.js
htmlUtils.js
import-enex-html-gen.js
import-enex-md-gen.js
import-enex.js
joplin-database.js
locale.js
logger.js
markJsUtils.js
markdownUtils.js
markupLanguageUtils.js
mime-utils.js
net-utils.js
onedrive-api-node-utils.js
onedrive-api.js
package.json
parameters.js
parseUri.js
path-utils.js
poor-man-intervals.js
promise-utils.js
randomClipperPort.js
react-logger.js
reducer.js
registry.js
reserved-ids.js
resourceUtils.js
shim-init-node.js
shim-init-react.js
shim.js
string-utils-common.js
string-utils.js
synchronizer.js
time-utils.js
urlUtils.js
uuid.js
welcomeAssets.js
locales
tools
.buckconfig
.flowconfig
.gitattributes
.gitignore
.watchmanconfig
PluginAssetsLoader.ts
app.json
clean_build.bat
gulpfile.js
index.android.js
index.ios.js
index.js
main.js
metro.config.js
package-lock.json
package.json
root.js
setUpQuickActions.ts
Tools
docs
patches
readme
.eslintignore
.eslintrc.js
.gitignore
.travis.yml
BUILD.md
CONTRIBUTING.md
Joplin_install_and_update.sh
LICENSE
README.md
SECURITY.md
_config.yml
appveyor.yml
build_tools_check.js
gulpfile.js
joplin.code-workspace
joplin.sublime-project
package-lock.json
package.json
tsconfig.json
joplin/ReactNativeClient/lib/components/screens/note.js

1053 lines
32 KiB
JavaScript
Raw Normal View History

const React = require('react');
const { ScrollView, Platform, Clipboard, Keyboard, View, TextInput, StyleSheet, Linking, Image, Share, Dimensions } = require('react-native');
const { connect } = require('react-redux');
const { uuid } = require('lib/uuid.js');
Mobile: Add toolbar, list continuation and Markdown preview to editor (#2224) * The basic editor is working! No list continuation still though * List continuation is working! Now to delete when entering again and not typing on line + handle ordered lists * Supports checkboxes + attempted at setting font * Editor font works now; now need to fix the delete (look at past state) * Fix deletion problem * Add ordered list handler * Add comments * Extract insertListLine * End lists on enter for empty bullets * Add MarkdownView (renders badly though) * Save edited text from MarkdownEditor * Cleanup * Refactor react-native-markdown-editor/ * Rename react-native-markdown-editor/ => MarkdownEditor/ * Cleanup * Fix preview styles; still need to fix checkbox problem * Fix keyboard padding * Change name back to #body_changeText * Incorporate PR feedback from @laurent22 * wip: Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Remove log statement * Focus TextInput in MarkdownEditor from grandparent * Make eslint happy * Extract textInputRefName to shared variable * Remove accidental #setState * Cleanup * Cleanup * Run linter * Cleanup * Update button order * Improve styles for config descriptions * Allow descriptions to be added to BOOL type Setting configs * Add editorBeta Setting * Move FailSafe details to description text * Update descriptionText styles * Put the editor under the beta flag toggle * Incorporate PR feedback from @laurent22 * Refactor Markdown editor focusing * Cleanup * Reorder MarkdownEditor formats * Make applyListFormat behavior more intuitive * Add comment * Show MarkdownEditor with preview by default * Show preview by default, then hide on typing * Fix MarkdownEditor selection bug * Cleanup * Update Markdown button styles * Make Markdown button colors theme-conscious * Fix merge conflict resolution mistake * Fix broken import * Delete package-lock.json * Reset package-lock.json Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-25 03:50:45 -07:00
const { MarkdownEditor } = require('../../../MarkdownEditor/index.js');
const RNFS = require('react-native-fs');
const Note = require('lib/models/Note.js');
const BaseItem = require('lib/models/BaseItem.js');
const Setting = require('lib/models/Setting.js');
const Resource = require('lib/models/Resource.js');
const Folder = require('lib/models/Folder.js');
const md5 = require('md5');
const { BackButtonService } = require('lib/services/back-button.js');
const NavService = require('lib/services/NavService.js');
const BaseModel = require('lib/BaseModel.js');
const { ActionButton } = require('lib/components/action-button.js');
const { fileExtension, safeFileExtension } = require('lib/path-utils.js');
const mimeUtils = require('lib/mime-utils.js').mime;
const { ScreenHeader } = require('lib/components/screen-header.js');
2018-03-17 23:00:01 +00:00
const NoteTagsDialog = require('lib/components/screens/NoteTagsDialog');
const { time } = require('lib/time-utils.js');
const { Checkbox } = require('lib/components/checkbox.js');
const { _ } = require('lib/locale.js');
const { reg } = require('lib/registry.js');
const { shim } = require('lib/shim.js');
const ResourceFetcher = require('lib/services/ResourceFetcher');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { themeStyle, editorFont } = require('lib/components/global-style.js');
const { dialogs } = require('lib/dialogs.js');
const DialogBox = require('react-native-dialogbox').default;
const { NoteBodyViewer } = require('lib/components/note-body-viewer.js');
const { DocumentPicker, DocumentPickerUtil } = require('react-native-document-picker');
const ImageResizer = require('react-native-image-resizer').default;
const shared = require('lib/components/shared/note-screen-shared.js');
const ImagePicker = require('react-native-image-picker');
const { SelectDateTimeDialog } = require('lib/components/select-date-time-dialog.js');
2020-02-09 14:51:12 +00:00
// const ShareExtension = require('react-native-share-extension').default;
2018-10-13 10:32:44 +01:00
const CameraView = require('lib/components/CameraView');
const SearchEngine = require('lib/services/SearchEngine');
const urlUtils = require('lib/urlUtils');
2017-05-12 20:23:54 +00:00
import FileViewer from 'react-native-file-viewer';
2017-07-14 18:49:14 +00:00
class NoteScreenComponent extends BaseScreenComponent {
static navigationOptions() {
2017-05-16 19:57:09 +00:00
return { header: null };
}
2017-05-12 20:23:54 +00:00
constructor() {
super();
2017-07-10 22:34:26 +01:00
this.state = {
note: Note.new(),
mode: 'view',
2017-07-13 22:50:21 +01:00
folder: null,
2017-07-15 00:12:32 +01:00
lastSavedNote: null,
2017-07-24 22:52:30 +01:00
isLoading: true,
titleTextInputHeight: 20,
2017-09-10 17:56:27 +01:00
alarmDialogShown: false,
heightBumpView: 0,
2018-03-17 23:00:01 +00:00
noteTagDialogShown: false,
fromShare: false,
2018-10-13 10:32:44 +01:00
showCamera: false,
noteResources: {},
// HACK: For reasons I can't explain, when the WebView is present, the TextInput initially does not display (It's just a white rectangle with
// no visible text). It will only appear when tapping it or doing certain action like selecting text on the webview. The bug started to
// appear one day and did not go away - reverting to an old RN version did not help, undoing all
// the commits till a working version did not help. The bug also does not happen in the simulator which makes it hard to fix.
// Eventually, a way that "worked" is to add a 1px margin on top of the text input just after the webview has loaded, then removing this
// margin. This forces RN to update the text input and to display it. Maybe that hack can be removed once RN is upgraded.
// See https://github.com/laurent22/joplin/issues/1057
HACK_webviewLoadingState: 0,
2017-07-16 22:17:22 +01:00
};
Mobile: Add toolbar, list continuation and Markdown preview to editor (#2224) * The basic editor is working! No list continuation still though * List continuation is working! Now to delete when entering again and not typing on line + handle ordered lists * Supports checkboxes + attempted at setting font * Editor font works now; now need to fix the delete (look at past state) * Fix deletion problem * Add ordered list handler * Add comments * Extract insertListLine * End lists on enter for empty bullets * Add MarkdownView (renders badly though) * Save edited text from MarkdownEditor * Cleanup * Refactor react-native-markdown-editor/ * Rename react-native-markdown-editor/ => MarkdownEditor/ * Cleanup * Fix preview styles; still need to fix checkbox problem * Fix keyboard padding * Change name back to #body_changeText * Incorporate PR feedback from @laurent22 * wip: Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Remove log statement * Focus TextInput in MarkdownEditor from grandparent * Make eslint happy * Extract textInputRefName to shared variable * Remove accidental #setState * Cleanup * Cleanup * Run linter * Cleanup * Update button order * Improve styles for config descriptions * Allow descriptions to be added to BOOL type Setting configs * Add editorBeta Setting * Move FailSafe details to description text * Update descriptionText styles * Put the editor under the beta flag toggle * Incorporate PR feedback from @laurent22 * Refactor Markdown editor focusing * Cleanup * Reorder MarkdownEditor formats * Make applyListFormat behavior more intuitive * Add comment * Show MarkdownEditor with preview by default * Show preview by default, then hide on typing * Fix MarkdownEditor selection bug * Cleanup * Update Markdown button styles * Make Markdown button colors theme-conscious * Fix merge conflict resolution mistake * Fix broken import * Delete package-lock.json * Reset package-lock.json Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-25 03:50:45 -07:00
this.markdownEditorRef = React.createRef(); // For focusing the Markdown editor
2019-06-26 23:21:12 +01:00
this.doFocusUpdate_ = false;
2017-11-19 15:19:36 +00:00
// iOS doesn't support multiline text fields properly so disable it
this.enableMultilineTitle_ = Platform.OS !== 'ios';
2017-12-19 21:14:40 +01:00
2017-07-16 22:17:22 +01:00
this.saveButtonHasBeenShown_ = false;
2017-07-15 00:12:32 +01:00
2017-08-01 18:53:50 +00:00
this.styles_ = {};
const saveDialog = async () => {
if (this.isModified()) {
const buttonId = await dialogs.pop(this, _('This note has been modified:'), [{ text: _('Save changes'), id: 'save' }, { text: _('Discard changes'), id: 'discard' }, { text: _('Cancel'), id: 'cancel' }]);
if (buttonId == 'cancel') return true;
if (buttonId == 'save') await this.saveNoteButton_press();
}
return false;
};
this.navHandler = async () => {
return await saveDialog();
};
this.backHandler = async () => {
if (this.isModified()) {
await this.saveNoteButton_press();
}
const isProvisionalNote = this.props.provisionalNoteIds.includes(this.props.noteId);
if (isProvisionalNote) {
return false;
}
if (this.state.mode == 'edit') {
Keyboard.dismiss();
this.setState({
note: Object.assign({}, this.state.lastSavedNote),
mode: 'view',
});
return true;
}
return false;
};
2018-03-17 23:00:01 +00:00
this.noteTagDialog_closeRequested = () => {
this.setState({ noteTagDialogShown: false });
};
this.onJoplinLinkClick_ = async msg => {
try {
if (msg.indexOf('joplin://') === 0) {
const resourceUrlInfo = urlUtils.parseResourceUrl(msg);
const itemId = resourceUrlInfo.itemId;
const item = await BaseItem.loadItemById(itemId);
if (!item) throw new Error(_('No item with ID %s', itemId));
if (item.type_ === BaseModel.TYPE_NOTE) {
// Easier to just go back, then go to the note since
2020-02-19 00:09:19 +00:00
// the Note screen doesn't handle reloading a different note
this.props.dispatch({
type: 'NAV_BACK',
});
setTimeout(() => {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Note',
noteId: item.id,
noteHash: resourceUrlInfo.hash,
});
}, 5);
} else if (item.type_ === BaseModel.TYPE_RESOURCE) {
if (!(await Resource.isReady(item))) throw new Error(_('This attachment is not downloaded or not decrypted yet.'));
const resourcePath = Resource.fullPath(item);
await FileViewer.open(resourcePath);
} else {
throw new Error(_('The Joplin mobile app does not currently support this type of link: %s', BaseModel.modelTypeToName(item.type_)));
}
} else {
2019-06-14 08:11:15 +01:00
if (msg.indexOf('file://') === 0) {
throw new Error(_('Links with protocol "%s" are not supported', 'file://'));
} else {
Linking.openURL(msg);
}
}
} catch (error) {
dialogs.error(this, error.message);
}
};
this.refreshResource = async (resource, noteBody = null) => {
if (noteBody === null && this.state.note && this.state.note.body) noteBody = this.state.note.body;
if (noteBody === null) return;
const resourceIds = await Note.linkedResourceIds(noteBody);
if (resourceIds.indexOf(resource.id) >= 0) {
shared.clearResourceCache();
const attachedResources = await shared.attachedResources(noteBody);
this.setState({ noteResources: attachedResources }, () => {
if (this.refs.noteBodyViewer) this.refs.noteBodyViewer.rebuildMd();
});
}
};
2018-10-13 10:32:44 +01:00
this.takePhoto_onPress = this.takePhoto_onPress.bind(this);
2018-10-13 10:32:44 +01:00
this.cameraView_onPhoto = this.cameraView_onPhoto.bind(this);
this.cameraView_onCancel = this.cameraView_onCancel.bind(this);
this.properties_onPress = this.properties_onPress.bind(this);
this.showOnMap_onPress = this.showOnMap_onPress.bind(this);
this.onMarkForDownload = this.onMarkForDownload.bind(this);
this.sideMenuOptions = this.sideMenuOptions.bind(this);
this.folderPickerOptions_valueChanged = this.folderPickerOptions_valueChanged.bind(this);
this.saveNoteButton_press = this.saveNoteButton_press.bind(this);
this.onAlarmDialogAccept = this.onAlarmDialogAccept.bind(this);
this.onAlarmDialogReject = this.onAlarmDialogReject.bind(this);
this.todoCheckbox_change = this.todoCheckbox_change.bind(this);
this.titleTextInput_contentSizeChange = this.titleTextInput_contentSizeChange.bind(this);
this.title_changeText = this.title_changeText.bind(this);
2017-07-15 00:12:32 +01:00
}
2017-08-01 18:53:50 +00:00
styles() {
const themeId = this.props.theme;
const theme = themeStyle(themeId);
const cacheKey = [themeId, this.state.titleTextInputHeight, this.state.HACK_webviewLoadingState].join('_');
if (this.styles_[cacheKey]) return this.styles_[cacheKey];
2017-08-01 18:53:50 +00:00
this.styles_ = {};
const dimensions = Dimensions.get('window');
Mobile: Add toolbar, list continuation and Markdown preview to editor (#2224) * The basic editor is working! No list continuation still though * List continuation is working! Now to delete when entering again and not typing on line + handle ordered lists * Supports checkboxes + attempted at setting font * Editor font works now; now need to fix the delete (look at past state) * Fix deletion problem * Add ordered list handler * Add comments * Extract insertListLine * End lists on enter for empty bullets * Add MarkdownView (renders badly though) * Save edited text from MarkdownEditor * Cleanup * Refactor react-native-markdown-editor/ * Rename react-native-markdown-editor/ => MarkdownEditor/ * Cleanup * Fix preview styles; still need to fix checkbox problem * Fix keyboard padding * Change name back to #body_changeText * Incorporate PR feedback from @laurent22 * wip: Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Remove log statement * Focus TextInput in MarkdownEditor from grandparent * Make eslint happy * Extract textInputRefName to shared variable * Remove accidental #setState * Cleanup * Cleanup * Run linter * Cleanup * Update button order * Improve styles for config descriptions * Allow descriptions to be added to BOOL type Setting configs * Add editorBeta Setting * Move FailSafe details to description text * Update descriptionText styles * Put the editor under the beta flag toggle * Incorporate PR feedback from @laurent22 * Refactor Markdown editor focusing * Cleanup * Reorder MarkdownEditor formats * Make applyListFormat behavior more intuitive * Add comment * Show MarkdownEditor with preview by default * Show preview by default, then hide on typing * Fix MarkdownEditor selection bug * Cleanup * Update Markdown button styles * Make Markdown button colors theme-conscious * Fix merge conflict resolution mistake * Fix broken import * Delete package-lock.json * Reset package-lock.json Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-25 03:50:45 -07:00
// TODO: Clean up these style names and nesting
const styles = {
2017-08-01 18:53:50 +00:00
bodyTextInput: {
flex: 1,
paddingLeft: theme.marginLeft,
paddingRight: theme.marginRight,
// Add extra space to allow scrolling past end of document, and also to fix this:
// https://github.com/laurent22/joplin/issues/1437
paddingBottom: Math.round(dimensions.height / 4),
textAlignVertical: 'top',
2017-08-01 18:53:50 +00:00
color: theme.color,
backgroundColor: theme.backgroundColor,
fontSize: theme.fontSize,
fontFamily: editorFont(this.props.editorFont),
2017-08-01 18:53:50 +00:00
},
noteBodyViewer: {
flex: 1,
paddingLeft: theme.marginLeft,
paddingRight: theme.marginRight,
Mobile: Add toolbar, list continuation and Markdown preview to editor (#2224) * The basic editor is working! No list continuation still though * List continuation is working! Now to delete when entering again and not typing on line + handle ordered lists * Supports checkboxes + attempted at setting font * Editor font works now; now need to fix the delete (look at past state) * Fix deletion problem * Add ordered list handler * Add comments * Extract insertListLine * End lists on enter for empty bullets * Add MarkdownView (renders badly though) * Save edited text from MarkdownEditor * Cleanup * Refactor react-native-markdown-editor/ * Rename react-native-markdown-editor/ => MarkdownEditor/ * Cleanup * Fix preview styles; still need to fix checkbox problem * Fix keyboard padding * Change name back to #body_changeText * Incorporate PR feedback from @laurent22 * wip: Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Remove log statement * Focus TextInput in MarkdownEditor from grandparent * Make eslint happy * Extract textInputRefName to shared variable * Remove accidental #setState * Cleanup * Cleanup * Run linter * Cleanup * Update button order * Improve styles for config descriptions * Allow descriptions to be added to BOOL type Setting configs * Add editorBeta Setting * Move FailSafe details to description text * Update descriptionText styles * Put the editor under the beta flag toggle * Incorporate PR feedback from @laurent22 * Refactor Markdown editor focusing * Cleanup * Reorder MarkdownEditor formats * Make applyListFormat behavior more intuitive * Add comment * Show MarkdownEditor with preview by default * Show preview by default, then hide on typing * Fix MarkdownEditor selection bug * Cleanup * Update Markdown button styles * Make Markdown button colors theme-conscious * Fix merge conflict resolution mistake * Fix broken import * Delete package-lock.json * Reset package-lock.json Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-25 03:50:45 -07:00
},
noteBodyViewerPreview: {
borderTopColor: theme.dividerColor,
borderTopWidth: 1,
borderBottomColor: theme.dividerColor,
borderBottomWidth: 1,
2017-08-01 18:53:50 +00:00
},
checkbox: {
color: theme.color,
paddingRight: 10,
paddingLeft: theme.marginLeft,
paddingTop: 10, // Added for iOS (Not needed for Android??)
paddingBottom: 10, // Added for iOS (Not needed for Android??)
},
Mobile: Add toolbar, list continuation and Markdown preview to editor (#2224) * The basic editor is working! No list continuation still though * List continuation is working! Now to delete when entering again and not typing on line + handle ordered lists * Supports checkboxes + attempted at setting font * Editor font works now; now need to fix the delete (look at past state) * Fix deletion problem * Add ordered list handler * Add comments * Extract insertListLine * End lists on enter for empty bullets * Add MarkdownView (renders badly though) * Save edited text from MarkdownEditor * Cleanup * Refactor react-native-markdown-editor/ * Rename react-native-markdown-editor/ => MarkdownEditor/ * Cleanup * Fix preview styles; still need to fix checkbox problem * Fix keyboard padding * Change name back to #body_changeText * Incorporate PR feedback from @laurent22 * wip: Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Remove log statement * Focus TextInput in MarkdownEditor from grandparent * Make eslint happy * Extract textInputRefName to shared variable * Remove accidental #setState * Cleanup * Cleanup * Run linter * Cleanup * Update button order * Improve styles for config descriptions * Allow descriptions to be added to BOOL type Setting configs * Add editorBeta Setting * Move FailSafe details to description text * Update descriptionText styles * Put the editor under the beta flag toggle * Incorporate PR feedback from @laurent22 * Refactor Markdown editor focusing * Cleanup * Reorder MarkdownEditor formats * Make applyListFormat behavior more intuitive * Add comment * Show MarkdownEditor with preview by default * Show preview by default, then hide on typing * Fix MarkdownEditor selection bug * Cleanup * Update Markdown button styles * Make Markdown button colors theme-conscious * Fix merge conflict resolution mistake * Fix broken import * Delete package-lock.json * Reset package-lock.json Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-25 03:50:45 -07:00
markdownButtons: {
borderColor: theme.dividerColor,
color: theme.htmlLinkColor,
},
2017-08-01 18:53:50 +00:00
};
styles.titleContainer = {
flex: 0,
flexDirection: 'row',
2017-08-01 18:53:50 +00:00
paddingLeft: theme.marginLeft,
paddingRight: theme.marginRight,
borderBottomColor: theme.dividerColor,
borderBottomWidth: 1,
};
styles.titleContainerTodo = Object.assign({}, styles.titleContainer);
2017-08-21 20:32:43 +02:00
styles.titleContainerTodo.paddingLeft = 0;
2017-08-01 18:53:50 +00:00
styles.titleTextInput = {
flex: 1,
marginTop: 0,
paddingLeft: 0,
color: theme.color,
backgroundColor: theme.backgroundColor,
fontWeight: 'bold',
fontSize: theme.fontSize,
paddingTop: 10, // Added for iOS (Not needed for Android??)
paddingBottom: 10, // Added for iOS (Not needed for Android??)
};
if (this.enableMultilineTitle_) styles.titleTextInput.height = this.state.titleTextInputHeight;
if (this.state.HACK_webviewLoadingState === 1) styles.titleTextInput.marginTop = 1;
this.styles_[cacheKey] = StyleSheet.create(styles);
return this.styles_[cacheKey];
2017-08-01 18:53:50 +00:00
}
2017-07-15 00:12:32 +01:00
isModified() {
return shared.isModified(this);
2017-05-12 20:23:54 +00:00
}
async UNSAFE_componentWillMount() {
BackButtonService.addHandler(this.backHandler);
NavService.addHandler(this.navHandler);
2017-07-15 00:12:32 +01:00
shared.clearResourceCache();
shared.installResourceHandling(this.refreshResource);
await shared.initState(this);
2017-07-23 15:11:44 +01:00
if (this.state.note && this.state.note.body && Setting.value('sync.resourceDownloadMode') === 'auto') {
const resourceIds = await Note.linkedResourceIds(this.state.note.body);
await ResourceFetcher.instance().markForDownload(resourceIds);
}
2017-07-13 22:50:21 +01:00
}
onMarkForDownload(event) {
ResourceFetcher.instance().markForDownload(event.resourceId);
}
componentDidUpdate(prevProps) {
2019-06-26 23:21:12 +01:00
if (this.doFocusUpdate_) {
this.doFocusUpdate_ = false;
this.focusUpdate();
}
if (prevProps.showSideMenu !== this.props.showSideMenu && this.props.showSideMenu) {
this.props.dispatch({
type: 'NOTE_SIDE_MENU_OPTIONS_SET',
options: this.sideMenuOptions(),
});
}
2019-06-26 23:21:12 +01:00
}
2017-07-15 00:12:32 +01:00
componentWillUnmount() {
BackButtonService.removeHandler(this.backHandler);
NavService.removeHandler(this.navHandler);
shared.uninstallResourceHandling(this.refreshResource);
2020-02-09 14:51:12 +00:00
// if (Platform.OS !== 'ios' && this.state.fromShare) {
// ShareExtension.close();
// }
}
2017-06-06 20:01:43 +00:00
title_changeText(text) {
shared.noteComponent_change(this, 'title', text);
this.setState({ newAndNoTitleChangeNoteId: null });
2019-06-26 23:26:26 +01:00
this.scheduleSave();
2017-05-12 20:23:54 +00:00
}
2017-06-06 20:01:43 +00:00
body_changeText(text) {
shared.noteComponent_change(this, 'body', text);
2019-06-26 23:26:26 +01:00
this.scheduleSave();
}
scheduleSave() {
if (this.scheduleSaveIID_) {
clearTimeout(this.scheduleSaveIID_);
this.scheduleSaveIID_ = null;
}
this.scheduleSaveIID_ = setTimeout(async () => {
await shared.saveNoteButton_press(this);
}, 1000);
}
async saveNoteButton_press(folderId = null) {
await shared.saveNoteButton_press(this, folderId);
2017-07-17 21:22:05 +01:00
2017-09-24 15:48:23 +01:00
Keyboard.dismiss();
2017-05-12 20:23:54 +00:00
}
async saveOneProperty(name, value) {
await shared.saveOneProperty(this, name, value);
}
2017-07-15 00:12:32 +01:00
async deleteNote_onPress() {
const note = this.state.note;
2017-07-15 00:12:32 +01:00
if (!note.id) return;
const ok = await dialogs.confirm(this, _('Delete note?'));
2017-07-15 00:12:32 +01:00
if (!ok) return;
const folderId = note.parent_id;
2017-07-15 00:12:32 +01:00
await Note.delete(note.id);
2017-07-16 22:17:22 +01:00
2017-07-25 18:09:01 +00:00
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Notes',
2017-07-25 18:09:01 +00:00
folderId: folderId,
});
2017-06-04 16:01:52 +01:00
}
2017-08-01 23:40:14 +02:00
async pickDocument() {
return new Promise((resolve) => {
DocumentPicker.show({ filetype: [DocumentPickerUtil.allFiles()] }, (error, res) => {
2017-08-01 23:40:14 +02:00
if (error) {
2017-09-10 17:57:06 +01:00
// Also returns an error if the user doesn't pick a file
// so just resolve with null.
console.info('pickDocument error:', error);
2017-09-10 17:57:06 +01:00
resolve(null);
2017-08-01 23:40:14 +02:00
return;
}
resolve(res);
});
});
}
2017-08-02 17:47:25 +00:00
async imageDimensions(uri) {
return new Promise((resolve, reject) => {
Image.getSize(
uri,
(width, height) => {
resolve({ width: width, height: height });
},
error => {
reject(error);
}
);
2017-08-02 17:47:25 +00:00
});
}
showImagePicker(options) {
return new Promise((resolve) => {
ImagePicker.launchImageLibrary(options, response => {
resolve(response);
});
});
}
2017-08-01 23:40:14 +02:00
async resizeImage(localFilePath, targetPath, mimeType) {
const maxSize = Resource.IMAGE_MAX_DIMENSION;
2017-08-02 17:47:25 +00:00
const dimensions = await this.imageDimensions(localFilePath);
reg.logger().info('Original dimensions ', dimensions);
let mustResize = dimensions.width > maxSize || dimensions.height > maxSize;
if (mustResize) {
const buttonId = await dialogs.pop(this, _('You are about to attach a large image (%dx%d pixels). Would you like to resize it down to %d pixels before attaching it?', dimensions.width, dimensions.height, maxSize), [
{ text: _('Yes'), id: 'yes' },
{ text: _('No'), id: 'no' },
{ text: _('Cancel'), id: 'cancel' },
]);
if (buttonId === 'cancel') return false;
mustResize = buttonId === 'yes';
}
if (mustResize) {
dimensions.width = maxSize;
dimensions.height = maxSize;
reg.logger().info('New dimensions ', dimensions);
2017-12-19 21:14:40 +01:00
const format = mimeType == 'image/png' ? 'PNG' : 'JPEG';
reg.logger().info(`Resizing image ${localFilePath}`);
const resizedImage = await ImageResizer.createResizedImage(localFilePath, dimensions.width, dimensions.height, format, 85); // , 0, targetPath);
2017-12-19 21:14:40 +01:00
const resizedImagePath = resizedImage.uri;
reg.logger().info('Resized image ', resizedImagePath);
reg.logger().info(`Moving ${resizedImagePath} => ${targetPath}`);
2017-12-19 21:14:40 +01:00
await RNFS.copyFile(resizedImagePath, targetPath);
try {
await RNFS.unlink(resizedImagePath);
} catch (error) {
reg.logger().warn('Error when unlinking cached file: ', error);
}
} else {
await RNFS.copyFile(localFilePath, targetPath);
}
return true;
}
async attachFile(pickerResponse, fileType) {
if (!pickerResponse) {
reg.logger().warn('Got no response from picker');
2017-11-01 17:39:56 +00:00
return;
}
2017-08-01 23:40:14 +02:00
if (pickerResponse.error) {
reg.logger().warn('Got error from picker', pickerResponse.error);
2017-11-01 17:39:56 +00:00
return;
}
2017-08-02 17:47:25 +00:00
if (pickerResponse.didCancel) {
reg.logger().info('User cancelled picker');
return;
}
2017-08-02 17:47:25 +00:00
const localFilePath = Platform.select({
android: pickerResponse.uri,
ios: decodeURI(pickerResponse.uri),
});
2017-11-19 22:08:58 +00:00
let mimeType = pickerResponse.type;
if (!mimeType) {
const ext = fileExtension(localFilePath);
mimeType = mimeUtils.fromFileExtension(ext);
}
2017-08-02 17:47:25 +00:00
if (!mimeType && fileType === 'image') {
2017-11-20 19:18:49 +00:00
// Assume JPEG if we couldn't determine the file type. It seems to happen with the image picker
// when the file path is something like content://media/external/images/media/123456
// If the image is not a JPEG, something will throw an error below, but there's a good chance
// it will work.
reg.logger().info('Missing file type and could not detect it - assuming image/jpg');
mimeType = 'image/jpg';
2017-11-20 19:18:49 +00:00
}
2019-09-19 22:51:18 +01:00
reg.logger().info(`Got file: ${localFilePath}`);
reg.logger().info(`Got type: ${mimeType}`);
2017-08-01 23:40:14 +02:00
let resource = Resource.new();
resource.id = uuid.create();
2017-11-19 22:08:58 +00:00
resource.mime = mimeType;
resource.title = pickerResponse.fileName ? pickerResponse.fileName : '';
2018-10-13 10:32:44 +01:00
resource.file_extension = safeFileExtension(fileExtension(pickerResponse.fileName ? pickerResponse.fileName : localFilePath));
if (!resource.mime) resource.mime = 'application/octet-stream';
2017-08-01 23:40:14 +02:00
const targetPath = Resource.fullPath(resource);
2017-08-02 17:47:25 +00:00
2017-11-19 22:08:58 +00:00
try {
if (mimeType == 'image/jpeg' || mimeType == 'image/jpg' || mimeType == 'image/png') {
const done = await this.resizeImage(localFilePath, targetPath, pickerResponse.mime);
if (!done) return;
} else {
if (fileType === 'image') {
dialogs.error(this, _('Unsupported image type: %s', mimeType));
2017-11-19 22:08:58 +00:00
return;
} else {
await shim.fsDriver().copy(localFilePath, targetPath);
const stat = await shim.fsDriver().stat(targetPath);
if (stat.size >= 10000000) {
await shim.fsDriver().remove(targetPath);
throw new Error('Resources larger than 10 MB are not currently supported as they may crash the mobile applications. The issue is being investigated and will be fixed at a later time.');
}
2017-11-19 22:08:58 +00:00
}
2017-08-02 17:47:25 +00:00
}
2017-11-19 22:08:58 +00:00
} catch (error) {
reg.logger().warn('Could not attach file:', error);
await dialogs.error(this, error.message);
2017-11-19 22:08:58 +00:00
return;
2017-08-02 17:47:25 +00:00
}
2017-08-01 23:40:14 +02:00
const itDoes = await shim.fsDriver().waitTillExists(targetPath);
2019-09-19 22:51:18 +01:00
if (!itDoes) throw new Error(`Resource file was not created: ${targetPath}`);
const fileStat = await shim.fsDriver().stat(targetPath);
resource.size = fileStat.size;
resource = await Resource.save(resource, { isNew: true });
2017-08-01 23:40:14 +02:00
const resourceTag = Resource.markdownTag(resource);
2017-08-02 17:47:25 +00:00
const newNote = Object.assign({}, this.state.note);
2019-09-19 22:51:18 +01:00
newNote.body += `\n${resourceTag}`;
2017-08-02 17:47:25 +00:00
this.setState({ note: newNote });
this.refreshResource(resource, newNote.body);
this.scheduleSave();
}
async attachPhoto_onPress() {
const response = await this.showImagePicker({ mediaType: 'photo' });
await this.attachFile(response, 'image');
}
takePhoto_onPress() {
2018-10-13 10:32:44 +01:00
this.setState({ showCamera: true });
}
2018-10-13 10:32:44 +01:00
cameraView_onPhoto(data) {
this.attachFile(
{
uri: data.uri,
didCancel: false,
error: null,
type: 'image/jpg',
},
'image'
);
2018-10-13 10:32:44 +01:00
this.setState({ showCamera: false });
}
cameraView_onCancel() {
this.setState({ showCamera: false });
}
async attachFile_onPress() {
const response = await this.pickDocument();
await this.attachFile(response, 'all');
}
2017-07-30 21:51:18 +02:00
toggleIsTodo_onPress() {
shared.toggleIsTodo_onPress(this);
this.scheduleSave();
2017-07-17 21:22:05 +01:00
}
tags_onPress() {
if (!this.state.note || !this.state.note.id) return;
2018-03-17 23:00:01 +00:00
this.setState({ noteTagDialogShown: true });
}
async share_onPress() {
await Share.share({
2019-09-19 22:51:18 +01:00
message: `${this.state.note.title}\n\n${this.state.note.body}`,
title: this.state.note.title,
});
}
properties_onPress() {
this.props.dispatch({ type: 'SIDE_MENU_OPEN' });
}
2017-09-10 17:56:27 +01:00
setAlarm_onPress() {
this.setState({ alarmDialogShown: true });
}
async onAlarmDialogAccept(date) {
const newNote = Object.assign({}, this.state.note);
2017-09-10 17:56:27 +01:00
newNote.todo_due = date ? date.getTime() : 0;
await this.saveOneProperty('todo_due', date ? date.getTime() : 0);
this.setState({ alarmDialogShown: false });
2017-09-10 17:56:27 +01:00
}
onAlarmDialogReject() {
this.setState({ alarmDialogShown: false });
}
async showOnMap_onPress() {
if (!this.state.note.id) return;
const note = await Note.load(this.state.note.id);
try {
const url = Note.geolocationUrl(note);
Linking.openURL(url);
} catch (error) {
this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
await dialogs.error(this, error.message);
}
}
async showSource_onPress() {
if (!this.state.note.id) return;
const note = await Note.load(this.state.note.id);
try {
Linking.openURL(note.source_url);
} catch (error) {
await dialogs.error(this, error.message);
}
}
copyMarkdownLink_onPress() {
const note = this.state.note;
Clipboard.setString(Note.markdownTag(note));
}
sideMenuOptions() {
const note = this.state.note;
if (!note) return [];
const output = [];
const createdDateString = time.formatMsToLocal(note.user_created_time);
const updatedDateString = time.formatMsToLocal(note.user_updated_time);
output.push({ title: _('Created: %s', createdDateString) });
output.push({ title: _('Updated: %s', updatedDateString) });
output.push({ isDivider: true });
output.push({
title: _('View on map'),
onPress: () => {
this.showOnMap_onPress();
},
});
2020-03-13 23:57:34 +00:00
if (note.source_url) {
output.push({
title: _('Go to source URL'),
onPress: () => {
this.showSource_onPress();
},
});
2020-03-13 23:57:34 +00:00
}
return output;
}
2017-06-06 20:01:43 +00:00
menuOptions() {
2017-07-17 21:22:05 +01:00
const note = this.state.note;
2017-09-10 17:57:06 +01:00
const isTodo = note && !!note.is_todo;
const isSaved = note && note.id;
2017-07-17 21:22:05 +01:00
const cacheKey = md5([isTodo, isSaved].join('_'));
if (!this.menuOptionsCache_) this.menuOptionsCache_ = {};
if (this.menuOptionsCache_[cacheKey]) return this.menuOptionsCache_[cacheKey];
const output = [];
2017-09-10 17:57:06 +01:00
2017-11-20 19:01:19 +00:00
// The file attachement modules only work in Android >= 5 (Version 21)
// https://github.com/react-community/react-native-image-picker/issues/606
let canAttachPicture = true;
if (Platform.OS === 'android' && Platform.Version < 21) canAttachPicture = false;
if (canAttachPicture) {
output.push({
title: _('Attach...'),
onPress: async () => {
const buttonId = await dialogs.pop(this, _('Choose an option'), [{ text: _('Take photo'), id: 'takePhoto' }, { text: _('Attach photo'), id: 'attachPhoto' }, { text: _('Attach any file'), id: 'attachFile' }]);
if (buttonId === 'takePhoto') this.takePhoto_onPress();
if (buttonId === 'attachPhoto') this.attachPhoto_onPress();
if (buttonId === 'attachFile') this.attachFile_onPress();
},
});
2017-11-20 19:01:19 +00:00
}
2017-07-17 21:22:05 +01:00
if (isTodo) {
output.push({
title: _('Set alarm'),
onPress: () => {
this.setState({ alarmDialogShown: true });
},
});
}
2017-09-10 17:57:06 +01:00
output.push({
title: _('Share'),
onPress: () => {
this.share_onPress();
},
});
2020-03-13 23:57:34 +00:00
if (isSaved) {
output.push({
title: _('Tags'),
onPress: () => {
this.tags_onPress();
},
});
2020-03-13 23:57:34 +00:00
}
output.push({
title: isTodo ? _('Convert to note') : _('Convert to todo'),
onPress: () => {
this.toggleIsTodo_onPress();
},
});
2020-03-13 23:57:34 +00:00
if (isSaved) {
output.push({
title: _('Copy Markdown link'),
onPress: () => {
this.copyMarkdownLink_onPress();
},
});
2020-03-13 23:57:34 +00:00
}
output.push({
title: _('Properties'),
onPress: () => {
this.properties_onPress();
},
});
output.push({
title: _('Delete'),
onPress: () => {
this.deleteNote_onPress();
},
});
2017-09-10 17:57:06 +01:00
this.menuOptionsCache_ = {};
this.menuOptionsCache_[cacheKey] = output;
2017-09-10 17:57:06 +01:00
return output;
2017-06-04 16:01:52 +01:00
}
2017-07-16 17:06:05 +01:00
async todoCheckbox_change(checked) {
await this.saveOneProperty('todo_completed', checked ? time.unixMs() : 0);
2017-07-15 00:12:32 +01:00
}
titleTextInput_contentSizeChange(event) {
2017-11-19 15:19:36 +00:00
if (!this.enableMultilineTitle_) return;
const height = event.nativeEvent.contentSize.height;
this.setState({ titleTextInputHeight: height });
}
scheduleFocusUpdate() {
if (this.focusUpdateIID_) clearTimeout(this.focusUpdateIID_);
this.focusUpdateIID_ = setTimeout(() => {
this.focusUpdateIID_ = null;
this.focusUpdate();
}, 100);
}
2019-06-26 23:21:12 +01:00
focusUpdate() {
if (this.focusUpdateIID_) clearTimeout(this.focusUpdateIID_);
this.focusUpdateIID_ = null;
2019-06-26 23:21:12 +01:00
if (!this.state.note) return;
let fieldToFocus = this.state.note.is_todo ? 'title' : 'body';
2019-06-26 23:21:12 +01:00
if (this.state.mode === 'view') fieldToFocus = '';
Mobile: Add toolbar, list continuation and Markdown preview to editor (#2224) * The basic editor is working! No list continuation still though * List continuation is working! Now to delete when entering again and not typing on line + handle ordered lists * Supports checkboxes + attempted at setting font * Editor font works now; now need to fix the delete (look at past state) * Fix deletion problem * Add ordered list handler * Add comments * Extract insertListLine * End lists on enter for empty bullets * Add MarkdownView (renders badly though) * Save edited text from MarkdownEditor * Cleanup * Refactor react-native-markdown-editor/ * Rename react-native-markdown-editor/ => MarkdownEditor/ * Cleanup * Fix preview styles; still need to fix checkbox problem * Fix keyboard padding * Change name back to #body_changeText * Incorporate PR feedback from @laurent22 * wip: Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Remove log statement * Focus TextInput in MarkdownEditor from grandparent * Make eslint happy * Extract textInputRefName to shared variable * Remove accidental #setState * Cleanup * Cleanup * Run linter * Cleanup * Update button order * Improve styles for config descriptions * Allow descriptions to be added to BOOL type Setting configs * Add editorBeta Setting * Move FailSafe details to description text * Update descriptionText styles * Put the editor under the beta flag toggle * Incorporate PR feedback from @laurent22 * Refactor Markdown editor focusing * Cleanup * Reorder MarkdownEditor formats * Make applyListFormat behavior more intuitive * Add comment * Show MarkdownEditor with preview by default * Show preview by default, then hide on typing * Fix MarkdownEditor selection bug * Cleanup * Update Markdown button styles * Make Markdown button colors theme-conscious * Fix merge conflict resolution mistake * Fix broken import * Delete package-lock.json * Reset package-lock.json Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-25 03:50:45 -07:00
if (fieldToFocus === 'title' && this.refs.titleTextField) {
this.refs.titleTextField.focus();
}
if (fieldToFocus === 'body' && this.markdownEditorRef.current) {
if (this.markdownEditorRef.current) {
this.markdownEditorRef.current.focus();
}
}
2019-06-26 23:21:12 +01:00
}
async folderPickerOptions_valueChanged(itemValue) {
const note = this.state.note;
if (!note.id) {
await this.saveNoteButton_press(itemValue);
} else {
await Note.moveToFolder(note.id, itemValue);
}
note.parent_id = itemValue;
const folder = await Folder.load(note.parent_id);
this.setState({
lastSavedNote: Object.assign({}, note),
note: note,
folder: folder,
});
}
folderPickerOptions() {
const options = {
enabled: true,
selectedFolderId: this.state.folder ? this.state.folder.id : null,
onValueChange: this.folderPickerOptions_valueChanged,
};
if (this.folderPickerOptions_ && options.selectedFolderId === this.folderPickerOptions_.selectedFolderId) return this.folderPickerOptions_;
this.folderPickerOptions_ = options;
return this.folderPickerOptions_;
}
2017-05-12 20:23:54 +00:00
render() {
2017-07-24 22:52:30 +01:00
if (this.state.isLoading) {
return (
<View style={this.styles().screen}>
<ScreenHeader />
2017-07-24 22:52:30 +01:00
</View>
);
}
2017-08-01 18:53:50 +00:00
const theme = themeStyle(this.props.theme);
2017-05-24 20:51:50 +00:00
const note = this.state.note;
const isTodo = !!Number(note.is_todo);
2018-10-13 10:32:44 +01:00
if (this.state.showCamera) {
return <CameraView theme={this.props.theme} style={{ flex: 1 }} onPhoto={this.cameraView_onPhoto} onCancel={this.cameraView_onCancel} />;
2018-10-13 10:32:44 +01:00
}
2017-07-10 22:34:26 +01:00
let bodyComponent = null;
Mobile: Add toolbar, list continuation and Markdown preview to editor (#2224) * The basic editor is working! No list continuation still though * List continuation is working! Now to delete when entering again and not typing on line + handle ordered lists * Supports checkboxes + attempted at setting font * Editor font works now; now need to fix the delete (look at past state) * Fix deletion problem * Add ordered list handler * Add comments * Extract insertListLine * End lists on enter for empty bullets * Add MarkdownView (renders badly though) * Save edited text from MarkdownEditor * Cleanup * Refactor react-native-markdown-editor/ * Rename react-native-markdown-editor/ => MarkdownEditor/ * Cleanup * Fix preview styles; still need to fix checkbox problem * Fix keyboard padding * Change name back to #body_changeText * Incorporate PR feedback from @laurent22 * wip: Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Remove log statement * Focus TextInput in MarkdownEditor from grandparent * Make eslint happy * Extract textInputRefName to shared variable * Remove accidental #setState * Cleanup * Cleanup * Run linter * Cleanup * Update button order * Improve styles for config descriptions * Allow descriptions to be added to BOOL type Setting configs * Add editorBeta Setting * Move FailSafe details to description text * Update descriptionText styles * Put the editor under the beta flag toggle * Incorporate PR feedback from @laurent22 * Refactor Markdown editor focusing * Cleanup * Reorder MarkdownEditor formats * Make applyListFormat behavior more intuitive * Add comment * Show MarkdownEditor with preview by default * Show preview by default, then hide on typing * Fix MarkdownEditor selection bug * Cleanup * Update Markdown button styles * Make Markdown button colors theme-conscious * Fix merge conflict resolution mistake * Fix broken import * Delete package-lock.json * Reset package-lock.json Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-25 03:50:45 -07:00
if (this.state.mode == 'view' && !Setting.value('editor.beta')) {
const onCheckboxChange = newBody => {
this.saveOneProperty('body', newBody);
2017-07-30 21:51:18 +02:00
};
2017-07-17 22:34:08 +01:00
// Currently keyword highlighting is supported only when FTS is available.
let keywords = [];
if (this.props.searchQuery && !!this.props.ftsEnabled) {
const parsedQuery = SearchEngine.instance().parseQuery(this.props.searchQuery);
keywords = SearchEngine.instance().allParsedQueryTerms(parsedQuery);
}
// Note: as of 2018-12-29 it's important not to display the viewer if the note body is empty,
// to avoid the HACK_webviewLoadingState related bug.
bodyComponent =
!note || !note.body.trim() ? null : (
<NoteBodyViewer
onJoplinLinkClick={this.onJoplinLinkClick_}
ref="noteBodyViewer"
style={this.styles().noteBodyViewer}
webViewStyle={theme}
Mobile: Add toolbar, list continuation and Markdown preview to editor (#2224) * The basic editor is working! No list continuation still though * List continuation is working! Now to delete when entering again and not typing on line + handle ordered lists * Supports checkboxes + attempted at setting font * Editor font works now; now need to fix the delete (look at past state) * Fix deletion problem * Add ordered list handler * Add comments * Extract insertListLine * End lists on enter for empty bullets * Add MarkdownView (renders badly though) * Save edited text from MarkdownEditor * Cleanup * Refactor react-native-markdown-editor/ * Rename react-native-markdown-editor/ => MarkdownEditor/ * Cleanup * Fix preview styles; still need to fix checkbox problem * Fix keyboard padding * Change name back to #body_changeText * Incorporate PR feedback from @laurent22 * wip: Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Remove log statement * Focus TextInput in MarkdownEditor from grandparent * Make eslint happy * Extract textInputRefName to shared variable * Remove accidental #setState * Cleanup * Cleanup * Run linter * Cleanup * Update button order * Improve styles for config descriptions * Allow descriptions to be added to BOOL type Setting configs * Add editorBeta Setting * Move FailSafe details to description text * Update descriptionText styles * Put the editor under the beta flag toggle * Incorporate PR feedback from @laurent22 * Refactor Markdown editor focusing * Cleanup * Reorder MarkdownEditor formats * Make applyListFormat behavior more intuitive * Add comment * Show MarkdownEditor with preview by default * Show preview by default, then hide on typing * Fix MarkdownEditor selection bug * Cleanup * Update Markdown button styles * Make Markdown button colors theme-conscious * Fix merge conflict resolution mistake * Fix broken import * Delete package-lock.json * Reset package-lock.json Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-25 03:50:45 -07:00
// Extra bottom padding to make it possible to scroll past the
// action button (so that it doesn't overlap the text)
paddingBottom="150"
note={note}
noteResources={this.state.noteResources}
highlightedKeywords={keywords}
theme={this.props.theme}
noteHash={this.props.noteHash}
onCheckboxChange={newBody => {
onCheckboxChange(newBody);
}}
onMarkForDownload={this.onMarkForDownload}
onLoadEnd={() => {
setTimeout(() => {
this.setState({ HACK_webviewLoadingState: 1 });
setTimeout(() => {
this.setState({ HACK_webviewLoadingState: 0 });
}, 50);
}, 5);
}}
/>
);
2017-07-10 22:34:26 +01:00
} else {
2019-06-26 23:21:12 +01:00
// autoFocus={fieldToFocus === 'body'}
Mobile: Add toolbar, list continuation and Markdown preview to editor (#2224) * The basic editor is working! No list continuation still though * List continuation is working! Now to delete when entering again and not typing on line + handle ordered lists * Supports checkboxes + attempted at setting font * Editor font works now; now need to fix the delete (look at past state) * Fix deletion problem * Add ordered list handler * Add comments * Extract insertListLine * End lists on enter for empty bullets * Add MarkdownView (renders badly though) * Save edited text from MarkdownEditor * Cleanup * Refactor react-native-markdown-editor/ * Rename react-native-markdown-editor/ => MarkdownEditor/ * Cleanup * Fix preview styles; still need to fix checkbox problem * Fix keyboard padding * Change name back to #body_changeText * Incorporate PR feedback from @laurent22 * wip: Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Remove log statement * Focus TextInput in MarkdownEditor from grandparent * Make eslint happy * Extract textInputRefName to shared variable * Remove accidental #setState * Cleanup * Cleanup * Run linter * Cleanup * Update button order * Improve styles for config descriptions * Allow descriptions to be added to BOOL type Setting configs * Add editorBeta Setting * Move FailSafe details to description text * Update descriptionText styles * Put the editor under the beta flag toggle * Incorporate PR feedback from @laurent22 * Refactor Markdown editor focusing * Cleanup * Reorder MarkdownEditor formats * Make applyListFormat behavior more intuitive * Add comment * Show MarkdownEditor with preview by default * Show preview by default, then hide on typing * Fix MarkdownEditor selection bug * Cleanup * Update Markdown button styles * Make Markdown button colors theme-conscious * Fix merge conflict resolution mistake * Fix broken import * Delete package-lock.json * Reset package-lock.json Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-25 03:50:45 -07:00
// Currently keyword highlighting is supported only when FTS is available.
let keywords = [];
if (this.props.searchQuery && !!this.props.ftsEnabled) {
const parsedQuery = SearchEngine.instance().parseQuery(this.props.searchQuery);
keywords = SearchEngine.instance().allParsedQueryTerms(parsedQuery);
}
const onCheckboxChange = newBody => {
this.saveOneProperty('body', newBody);
};
bodyComponent = Setting.value('editor.beta')
// Note: blurOnSubmit is necessary to get multiline to work.
// See https://github.com/facebook/react-native/issues/12717#issuecomment-327001997
? <MarkdownEditor
ref={this.markdownEditorRef} // For focusing the Markdown editor
editorFont={editorFont(this.props.editorFont)}
style={this.styles().bodyTextInput}
previewStyles={this.styles().noteBodyViewer}
value={note.body}
borderColor={this.styles().markdownButtons.borderColor}
markdownButtonsColor={this.styles().markdownButtons.color}
saveText={text => this.body_changeText(text)}
blurOnSubmit={false}
selectionColor={theme.textSelectionColor}
placeholder={_('Add body')}
placeholderTextColor={theme.colorFaded}
noteBodyViewer={{
onJoplinLinkClick: this.onJoplinLinkClick_,
ref: 'noteBodyViewer',
style: {
...this.styles().noteBodyViewer,
...this.styles().noteBodyViewerPreview,
},
webViewStyle: theme,
note: note,
noteResources: this.state.noteResources,
highlightedKeywords: keywords,
theme: this.props.theme,
noteHash: this.props.noteHash,
onCheckboxChange: newBody => {
onCheckboxChange(newBody);
},
onMarkForDownload: this.onMarkForDownload,
onLoadEnd: () => {
setTimeout(() => {
this.setState({ HACK_webviewLoadingState: 1 });
setTimeout(() => {
this.setState({ HACK_webviewLoadingState: 0 });
}, 50);
}, 5);
},
}}
/>
: (
<ScrollView persistentScrollbar>
<TextInput autoCapitalize="sentences" style={this.styles().bodyTextInput} ref="noteBodyTextField" multiline={true} value={note.body} onChangeText={text => this.body_changeText(text)} blurOnSubmit={false} selectionColor={theme.textSelectionColor} placeholder={_('Add body')} placeholderTextColor={theme.colorFaded} />
</ScrollView>
);
2017-07-10 22:34:26 +01:00
}
2017-07-14 00:35:37 +01:00
const renderActionButton = () => {
const buttons = [];
2017-07-14 00:35:37 +01:00
buttons.push({
title: _('Edit'),
icon: 'md-create',
2017-07-14 00:35:37 +01:00
onPress: () => {
this.setState({ mode: 'edit' });
2019-06-26 23:21:12 +01:00
this.doFocusUpdate_ = true;
2017-07-14 00:35:37 +01:00
},
});
if (this.state.mode == 'edit') return null;
2017-07-15 00:12:32 +01:00
return <ActionButton multiStates={true} buttons={buttons} buttonIndex={0} />;
};
2017-07-14 00:35:37 +01:00
const actionButtonComp = renderActionButton();
const showSaveButton = this.state.mode == 'edit' || this.isModified() || this.saveButtonHasBeenShown_;
const saveButtonDisabled = !this.isModified();
2017-07-16 22:17:22 +01:00
if (showSaveButton) this.saveButtonHasBeenShown_ = true;
2017-08-01 18:53:50 +00:00
const titleContainerStyle = isTodo ? this.styles().titleContainerTodo : this.styles().titleContainer;
const dueDate = Note.dueDateObject(note);
2017-09-10 17:56:27 +01:00
const titleComp = (
<View style={titleContainerStyle}>
{isTodo && <Checkbox style={this.styles().checkbox} checked={!!Number(note.todo_completed)} onChange={this.todoCheckbox_change} />}
<TextInput onContentSizeChange={this.titleTextInput_contentSizeChange} multiline={this.enableMultilineTitle_} ref="titleTextField" underlineColorAndroid="#ffffff00" autoCapitalize="sentences" style={this.styles().titleTextInput} value={note.title} onChangeText={this.title_changeText} selectionColor={theme.textSelectionColor} placeholder={_('Add title')} placeholderTextColor={theme.colorFaded} />
</View>
);
const noteTagDialog = !this.state.noteTagDialogShown ? null : <NoteTagsDialog onCloseRequested={this.noteTagDialog_closeRequested} />;
2018-03-17 23:00:01 +00:00
2017-05-12 20:23:54 +00:00
return (
<View style={this.rootStyle(this.props.theme).root}>
<ScreenHeader folderPickerOptions={this.folderPickerOptions()} menuOptions={this.menuOptions()} showSaveButton={showSaveButton} saveButtonDisabled={saveButtonDisabled} onSaveButtonPress={this.saveNoteButton_press} showSideMenuButton={false} showSearchButton={false} />
{titleComp}
{bodyComponent}
Mobile: Add toolbar, list continuation and Markdown preview to editor (#2224) * The basic editor is working! No list continuation still though * List continuation is working! Now to delete when entering again and not typing on line + handle ordered lists * Supports checkboxes + attempted at setting font * Editor font works now; now need to fix the delete (look at past state) * Fix deletion problem * Add ordered list handler * Add comments * Extract insertListLine * End lists on enter for empty bullets * Add MarkdownView (renders badly though) * Save edited text from MarkdownEditor * Cleanup * Refactor react-native-markdown-editor/ * Rename react-native-markdown-editor/ => MarkdownEditor/ * Cleanup * Fix preview styles; still need to fix checkbox problem * Fix keyboard padding * Change name back to #body_changeText * Incorporate PR feedback from @laurent22 * wip: Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Move MarkdownEditor/ from ReactNativeClient/lib/ to ReactNativeClient/ * Remove log statement * Focus TextInput in MarkdownEditor from grandparent * Make eslint happy * Extract textInputRefName to shared variable * Remove accidental #setState * Cleanup * Cleanup * Run linter * Cleanup * Update button order * Improve styles for config descriptions * Allow descriptions to be added to BOOL type Setting configs * Add editorBeta Setting * Move FailSafe details to description text * Update descriptionText styles * Put the editor under the beta flag toggle * Incorporate PR feedback from @laurent22 * Refactor Markdown editor focusing * Cleanup * Reorder MarkdownEditor formats * Make applyListFormat behavior more intuitive * Add comment * Show MarkdownEditor with preview by default * Show preview by default, then hide on typing * Fix MarkdownEditor selection bug * Cleanup * Update Markdown button styles * Make Markdown button colors theme-conscious * Fix merge conflict resolution mistake * Fix broken import * Delete package-lock.json * Reset package-lock.json Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2020-03-25 03:50:45 -07:00
{!Setting.value('editor.beta') && actionButtonComp}
<SelectDateTimeDialog shown={this.state.alarmDialogShown} date={dueDate} onAccept={this.onAlarmDialogAccept} onReject={this.onAlarmDialogReject} />
<DialogBox
ref={dialogbox => {
this.dialogbox = dialogbox;
}}
/>
{noteTagDialog}
</View>
2017-05-12 20:23:54 +00:00
);
}
}
const NoteScreen = connect(state => {
return {
noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
noteHash: state.selectedNoteHash,
folderId: state.selectedFolderId,
itemType: state.selectedItemType,
folders: state.folders,
searchQuery: state.searchQuery,
theme: state.settings.theme,
editorFont: [state.settings['style.editor.fontFamily']],
ftsEnabled: state.settings['db.ftsEnabled'],
sharedData: state.sharedData,
showSideMenu: state.showSideMenu,
provisionalNoteIds: state.provisionalNoteIds,
};
})(NoteScreenComponent);
module.exports = { NoteScreen };