diff --git a/app/assets/javascripts/components/ChangeEditorButton.tsx b/app/assets/javascripts/components/ChangeEditorButton.tsx index c080c35ec..0523dc878 100644 --- a/app/assets/javascripts/components/ChangeEditorButton.tsx +++ b/app/assets/javascripts/components/ChangeEditorButton.tsx @@ -7,14 +7,11 @@ import { DisclosurePanel, } from '@reach/disclosure'; import VisuallyHidden from '@reach/visually-hidden'; -import { ComponentArea, SNComponent } from '@standardnotes/snjs'; import { observer } from 'mobx-react-lite'; import { FunctionComponent } from 'preact'; -import { useEffect, useRef, useState } from 'preact/hooks'; +import { useRef, useState } from 'preact/hooks'; import { Icon } from './Icon'; import { ChangeEditorMenu } from './NotesOptions/changeEditor/ChangeEditorMenu'; -import { createEditorMenuGroups } from './NotesOptions/changeEditor/createEditorMenuGroups'; -import { EditorMenuGroup } from './NotesOptions/ChangeEditorOption'; import { useCloseOnBlur } from './utils'; type Props = { @@ -26,7 +23,8 @@ type Props = { export const ChangeEditorButton: FunctionComponent = observer( ({ application, appState, onClickPreprocessing }) => { const note = Object.values(appState.notes.selectedNotes)[0]; - const [open, setOpen] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const [isVisible, setIsVisible] = useState(false); const [position, setPosition] = useState({ top: 0, right: 0, @@ -35,28 +33,7 @@ export const ChangeEditorButton: FunctionComponent = observer( const buttonRef = useRef(null); const panelRef = useRef(null); const containerRef = useRef(null); - const [closeOnBlur] = useCloseOnBlur(containerRef, setOpen); - const [editors] = useState(() => - application.componentManager - .componentsForArea(ComponentArea.Editor) - .sort((a, b) => { - return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1; - }) - ); - const [editorMenuGroups, setEditorMenuGroups] = useState( - [] - ); - const [currentEditor, setCurrentEditor] = useState(); - - useEffect(() => { - setEditorMenuGroups(createEditorMenuGroups(application, editors)); - }, [application, editors]); - - useEffect(() => { - if (note) { - setCurrentEditor(application.componentManager.editorForNote(note)); - } - }, [application, note]); + const [closeOnBlur] = useCloseOnBlur(containerRef, setIsOpen); const toggleChangeEditorMenu = async () => { const rect = buttonRef.current?.getBoundingClientRect(); @@ -81,22 +58,25 @@ export const ChangeEditorButton: FunctionComponent = observer( right: document.body.clientWidth - rect.right, }); - const newOpenState = !open; + const newOpenState = !isOpen; if (newOpenState && onClickPreprocessing) { await onClickPreprocessing(); } - setOpen(newOpenState); + setIsOpen(newOpenState); + setTimeout(() => { + setIsVisible(newOpenState); + }); } }; return (
- + { if (event.key === 'Escape') { - setOpen(false); + setIsOpen(false); } }} onBlur={closeOnBlur} @@ -109,7 +89,7 @@ export const ChangeEditorButton: FunctionComponent = observer( { if (event.key === 'Escape') { - setOpen(false); + setIsOpen(false); buttonRef.current?.focus(); } }} @@ -121,17 +101,14 @@ export const ChangeEditorButton: FunctionComponent = observer( className="sn-dropdown sn-dropdown--animated min-w-68 max-h-120 max-w-xs flex flex-col overflow-y-auto fixed" onBlur={closeOnBlur} > - {open && ( + {isOpen && ( { - setOpen(false); + setIsOpen(false); }} /> )} diff --git a/app/assets/javascripts/components/NotesListOptionsMenu.tsx b/app/assets/javascripts/components/NotesListOptionsMenu.tsx index 2976ed275..943911140 100644 --- a/app/assets/javascripts/components/NotesListOptionsMenu.tsx +++ b/app/assets/javascripts/components/NotesListOptionsMenu.tsx @@ -16,10 +16,6 @@ type Props = { export const NotesListOptionsMenu: FunctionComponent = observer( ({ closeDisplayOptionsMenu, closeOnBlur, application, isOpen }) => { - const menuClassName = - 'sn-dropdown sn-dropdown--animated min-w-70 overflow-y-auto \ -border-1 border-solid border-main text-sm z-index-dropdown-menu \ -flex flex-col py-2 top-full bottom-0 left-2 absolute'; const [sortBy, setSortBy] = useState(() => application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt) ); @@ -117,154 +113,155 @@ flex flex-col py-2 top-full bottom-0 left-2 absolute'; application.setPreference(PrefKey.NotesHideEditorIcon, !hideEditorIcon); }; - const menuRef = useRef(null); - return ( -
- +
+ Sort by +
+ -
- Sort by +
+ Date modified + {sortBy === CollectionSort.UpdatedAt ? ( + sortReverse ? ( + + ) : ( + + ) + ) : null}
- -
- Date modified - {sortBy === CollectionSort.UpdatedAt ? ( - sortReverse ? ( - - ) : ( - - ) - ) : null} -
-
- -
- Creation date - {sortBy === CollectionSort.CreatedAt ? ( - sortReverse ? ( - - ) : ( - - ) - ) : null} -
-
- -
- Title - {sortBy === CollectionSort.Title ? ( - sortReverse ? ( - - ) : ( - - ) - ) : null} -
-
- -
- View + + +
+ Creation date + {sortBy === CollectionSort.CreatedAt ? ( + sortReverse ? ( + + ) : ( + + ) + ) : null}
- -
Show note preview
-
- - Show date - - - Show tags - - - Show editor icon - -
-
- Other + + +
+ Title + {sortBy === CollectionSort.Title ? ( + sortReverse ? ( + + ) : ( + + ) + ) : null}
- - Show pinned notes - - - Show protected notes - - - Show archived notes - - - Show trashed notes - -
-
+ + +
+ View +
+ +
Show note preview
+
+ + Show date + + + Show tags + + + Show editor icon + +
+
+ Other +
+ + Show pinned notes + + + Show protected notes + + + Show archived notes + + + Show trashed notes + + ); } ); diff --git a/app/assets/javascripts/components/NotesOptions/AddTagOption.tsx b/app/assets/javascripts/components/NotesOptions/AddTagOption.tsx new file mode 100644 index 000000000..c5d046cee --- /dev/null +++ b/app/assets/javascripts/components/NotesOptions/AddTagOption.tsx @@ -0,0 +1,129 @@ +import { AppState } from '@/ui_models/app_state'; +import { + calculateSubmenuStyle, + SubmenuStyle, +} from '@/utils/calculateSubmenuStyle'; +import { + Disclosure, + DisclosureButton, + DisclosurePanel, +} from '@reach/disclosure'; +import { observer } from 'mobx-react-lite'; +import { FunctionComponent } from 'preact'; +import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; +import { Icon } from '../Icon'; +import { useCloseOnBlur } from '../utils'; + +type Props = { + appState: AppState; +}; + +export const AddTagOption: FunctionComponent = observer( + ({ appState }) => { + const menuContainerRef = useRef(null); + const menuRef = useRef(null); + const menuButtonRef = useRef(null); + + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [menuStyle, setMenuStyle] = useState({ + right: 0, + bottom: 0, + maxHeight: 'auto', + }); + + const [closeOnBlur] = useCloseOnBlur(menuContainerRef, setIsMenuOpen); + + const toggleTagsMenu = () => { + if (!isMenuOpen) { + const menuPosition = calculateSubmenuStyle(menuButtonRef.current); + if (menuPosition) { + setMenuStyle(menuPosition); + console.log(menuPosition); + } + } + + setIsMenuOpen(!isMenuOpen); + }; + + const recalculateMenuStyle = useCallback(() => { + const newMenuPosition = calculateSubmenuStyle( + menuButtonRef.current, + menuRef.current + ); + + if (newMenuPosition) { + setMenuStyle(newMenuPosition); + console.log(newMenuPosition); + } + }, []); + + useEffect(() => { + if (isMenuOpen) { + setTimeout(() => { + recalculateMenuStyle(); + }); + } + }, [isMenuOpen, recalculateMenuStyle]); + + return ( +
+ + { + if (event.key === 'Escape') { + setIsMenuOpen(false); + } + }} + onBlur={closeOnBlur} + ref={menuButtonRef} + className="sn-dropdown-item justify-between" + > +
+ + Add tag +
+ +
+ { + if (event.key === 'Escape') { + setIsMenuOpen(false); + menuButtonRef.current?.focus(); + } + }} + style={{ + ...menuStyle, + position: 'fixed', + }} + className="sn-dropdown min-w-80 flex flex-col py-2 max-h-120 max-w-xs fixed overflow-y-auto" + > + {appState.tags.tags.map((tag) => ( + + ))} + +
+
+ ); + } +); diff --git a/app/assets/javascripts/components/NotesOptions/ChangeEditorOption.tsx b/app/assets/javascripts/components/NotesOptions/ChangeEditorOption.tsx index fb90c0623..efa4f98c4 100644 --- a/app/assets/javascripts/components/NotesOptions/ChangeEditorOption.tsx +++ b/app/assets/javascripts/components/NotesOptions/ChangeEditorOption.tsx @@ -6,27 +6,21 @@ import { DisclosureButton, DisclosurePanel, } from '@reach/disclosure'; -import { - ComponentArea, - IconType, - SNComponent, - SNNote, -} from '@standardnotes/snjs'; +import { IconType, SNComponent, SNNote } from '@standardnotes/snjs'; import { FunctionComponent } from 'preact'; import { useEffect, useRef, useState } from 'preact/hooks'; import { Icon } from '../Icon'; -import { createEditorMenuGroups } from './changeEditor/createEditorMenuGroups'; import { ChangeEditorMenu } from './changeEditor/ChangeEditorMenu'; import { calculateSubmenuStyle, SubmenuStyle, } from '@/utils/calculateSubmenuStyle'; +import { useCloseOnBlur } from '../utils'; type ChangeEditorOptionProps = { appState: AppState; application: WebApplication; note: SNNote; - closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void; }; type AccordionMenuGroup = { @@ -46,114 +40,97 @@ export type EditorMenuGroup = AccordionMenuGroup; export const ChangeEditorOption: FunctionComponent = ({ application, - closeOnBlur, note, }) => { - const [changeEditorMenuOpen, setChangeEditorMenuOpen] = useState(false); - const [changeEditorMenuVisible, setChangeEditorMenuVisible] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const [isVisible, setIsVisible] = useState(false); const [menuStyle, setMenuStyle] = useState({ right: 0, bottom: 0, maxHeight: 'auto', }); - const changeEditorMenuRef = useRef(null); - const changeEditorButtonRef = useRef(null); - const [editors] = useState(() => - application.componentManager - .componentsForArea(ComponentArea.Editor) - .sort((a, b) => { - return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1; - }) - ); - const [editorMenuGroups, setEditorMenuGroups] = useState( - [] - ); - const [selectedEditor, setSelectedEditor] = useState(() => - application.componentManager.editorForNote(note) - ); + const menuContainerRef = useRef(null); + const menuRef = useRef(null); + const buttonRef = useRef(null); - useEffect(() => { - setEditorMenuGroups(createEditorMenuGroups(application, editors)); - }, [application, editors]); - - useEffect(() => { - setSelectedEditor(application.componentManager.editorForNote(note)); - }, [application, note]); + const [closeOnBlur] = useCloseOnBlur(menuContainerRef, (open: boolean) => { + setIsOpen(open); + setIsVisible(open); + }); const toggleChangeEditorMenu = () => { - if (!changeEditorMenuOpen) { - const menuStyle = calculateSubmenuStyle(changeEditorButtonRef.current); + if (!isOpen) { + const menuStyle = calculateSubmenuStyle(buttonRef.current); if (menuStyle) { setMenuStyle(menuStyle); } } - setChangeEditorMenuOpen(!changeEditorMenuOpen); + setIsOpen(!isOpen); }; useEffect(() => { - if (changeEditorMenuOpen) { + if (isOpen) { setTimeout(() => { const newMenuStyle = calculateSubmenuStyle( - changeEditorButtonRef.current, - changeEditorMenuRef.current + buttonRef.current, + menuRef.current ); if (newMenuStyle) { setMenuStyle(newMenuStyle); - setChangeEditorMenuVisible(true); + setIsVisible(true); } }); } - }, [changeEditorMenuOpen]); + }, [isOpen]); return ( - - { - if (event.key === KeyboardKey.Escape) { - setChangeEditorMenuOpen(false); - } - }} - onBlur={closeOnBlur} - ref={changeEditorButtonRef} - className="sn-dropdown-item justify-between" - > -
- - Change editor -
- -
- { - if (event.key === KeyboardKey.Escape) { - setChangeEditorMenuOpen(false); - changeEditorButtonRef.current?.focus(); - } - }} - style={{ - ...menuStyle, - position: 'fixed', - }} - className="sn-dropdown flex flex-col max-h-120 min-w-68 fixed overflow-y-auto" - > - {changeEditorMenuOpen && ( - { - setChangeEditorMenuOpen(false); - }} - /> - )} - -
+
+ + { + if (event.key === KeyboardKey.Escape) { + setIsOpen(false); + } + }} + onBlur={closeOnBlur} + ref={buttonRef} + className="sn-dropdown-item justify-between" + > +
+ + Change editor +
+ +
+ { + if (event.key === KeyboardKey.Escape) { + setIsOpen(false); + buttonRef.current?.focus(); + } + }} + style={{ + ...menuStyle, + position: 'fixed', + }} + className="sn-dropdown flex flex-col max-h-120 min-w-68 fixed overflow-y-auto" + > + {isOpen && ( + { + setIsOpen(false); + }} + /> + )} + +
+
); }; diff --git a/app/assets/javascripts/components/NotesOptions/ListedActionsOption.tsx b/app/assets/javascripts/components/NotesOptions/ListedActionsOption.tsx index 044939f46..5fc9bef1a 100644 --- a/app/assets/javascripts/components/NotesOptions/ListedActionsOption.tsx +++ b/app/assets/javascripts/components/NotesOptions/ListedActionsOption.tsx @@ -12,11 +12,11 @@ import { Action, ListedAccount, SNNote } from '@standardnotes/snjs'; import { Fragment, FunctionComponent } from 'preact'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; import { Icon } from '../Icon'; +import { useCloseOnBlur } from '../utils'; type Props = { application: WebApplication; note: SNNote; - closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void; }; type ListedMenuGroup = { @@ -230,8 +230,8 @@ const ListedActionsMenu: FunctionComponent = ({ export const ListedActionsOption: FunctionComponent = ({ application, note, - closeOnBlur, }) => { + const menuContainerRef = useRef(null); const menuRef = useRef(null); const menuButtonRef = useRef(null); @@ -242,6 +242,8 @@ export const ListedActionsOption: FunctionComponent = ({ maxHeight: 'auto', }); + const [closeOnBlur] = useCloseOnBlur(menuContainerRef, setIsMenuOpen); + const toggleListedMenu = () => { if (!isMenuOpen) { const menuPosition = calculateSubmenuStyle(menuButtonRef.current); @@ -273,34 +275,36 @@ export const ListedActionsOption: FunctionComponent = ({ }, [isMenuOpen, recalculateMenuStyle]); return ( - - -
- - Listed actions -
- -
- - {isMenuOpen && ( - - )} - -
+
+ + +
+ + Listed actions +
+ +
+ + {isMenuOpen && ( + + )} + +
+
); }; diff --git a/app/assets/javascripts/components/NotesOptions/NotesOptions.tsx b/app/assets/javascripts/components/NotesOptions/NotesOptions.tsx index 8a00573b2..7c4b74fb8 100644 --- a/app/assets/javascripts/components/NotesOptions/NotesOptions.tsx +++ b/app/assets/javascripts/components/NotesOptions/NotesOptions.tsx @@ -2,29 +2,20 @@ import { AppState } from '@/ui_models/app_state'; import { Icon } from '../Icon'; import { Switch } from '../Switch'; import { observer } from 'mobx-react-lite'; -import { useRef, useState, useEffect, useMemo } from 'preact/hooks'; -import { - Disclosure, - DisclosureButton, - DisclosurePanel, -} from '@reach/disclosure'; +import { useState, useEffect, useMemo } from 'preact/hooks'; import { SNApplication, SNNote } from '@standardnotes/snjs'; import { WebApplication } from '@/ui_models/application'; import { KeyboardModifier } from '@/services/ioService'; import { FunctionComponent } from 'preact'; import { ChangeEditorOption } from './ChangeEditorOption'; -import { - MENU_MARGIN_FROM_APP_BORDER, - MAX_MENU_SIZE_MULTIPLIER, - BYTES_IN_ONE_MEGABYTE, -} from '@/constants'; +import { BYTES_IN_ONE_MEGABYTE } from '@/constants'; import { ListedActionsOption } from './ListedActionsOption'; +import { AddTagOption } from './AddTagOption'; export type NotesOptionsProps = { application: WebApplication; appState: AppState; closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void; - onSubmenuChange?: (submenuOpen: boolean) => void; }; type DeletePermanentlyButtonProps = { @@ -206,24 +197,7 @@ const NoteSizeWarning: FunctionComponent<{ ) : null; export const NotesOptions = observer( - ({ - application, - appState, - closeOnBlur, - onSubmenuChange, - }: NotesOptionsProps) => { - const [tagsMenuOpen, setTagsMenuOpen] = useState(false); - const [tagsMenuPosition, setTagsMenuPosition] = useState<{ - top: number; - right?: number; - left?: number; - }>({ - top: 0, - right: 0, - }); - const [tagsMenuMaxHeight, setTagsMenuMaxHeight] = useState( - 'auto' - ); + ({ application, appState, closeOnBlur }: NotesOptionsProps) => { const [altKeyDown, setAltKeyDown] = useState(false); const toggleOn = (condition: (note: SNNote) => boolean) => { @@ -246,14 +220,6 @@ export const NotesOptions = observer( const unpinned = notes.some((note) => !note.pinned); const errored = notes.some((note) => note.errorDecrypting); - const tagsButtonRef = useRef(null); - - useEffect(() => { - if (onSubmenuChange) { - onSubmenuChange(tagsMenuOpen); - } - }, [tagsMenuOpen, onSubmenuChange]); - useEffect(() => { const removeAltKeyObserver = application.io.addKeyObserver({ modifiers: [KeyboardModifier.Alt], @@ -270,48 +236,6 @@ export const NotesOptions = observer( }; }, [application]); - const openTagsMenu = () => { - const defaultFontSize = window.getComputedStyle( - document.documentElement - ).fontSize; - const maxTagsMenuSize = - parseFloat(defaultFontSize) * MAX_MENU_SIZE_MULTIPLIER; - const { clientWidth, clientHeight } = document.documentElement; - const buttonRect = tagsButtonRef.current?.getBoundingClientRect(); - const footerElementRect = document - .getElementById('footer-bar') - ?.getBoundingClientRect(); - const footerHeightInPx = footerElementRect?.height; - - if (buttonRect && footerHeightInPx) { - if ( - buttonRect.top + maxTagsMenuSize > - clientHeight - footerHeightInPx - ) { - setTagsMenuMaxHeight( - clientHeight - - buttonRect.top - - footerHeightInPx - - MENU_MARGIN_FROM_APP_BORDER - ); - } - - if (buttonRect.right + maxTagsMenuSize > clientWidth) { - setTagsMenuPosition({ - top: buttonRect.top, - right: clientWidth - buttonRect.left, - }); - } else { - setTagsMenuPosition({ - top: buttonRect.top, - left: buttonRect.right, - }); - } - } - - setTagsMenuOpen(!tagsMenuOpen); - }; - const downloadSelectedItems = () => { notes.forEach((note) => { const editor = application.componentManager.editorForNote(note); @@ -416,70 +340,12 @@ export const NotesOptions = observer( )}
- {appState.tags.tagsCount > 0 && ( - - { - if (event.key === 'Escape') { - setTagsMenuOpen(false); - } - }} - onBlur={closeOnBlur} - ref={tagsButtonRef} - className="sn-dropdown-item justify-between" - > -
- - {'Add tag'} -
- -
- { - if (event.key === 'Escape') { - setTagsMenuOpen(false); - tagsButtonRef.current?.focus(); - } - }} - style={{ - ...tagsMenuPosition, - maxHeight: tagsMenuMaxHeight, - position: 'fixed', - }} - className="sn-dropdown min-w-80 flex flex-col py-2 max-h-120 max-w-xs fixed overflow-y-auto" - > - {appState.tags.tags.map((tag) => ( - - ))} - -
- )} + {appState.tags.tagsCount > 0 && } {unpinned && (