feat: toggle move to trash to delete permanently on alt key down
This commit is contained in:
@@ -213,7 +213,7 @@ DEPENDENCIES
|
|||||||
dotenv-rails
|
dotenv-rails
|
||||||
haml
|
haml
|
||||||
lograge (~> 0.11.2)
|
lograge (~> 0.11.2)
|
||||||
newrelic_rpm
|
newrelic_rpm (~> 7.0)
|
||||||
non-stupid-digest-assets
|
non-stupid-digest-assets
|
||||||
puma
|
puma
|
||||||
rack-cors
|
rack-cors
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import { toDirective, useCloseOnBlur, useCloseOnClickOutside } from './utils';
|
|||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { NotesOptions } from './NotesOptions';
|
import { NotesOptions } from './NotesOptions';
|
||||||
import { useCallback, useEffect, useRef } from 'preact/hooks';
|
import { useCallback, useEffect, useRef } from 'preact/hooks';
|
||||||
|
import { WebApplication } from '@/ui_models/application';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
application: WebApplication;
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
};
|
};
|
||||||
|
|
||||||
const NotesContextMenu = observer(({ appState }: Props) => {
|
const NotesContextMenu = observer(({ application, appState }: Props) => {
|
||||||
const {
|
const {
|
||||||
contextMenuOpen,
|
contextMenuOpen,
|
||||||
contextMenuPosition,
|
contextMenuPosition,
|
||||||
@@ -46,7 +48,11 @@ const NotesContextMenu = observer(({ appState }: Props) => {
|
|||||||
maxHeight: contextMenuMaxHeight,
|
maxHeight: contextMenuMaxHeight,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<NotesOptions appState={appState} closeOnBlur={closeOnBlur} />
|
<NotesOptions
|
||||||
|
application={application}
|
||||||
|
appState={appState}
|
||||||
|
closeOnBlur={closeOnBlur}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,15 +9,33 @@ import {
|
|||||||
DisclosurePanel,
|
DisclosurePanel,
|
||||||
} from '@reach/disclosure';
|
} from '@reach/disclosure';
|
||||||
import { SNNote } from '@standardnotes/snjs/dist/@types';
|
import { SNNote } from '@standardnotes/snjs/dist/@types';
|
||||||
|
import { WebApplication } from '@/ui_models/application';
|
||||||
|
import { KeyboardModifier } from '@/services/ioService';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
application: WebApplication;
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void;
|
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void;
|
||||||
onSubmenuChange?: (submenuOpen: boolean) => void;
|
onSubmenuChange?: (submenuOpen: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type DeletePermanentlyButtonProps = {
|
||||||
|
closeOnBlur: Props["closeOnBlur"];
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeletePermanentlyButton = ({
|
||||||
|
closeOnBlur,
|
||||||
|
onClick,
|
||||||
|
}: DeletePermanentlyButtonProps) => (
|
||||||
|
<button onBlur={closeOnBlur} className="sn-dropdown-item" onClick={onClick}>
|
||||||
|
<Icon type="close" className="color-danger mr-2" />
|
||||||
|
<span className="color-danger">Delete permanently</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
export const NotesOptions = observer(
|
export const NotesOptions = observer(
|
||||||
({ appState, closeOnBlur, onSubmenuChange }: Props) => {
|
({ application, appState, closeOnBlur, onSubmenuChange }: Props) => {
|
||||||
const [tagsMenuOpen, setTagsMenuOpen] = useState(false);
|
const [tagsMenuOpen, setTagsMenuOpen] = useState(false);
|
||||||
const [tagsMenuPosition, setTagsMenuPosition] = useState<{
|
const [tagsMenuPosition, setTagsMenuPosition] = useState<{
|
||||||
top: number;
|
top: number;
|
||||||
@@ -29,6 +47,7 @@ export const NotesOptions = observer(
|
|||||||
});
|
});
|
||||||
const [tagsMenuMaxHeight, setTagsMenuMaxHeight] =
|
const [tagsMenuMaxHeight, setTagsMenuMaxHeight] =
|
||||||
useState<number | 'auto'>('auto');
|
useState<number | 'auto'>('auto');
|
||||||
|
const [altKeyDown, setAltKeyDown] = useState(false);
|
||||||
|
|
||||||
const toggleOn = (condition: (note: SNNote) => boolean) => {
|
const toggleOn = (condition: (note: SNNote) => boolean) => {
|
||||||
const notesMatchingAttribute = notes.filter(condition);
|
const notesMatchingAttribute = notes.filter(condition);
|
||||||
@@ -59,6 +78,22 @@ export const NotesOptions = observer(
|
|||||||
}
|
}
|
||||||
}, [tagsMenuOpen, onSubmenuChange]);
|
}, [tagsMenuOpen, onSubmenuChange]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const removeAltKeyObserver = application.io.addKeyObserver({
|
||||||
|
modifiers: [KeyboardModifier.Alt],
|
||||||
|
onKeyDown: () => {
|
||||||
|
setAltKeyDown(true);
|
||||||
|
},
|
||||||
|
onKeyUp: () => {
|
||||||
|
setAltKeyDown(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
removeAltKeyObserver();
|
||||||
|
};
|
||||||
|
}, [application]);
|
||||||
|
|
||||||
const openTagsMenu = () => {
|
const openTagsMenu = () => {
|
||||||
const defaultFontSize = window.getComputedStyle(
|
const defaultFontSize = window.getComputedStyle(
|
||||||
document.documentElement
|
document.documentElement
|
||||||
@@ -235,18 +270,26 @@ export const NotesOptions = observer(
|
|||||||
Unarchive
|
Unarchive
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{notTrashed && (
|
{notTrashed &&
|
||||||
<button
|
(altKeyDown ? (
|
||||||
onBlur={closeOnBlur}
|
<DeletePermanentlyButton
|
||||||
className="sn-dropdown-item"
|
closeOnBlur={closeOnBlur}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await appState.notes.setTrashSelectedNotes(true);
|
await appState.notes.deleteNotesPermanently();
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<Icon type="trash" className={iconClass} />
|
) : (
|
||||||
Move to Trash
|
<button
|
||||||
</button>
|
onBlur={closeOnBlur}
|
||||||
)}
|
className="sn-dropdown-item"
|
||||||
|
onClick={async () => {
|
||||||
|
await appState.notes.setTrashSelectedNotes(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="trash" className={iconClass} />
|
||||||
|
Move to Trash
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
{trashed && (
|
{trashed && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
@@ -259,16 +302,12 @@ export const NotesOptions = observer(
|
|||||||
<Icon type="restore" className={iconClass} />
|
<Icon type="restore" className={iconClass} />
|
||||||
Restore
|
Restore
|
||||||
</button>
|
</button>
|
||||||
<button
|
<DeletePermanentlyButton
|
||||||
onBlur={closeOnBlur}
|
closeOnBlur={closeOnBlur}
|
||||||
className="sn-dropdown-item"
|
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await appState.notes.deleteNotesPermanently();
|
await appState.notes.deleteNotesPermanently();
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<Icon type="close" className="color-danger mr-2" />
|
|
||||||
<span className="color-danger">Delete permanently</span>
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
onBlur={closeOnBlur}
|
onBlur={closeOnBlur}
|
||||||
className="sn-dropdown-item"
|
className="sn-dropdown-item"
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ import {
|
|||||||
import { useRef, useState } from 'preact/hooks';
|
import { useRef, useState } from 'preact/hooks';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { NotesOptions } from './NotesOptions';
|
import { NotesOptions } from './NotesOptions';
|
||||||
|
import { WebApplication } from '@/ui_models/application';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
application: WebApplication;
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NotesOptionsPanel = observer(({ appState }: Props) => {
|
export const NotesOptionsPanel = observer(({ application, appState }: Props) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [position, setPosition] = useState({
|
const [position, setPosition] = useState({
|
||||||
top: 0,
|
top: 0,
|
||||||
@@ -76,6 +78,7 @@ export const NotesOptionsPanel = observer(({ appState }: Props) => {
|
|||||||
>
|
>
|
||||||
{open && (
|
{open && (
|
||||||
<NotesOptions
|
<NotesOptions
|
||||||
|
application={application}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
closeOnBlur={closeOnBlur}
|
closeOnBlur={closeOnBlur}
|
||||||
onSubmenuChange={onSubmenuChange}
|
onSubmenuChange={onSubmenuChange}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
on-dismiss="self.removeChallenge(challenge)"
|
on-dismiss="self.removeChallenge(challenge)"
|
||||||
)
|
)
|
||||||
notes-context-menu(
|
notes-context-menu(
|
||||||
|
application='self.application'
|
||||||
app-state='self.appState'
|
app-state='self.appState'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
) {{self.state.noteStatus.message}}
|
) {{self.state.noteStatus.message}}
|
||||||
.desc(ng-show='self.state.noteStatus.desc') {{self.state.noteStatus.desc}}
|
.desc(ng-show='self.state.noteStatus.desc') {{self.state.noteStatus.desc}}
|
||||||
notes-options-panel(
|
notes-options-panel(
|
||||||
|
application='self.application',
|
||||||
app-state='self.appState',
|
app-state='self.appState',
|
||||||
ng-if='self.appState.notes.selectedNotesCount > 0'
|
ng-if='self.appState.notes.selectedNotesCount > 0'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ type EditorState = {
|
|||||||
showOptionsMenu: boolean;
|
showOptionsMenu: boolean;
|
||||||
showEditorMenu: boolean;
|
showEditorMenu: boolean;
|
||||||
showHistoryMenu: boolean;
|
showHistoryMenu: boolean;
|
||||||
altKeyDown: boolean;
|
|
||||||
spellcheck: boolean;
|
spellcheck: boolean;
|
||||||
/**
|
/**
|
||||||
* Setting to false then true will allow the current editor component-view to be destroyed
|
* Setting to false then true will allow the current editor component-view to be destroyed
|
||||||
@@ -217,7 +216,6 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
showOptionsMenu: false,
|
showOptionsMenu: false,
|
||||||
showEditorMenu: false,
|
showEditorMenu: false,
|
||||||
showHistoryMenu: false,
|
showHistoryMenu: false,
|
||||||
altKeyDown: false,
|
|
||||||
noteStatus: undefined,
|
noteStatus: undefined,
|
||||||
editorUnloading: false,
|
editorUnloading: false,
|
||||||
textareaUnloading: false,
|
textareaUnloading: false,
|
||||||
@@ -277,7 +275,6 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
showOptionsMenu: false,
|
showOptionsMenu: false,
|
||||||
showEditorMenu: false,
|
showEditorMenu: false,
|
||||||
showHistoryMenu: false,
|
showHistoryMenu: false,
|
||||||
altKeyDown: false,
|
|
||||||
noteStatus: undefined,
|
noteStatus: undefined,
|
||||||
});
|
});
|
||||||
this.editorValues.title = note.title;
|
this.editorValues.title = note.title;
|
||||||
@@ -843,22 +840,6 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerKeyboardShortcuts() {
|
registerKeyboardShortcuts() {
|
||||||
this.removeAltKeyObserver = this.application
|
|
||||||
.io
|
|
||||||
.addKeyObserver({
|
|
||||||
modifiers: [KeyboardModifier.Alt],
|
|
||||||
onKeyDown: () => {
|
|
||||||
this.setState({
|
|
||||||
altKeyDown: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onKeyUp: () => {
|
|
||||||
this.setState({
|
|
||||||
altKeyDown: false,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.removeTrashKeyObserver = this.application
|
this.removeTrashKeyObserver = this.application
|
||||||
.io
|
.io
|
||||||
.addKeyObserver({
|
.addKeyObserver({
|
||||||
|
|||||||
Reference in New Issue
Block a user