import Icon from '@/Components/Icon/Icon' import Switch from '@/Components/Switch/Switch' import { observer } from 'mobx-react-lite' import { useState, useEffect, useMemo, useCallback } from 'react' import { NoteType, Platform, SNNote } from '@standardnotes/snjs' import { OPEN_NOTE_HISTORY_COMMAND, PIN_NOTE_COMMAND, SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND, STAR_NOTE_COMMAND, SUPER_SHOW_MARKDOWN_PREVIEW, } from '@standardnotes/ui-services' import ChangeEditorOption from './ChangeEditorOption' import ListedActionsOption from './Listed/ListedActionsOption' import AddTagOption from './AddTagOption' import { addToast, dismissToast, ToastType } from '@standardnotes/toast' import { NotesOptionsProps } from './NotesOptionsProps' import HorizontalSeparator from '../Shared/HorizontalSeparator' import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider' import { AppPaneId } from '../ResponsivePane/AppPaneMetadata' import { getNoteBlob, getNoteFileName } from '@/Utils/NoteExportUtils' import { shareSelectedNotes } from '@/NativeMobileWeb/ShareSelectedNotes' import { downloadSelectedNotesOnAndroid } from '@/NativeMobileWeb/DownloadSelectedNotesOnAndroid' import ProtectedUnauthorizedLabel from '../ProtectedItemOverlay/ProtectedUnauthorizedLabel' import { classNames } from '@standardnotes/utils' import { MenuItemIconSize } from '@/Constants/TailwindClassNames' import { KeyboardShortcutIndicator } from '../KeyboardShortcutIndicator/KeyboardShortcutIndicator' import { NoteAttributes } from './NoteAttributes' import { SpellcheckOptions } from './SpellcheckOptions' import { NoteSizeWarning } from './NoteSizeWarning' import { DeletePermanentlyButton } from './DeletePermanentlyButton' import { useCommandService } from '../ApplicationView/CommandProvider' import { menuItemClassNames, menuItemSwitchClassNames, iconClass } from './ClassNames' import SuperNoteOptions from './SuperNoteOptions' const iconSize = MenuItemIconSize const iconClassDanger = `text-danger mr-2 ${iconSize}` const iconClassWarning = `text-warning mr-2 ${iconSize}` const iconClassSuccess = `text-success mr-2 ${iconSize}` const NotesOptions = ({ application, navigationController, notesController, historyModalController, closeMenu, }: NotesOptionsProps) => { const [altKeyDown, setAltKeyDown] = useState(false) const { toggleAppPane } = useResponsiveAppPane() const commandService = useCommandService() const markdownShortcut = useMemo( () => commandService.keyboardShortcutForCommand(SUPER_SHOW_MARKDOWN_PREVIEW), [commandService], ) 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 = notesController.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 starred = notes.some((note) => note.starred) const editorForNote = useMemo( () => (notes[0] ? application.componentManager.editorForNote(notes[0]) : undefined), [application.componentManager, notes], ) useEffect(() => { const removeAltKeyObserver = application.keyboardService.addCommandHandler({ command: SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND, onKeyDown: () => { setAltKeyDown(true) }, onKeyUp: () => { setAltKeyDown(false) }, }) return () => { removeAltKeyObserver() } }, [application]) const downloadSelectedItems = useCallback(async () => { if (notes.length === 1) { const note = notes[0] const blob = getNoteBlob(application, note) application.getArchiveService().downloadData(blob, getNoteFileName(application, note)) return } if (notes.length > 1) { const loadingToastId = addToast({ type: ToastType.Loading, message: `Exporting ${notes.length} notes...`, }) await application.getArchiveService().downloadDataAsZip( notes.map((note) => { return { name: getNoteFileName(application, note), content: getNoteBlob(application, note), } }), ) dismissToast(loadingToastId) addToast({ type: ToastType.Success, message: `Exported ${notes.length} notes`, }) } }, [application, notes]) const closeMenuAndToggleNotesList = useCallback(() => { toggleAppPane(AppPaneId.Items) closeMenu() }, [closeMenu, toggleAppPane]) const duplicateSelectedItems = useCallback(async () => { await Promise.all(notes.map((note) => application.mutator.duplicateItem(note).catch(console.error))) closeMenuAndToggleNotesList() }, [application.mutator, closeMenuAndToggleNotesList, notes]) const openRevisionHistoryModal = useCallback(() => { historyModalController.openModal(notesController.firstSelectedNote) }, [historyModalController, notesController.firstSelectedNote]) const historyShortcut = useMemo( () => application.keyboardService.keyboardShortcutForCommand(OPEN_NOTE_HISTORY_COMMAND), [application], ) const pinShortcut = useMemo( () => application.keyboardService.keyboardShortcutForCommand(PIN_NOTE_COMMAND), [application], ) const starShortcut = useMemo( () => application.keyboardService.keyboardShortcutForCommand(STAR_NOTE_COMMAND), [application], ) const enableSuperMarkdownPreview = useCallback(() => { commandService.triggerCommand(SUPER_SHOW_MARKDOWN_PREVIEW) }, [commandService]) const unauthorized = notes.some((note) => !application.isAuthorizedToRenderItem(note)) if (unauthorized) { return } const firstItemClass = 'pt-4' return ( <> {notes.length === 1 && ( <> Note history {historyShortcut && } > )} { notesController.setLockSelectedNotes(!locked) }} > Prevent editing { notesController.setHideSelectedNotePreviews(!hidePreviews) }} > Show preview { notesController.setProtectSelectedNotes(!protect).catch(console.error) }} > Password protect {notes.length === 1 && ( <> > )} {navigationController.tagsCount > 0 && ( )} { notesController.setStarSelectedNotes(!starred) }} > {starred ? 'Unstar' : 'Star'} {starShortcut && } {unpinned && ( { notesController.setPinSelectedNotes(true) }} > Pin to top {pinShortcut && } )} {pinned && ( { notesController.setPinSelectedNotes(false) }} > Unpin {pinShortcut && } )} {notes[0].noteType !== NoteType.Super && ( <> { application.isNativeMobileWeb() ? void shareSelectedNotes(application, notes) : void downloadSelectedItems() }} > {application.platform === Platform.Android ? 'Share' : 'Export'} {application.platform === Platform.Android && ( downloadSelectedNotesOnAndroid(application, notes)}> Export )} > )} Duplicate {unarchived && ( { await notesController.setArchiveSelectedNotes(true).catch(console.error) closeMenuAndToggleNotesList() }} > Archive )} {archived && ( { await notesController.setArchiveSelectedNotes(false).catch(console.error) closeMenuAndToggleNotesList() }} > Unarchive )} {notTrashed && (altKeyDown ? ( { await notesController.deleteNotesPermanently() closeMenuAndToggleNotesList() }} /> ) : ( { await notesController.setTrashSelectedNotes(true) closeMenuAndToggleNotesList() }} > Move to trash ))} {trashed && ( <> { await notesController.setTrashSelectedNotes(false) closeMenuAndToggleNotesList() }} > Restore { await notesController.deleteNotesPermanently() closeMenuAndToggleNotesList() }} /> { await notesController.emptyTrash() closeMenuAndToggleNotesList() }} > Empty Trash {notesController.trashedNotesCount} notes in Trash > )} {notes.length === 1 ? ( <> {notes[0].noteType === NoteType.Super && ( )} > ) : null} > ) } export default observer(NotesOptions)