From 4414469ab3a6014972b7d9c7c1a89e69a0c847dc Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 30 Dec 2018 17:21:39 -0600 Subject: [PATCH] Editor selection --- src/app.js | 4 +- src/containers/NoteCell.js | 69 ++++++++++-------------- src/lib/componentManager.js | 27 ++++++++-- src/lib/itemActionManager.js | 2 + src/screens/Compose.js | 41 +++++++++----- src/screens/NoteOptions.js | 53 ------------------ src/screens/SideMenu/NoteSideMenu.js | 72 ++++++++++++++++++------- src/screens/SideMenu/SideMenuCell.js | 2 +- src/screens/SideMenu/SideMenuManager.js | 1 + src/style/ActionSheetWrapper.js | 54 +++++++++++++++++++ 10 files changed, 196 insertions(+), 129 deletions(-) create mode 100644 src/style/ActionSheetWrapper.js diff --git a/src/app.js b/src/app.js index 9d73d0b5..e6054d1d 100644 --- a/src/app.js +++ b/src/app.js @@ -159,6 +159,8 @@ export default class App extends Component { return null; } - return ; + return ( + + ) } } diff --git a/src/containers/NoteCell.js b/src/containers/NoteCell.js index 001df628..ce889b99 100644 --- a/src/containers/NoteCell.js +++ b/src/containers/NoteCell.js @@ -3,6 +3,7 @@ import { StyleSheet, View, Text, TouchableWithoutFeedback } from 'react-native'; import Icon from 'react-native-vector-icons/Ionicons'; import StyleKit from "../style/StyleKit" import ActionSheet from 'react-native-actionsheet' +import ActionSheetWrapper from "@Style/ActionSheetWrapper" import ItemActionManager from '../lib/itemActionManager' import ThemedPureComponent from "@Components/ThemedPureComponent"; @@ -47,41 +48,37 @@ export default class NoteCell extends ThemedPureComponent { } } - static ActionSheetCancelIndex = 0; - static ActionSheetDestructiveIndex = 4; - - actionSheetActions() { - var pinAction = this.props.item.pinned ? "Unpin" : "Pin"; - let pinEvent = pinAction == "Pin" ? ItemActionManager.PinEvent : ItemActionManager.UnpinEvent; - - var archiveOption = this.props.item.archived ? "Unarchive" : "Archive"; - let archiveEvent = archiveOption == "Archive" ? ItemActionManager.ArchiveEvent : ItemActionManager.UnarchiveEvent; - - return [ - ['Cancel', ""], - [pinAction, pinEvent], - [archiveOption, archiveEvent], - ['Share', ItemActionManager.ShareEvent], - ['Delete', ItemActionManager.DeleteEvent] - ]; - } - showActionSheet = () => { - this.actionSheet.show(); - } - - handleActionSheetPress = (index) => { - if(index == 0) { - return; + let callbackForOption = (option) => { + ItemActionManager.handleEvent(option.key, this.props.item, () => { + this.forceUpdate(); + }, () => { + // afterConfirmCallback + // We want to show "Deleting.." on top of note cell after the user confirms the dialogue + this.forceUpdate(); + }); } - ItemActionManager.handleEvent(this.actionSheetActions()[index][1], this.props.item, () => { - this.forceUpdate(); - }, () => { - // afterConfirmCallback - // We want to show "Deleting.." on top of note cell after the user confirms the dialogue - this.forceUpdate(); + var pinLabel = this.props.item.pinned ? "Unpin" : "Pin"; + let pinEvent = pinLabel == "Pin" ? ItemActionManager.PinEvent : ItemActionManager.UnpinEvent; + + var archiveLabel = this.props.item.archived ? "Unarchive" : "Archive"; + let archiveEvent = archiveLabel == "Archive" ? ItemActionManager.ArchiveEvent : ItemActionManager.UnarchiveEvent; + + let sheet = new ActionSheetWrapper({ + title: this.props.item.safeTitle(), + options: [ + ActionSheetWrapper.BuildOption({text: pinLabel, key: pinEvent, callback: callbackForOption}), + ActionSheetWrapper.BuildOption({text: archiveLabel, key: archiveEvent, callback: callbackForOption}), + ActionSheetWrapper.BuildOption({text: "Share", key: ItemActionManager.ShareEvent, callback: callbackForOption}), + ActionSheetWrapper.BuildOption({text: "Delete", key: ItemActionManager.DeleteEvent, destructive: true, callback: callbackForOption}), + ], onCancel: () => { + this.setState({actionSheet: null}); + } }); + + this.setState({actionSheet: sheet.actionSheetElement()}); + sheet.show(); } render() { @@ -150,15 +147,7 @@ export default class NoteCell extends ThemedPureComponent { } - this.actionSheet = o} - title={note.safeTitle()} - options={this.actionSheetActions().map((action) => {return action[0]})} - cancelButtonIndex={NoteCell.ActionSheetCancelIndex} - destructiveButtonIndex={NoteCell.ActionSheetDestructiveIndex} - onPress={this.handleActionSheetPress} - {...StyleKit.actionSheetStyles()} - /> + {this.state.actionSheet && this.state.actionSheet} ) diff --git a/src/lib/componentManager.js b/src/lib/componentManager.js index e2e56605..03460570 100644 --- a/src/lib/componentManager.js +++ b/src/lib/componentManager.js @@ -366,6 +366,14 @@ export default class ComponentManager { this.sendMessageToComponent(component, {action: "themes", data: data}) } + getEditors() { + return this.componentsForArea("editor-editor"); + } + + getDefaultEditor() { + return this.getEditors().filter((e) => {return e.content.isMobileDefault})[0]; + } + editorForNote(note) { let editors = ModelManager.get().validItemsForContentType("SN|Component").filter(function(component){ return component.area == "editor-editor"; @@ -376,6 +384,17 @@ export default class ComponentManager { return editor; } } + + // No editor found for note. Use default editor, if note does not prefer system editor + if(!note.content.mobilePrefersPlainEditor) { + return editors.filter((e) => {return e.content.isMobileDefault})[0]; + } + } + + setEditorAsMobileDefault(editor, isDefault) { + editor.content.isMobileDefault = isDefault; + editor.setDirty(true); + Sync.get().sync(); } associateEditorWithNote(editor, note) { @@ -392,8 +411,8 @@ export default class ComponentManager { } if(editor) { - if(note.getAppDataItem("prefersPlainEditor") == true) { - note.setAppDataItem("prefersPlainEditor", false); + if(note.content.mobilePrefersPlainEditor == true) { + note.content.mobilePrefersPlainEditor = false; note.setDirty(true); } @@ -406,8 +425,8 @@ export default class ComponentManager { editor.setDirty(true); } else { // Note prefers plain editor - if(!note.getAppDataItem("prefersPlainEditor")) { - note.setAppDataItem("prefersPlainEditor", true); + if(!note.content.mobilePrefersPlainEditor) { + note.content.mobilePrefersPlainEditor = true; note.setDirty(true); } } diff --git a/src/lib/itemActionManager.js b/src/lib/itemActionManager.js index cac55b01..7d81c0e1 100644 --- a/src/lib/itemActionManager.js +++ b/src/lib/itemActionManager.js @@ -23,6 +23,8 @@ export default class ItemActionManager { static handleEvent(event, item, callback, afterConfirmCallback) { + console.log("Handling event", event); + if(event == this.DeleteEvent) { var title = `Delete ${item.displayName}`; var message = `Are you sure you want to delete this ${item.displayName.toLowerCase()}?`; diff --git a/src/screens/Compose.js b/src/screens/Compose.js index f354fced..3709293d 100644 --- a/src/screens/Compose.js +++ b/src/screens/Compose.js @@ -50,11 +50,15 @@ export default class Compose extends Abstract { if(noteId) { note = ModelManager.get().findItem(noteId);} if(!note) { note = ModelManager.get().createItem({content_type: "Note", dummy: true, text: ""}); - // We needed to add the item originally for default editors to work, but default editors was removed - // So the only way to select an editor is to make a change to the note, which will add it. - // The problem with adding it here is that if you open Compose and close it without making changes, it will save an empty note. - // ModelManager.get().addItem(note); note.dummy = true; + // Editors need a valid note with uuid and modelmanager mapped in order to interact with it + // Note that this can create dummy notes that aren't deleted automatically. + // Also useful to keep right menu enabled at all times. If the note has a uuid and is a dummy, + // it will be removed locally on blur + note.initUUID().then(() => { + ModelManager.get().addItem(note); + this.forceUpdate(); + }) } this.note = note; @@ -99,8 +103,7 @@ export default class Compose extends Abstract { iconName: "ios-menu-outline", onPress: () => { this.props.navigation.openRightDrawer(); - }, - disabled: !this.note.uuid + } } }) } @@ -117,10 +120,6 @@ export default class Compose extends Abstract { componentWillFocus() { super.componentWillFocus(); - if(this.needsEditorReload) { - this.forceUpdate(); - this.needsEditorReload = false; - } if(this.note.dirty) { // We want the "Saving..." / "All changes saved..." subtitle to be visible to the user, so we delay @@ -149,8 +148,17 @@ export default class Compose extends Abstract { } SideMenuManager.get().setHandlerForRightSideMenu({ + getCurrentNote: () => { + return this.note + }, onEditorSelect: (editor) => { - this.needsEditorReload = true; + if(editor) { + ComponentManager.get().associateEditorWithNote(editor, this.note); + } else { + ComponentManager.get().clearEditorForNote(this.note); + } + this.forceUpdate(); + this.props.navigation.closeRightDrawer(); }, onTagSelect: (tag) => { let selectedTags = this.note.tags; @@ -172,6 +180,14 @@ export default class Compose extends Abstract { }) } + componentWillBlur() { + super.componentWillBlur(); + if(this.note.uuid && this.note.dummy) { + // A dummy note created to work with default external editor. Safe to delete. + ModelManager.get().removeItemLocally(this.note); + } + } + componentDidBlur() { super.componentDidBlur(); @@ -312,7 +328,8 @@ export default class Compose extends Abstract { var noteEditor = ComponentManager.get().editorForNote(this.note); let windowWidth = this.state.windowWidth || Dimensions.get('window').width; - var shouldDisplayEditor = noteEditor != null; + // If new note with default editor, note.uuid may not be ready + var shouldDisplayEditor = noteEditor != null && this.note.uuid; return ( diff --git a/src/screens/NoteOptions.js b/src/screens/NoteOptions.js index 0e675b19..85abc22c 100644 --- a/src/screens/NoteOptions.js +++ b/src/screens/NoteOptions.js @@ -173,32 +173,11 @@ export default class NoteOptions extends Abstract { }) } - onEditorSelect = (editor) => { - if(editor) { - ComponentManager.get().associateEditorWithNote(editor, this.note); - } else { - ComponentManager.get().clearEditorForNote(this.note); - } - - this.getProp("onEditorSelect") && this.getProp("onEditorSelect")(editor); - this.dismiss(); - } - - getEditors() { - return ModelManager.get().validItemsForContentType("SN|Component").filter((component) => { - return component.area == "editor-editor"; - }) - } - clearTags = (close) => { this.setSelectedTags([]); if(close) { this.dismiss(); } } - openExternalEditorsLink() { - Linking.openURL("https://standardnotes.org/extensions"); - } - render() { var viewStyles = [StyleKit.styles().container]; @@ -221,9 +200,6 @@ export default class NoteOptions extends Abstract { var lockOption = this.note.locked ? "Unlock" : "Lock"; let lockEvent = lockOption == "Lock" ? ItemActionManager.LockEvent : ItemActionManager.UnlockEvent; - let editors = this.getEditors(); - let selectedEditor = ComponentManager.get().editorForNote(this.note); - return ( @@ -267,35 +243,6 @@ export default class NoteOptions extends Abstract { /> - - {this.onEditorSelect(null)}} - /> - {editors.map((editor, i) => { - return ( - {this.onEditorSelect(editor)}} - text={editor.name} - key={editor.uuid} - first={i == 0} - selected={() => {return editor == selectedEditor}} - buttonCell={true} - /> - ) - })} - - {editors.length == 0 && - {this.openExternalEditorsLink()}} - text={"Get Editors →"} - first={true} - buttonCell={true} - /> - } - - { - this.handler.onEditorSelect(editor) + this.handler.onEditorSelect(editor); + this.forceUpdate(); } onTagSelect = (tag) => { @@ -44,32 +46,65 @@ export default class NoteSideMenu extends Abstract { this.forceUpdate(); } + get note() { + return this.handler.getCurrentNote(); + } - /* - Render - */ + onEditorLongPress = (editor) => { + let currentDefaultEDitor = ComponentManager.get().getDefaultEditor(); + let isDefault = false; + if(!editor) { + // System editor + if(currentDefaultEDitor) { + isDefault = false; + } + } else { + isDefault = editor.content.isMobileDefault; + } - iconDescriptorForEditor = (editor) => { - let desc = { - type: "circle", - side: "right", - backgroundColor: "red", - borderColor: "red" - }; + let action = isDefault ? "Remove as Default Editor" : "Set as Default Editor"; + let sheet = new ActionSheetWrapper({ + title: editor && editor.name, + options: [ + ActionSheetWrapper.BuildOption({text: action, callback: () => { + if(!editor) { + // Default to plain + ComponentManager.get().setEditorAsMobileDefault(currentDefaultEDitor, false); + } else { + ComponentManager.get().setEditorAsMobileDefault(editor, !isDefault); + } + }}), + ], onCancel: () => { + this.setState({actionSheet: null}); + } + }); - return desc; + this.setState({actionSheet: sheet.actionSheetElement()}); + sheet.show(); } + /* + Render + */ + buildOptionsForEditors() { - let editors = []; // TODO - let options = []; - for(var editor of editors) { + let editors = ComponentManager.get().getEditors(); + let selectedEditor = ComponentManager.get().editorForNote(this.note); + let options = [{ + text: "Plain Editor", + key: "plain-editor", + selected: !selectedEditor, + onSelect: () => {this.onEditorSelect(null)}, + onLongPress: () => {this.onEditorLongPress(null)} + }]; + + for(let editor of editors) { let option = SideMenuSection.BuildOption({ text: editor.name, key: editor.uuid || editor.name, - iconDesc: this.iconDescriptorForEditor(editor), - selected: editor.active, + selected: editor == selectedEditor, onSelect: () => {this.onEditorSelect(editor)}, + onLongPress: () => {this.onEditorLongPress(editor)} }) options.push(option); @@ -115,6 +150,7 @@ export default class NoteSideMenu extends Abstract { + {this.state.actionSheet && this.state.actionSheet} ); } diff --git a/src/screens/SideMenu/SideMenuCell.js b/src/screens/SideMenu/SideMenuCell.js index 9e26e977..5af93865 100644 --- a/src/screens/SideMenu/SideMenuCell.js +++ b/src/screens/SideMenu/SideMenuCell.js @@ -65,7 +65,7 @@ export default class SideMenuCell extends ThemedComponent { } render() { - let iconSide = this.props.iconDesc.side ? this.props.iconDesc.side : "left"; + let iconSide = (this.props.iconDesc && this.props.iconDesc.side) ? this.props.iconDesc.side : "left"; return ( { + return candidate.destructive; + })) + this.cancelIndex = this.options.length - 1; + this.title = title; + } + + show() { + this.actionSheet.show(); + } + + handleActionSheetPress = (index) => { + console.log("handleActionSheetPress", index); + + let option = this.options[index]; + option.callback && option.callback(option); + } + + actionSheetElement() { + return ( + this.actionSheet = o} + title={this.title} + options={this.options.map((option) => {return option.text})} + cancelButtonIndex={this.cancelIndex} + destructiveButtonIndex={this.destructiveIndex} + onPress={this.handleActionSheetPress} + {...StyleKit.actionSheetStyles()} + /> + ) + } + +}