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 } from 'preact/hooks'; import { Disclosure, DisclosureButton, DisclosurePanel, } from '@reach/disclosure'; import { SNNote } from '@standardnotes/snjs/dist/@types'; import { WebApplication } from '@/ui_models/application'; import { KeyboardModifier } from '@/services/ioService'; type Props = { application: WebApplication; appState: AppState; closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void; onSubmenuChange?: (submenuOpen: boolean) => void; }; type DeletePermanentlyButtonProps = { closeOnBlur: Props["closeOnBlur"]; onClick: () => void; } const DeletePermanentlyButton = ({ closeOnBlur, onClick, }: DeletePermanentlyButtonProps) => ( Delete permanently ); export const NotesOptions = observer( ({ application, appState, closeOnBlur, onSubmenuChange }: Props) => { const [tagsMenuOpen, setTagsMenuOpen] = useState(false); const [tagsMenuPosition, setTagsMenuPosition] = useState<{ top: number; right?: number; left?: number; }>({ top: 0, right: 0, }); const [tagsMenuMaxHeight, setTagsMenuMaxHeight] = useState('auto'); const [altKeyDown, setAltKeyDown] = useState(false); const toggleOn = (condition: (note: SNNote) => boolean) => { const notesMatchingAttribute = notes.filter(condition); const notesNotMatchingAttribute = notes.filter( (note) => !condition(note) ); return notesMatchingAttribute.length > notesNotMatchingAttribute.length; }; const notes = Object.values(appState.notes.selectedNotes); const hidePreviews = toggleOn((note) => note.hidePreview); const locked = toggleOn((note) => note.locked); const protect = toggleOn((note) => note.protected); const archived = notes.some((note) => note.archived); const unarchived = notes.some((note) => !note.archived); const trashed = notes.some((note) => note.trashed); const notTrashed = notes.some((note) => !note.trashed); const pinned = notes.some((note) => note.pinned); const unpinned = notes.some((note) => !note.pinned); const tagsButtonRef = useRef(); const iconClass = 'color-neutral mr-2'; useEffect(() => { if (onSubmenuChange) { onSubmenuChange(tagsMenuOpen); } }, [tagsMenuOpen, onSubmenuChange]); useEffect(() => { const removeAltKeyObserver = application.io.addKeyObserver({ modifiers: [KeyboardModifier.Alt], onKeyDown: () => { setAltKeyDown(true); }, onKeyUp: () => { setAltKeyDown(false); } }); return () => { removeAltKeyObserver(); }; }, [application]); const openTagsMenu = () => { const defaultFontSize = window.getComputedStyle( document.documentElement ).fontSize; const maxTagsMenuSize = parseFloat(defaultFontSize) * 30; const { clientWidth, clientHeight } = document.documentElement; const buttonRect = tagsButtonRef.current.getBoundingClientRect(); const footerHeight = 32; if (buttonRect.top + maxTagsMenuSize > clientHeight - footerHeight) { setTagsMenuMaxHeight(clientHeight - buttonRect.top - footerHeight - 2); } if (buttonRect.right + maxTagsMenuSize > clientWidth) { setTagsMenuPosition({ top: buttonRect.top, right: clientWidth - buttonRect.left, }); } else { setTagsMenuPosition({ top: buttonRect.top, left: buttonRect.right, }); } setTagsMenuOpen(!tagsMenuOpen); }; return ( <> { appState.notes.setLockSelectedNotes(!locked); }} > Prevent editing { appState.notes.setHideSelectedNotePreviews(!hidePreviews); }} > Show preview { appState.notes.setProtectSelectedNotes(!protect); }} > Protect {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.notes.isTagInSelectedNotes(tag) ? appState.notes.removeTagFromSelectedNotes(tag) : appState.notes.addTagToSelectedNotes(tag); }} > {tag.title} ))} )} {unpinned && ( { appState.notes.setPinSelectedNotes(true); }} > Pin to top )} {pinned && ( { appState.notes.setPinSelectedNotes(false); }} > Unpin )} {unarchived && ( { appState.notes.setArchiveSelectedNotes(true); }} > Archive )} {archived && ( { appState.notes.setArchiveSelectedNotes(false); }} > Unarchive )} {notTrashed && (altKeyDown ? ( { await appState.notes.deleteNotesPermanently(); }} /> ) : ( { await appState.notes.setTrashSelectedNotes(true); }} > Move to Trash ))} {trashed && ( <> { await appState.notes.setTrashSelectedNotes(false); }} > Restore { await appState.notes.deleteNotesPermanently(); }} /> { await appState.notes.emptyTrash(); }} > Empty Trash {appState.notes.trashedNotesCount} notes in Trash > )} > ); } );