feat: Added "Add tag" option to file context menu
This commit is contained in:
@@ -216,6 +216,8 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
||||
<FileContextMenuWrapper
|
||||
filesController={viewControllerManager.filesController}
|
||||
selectionController={viewControllerManager.selectionController}
|
||||
navigationController={viewControllerManager.navigationController}
|
||||
linkingController={viewControllerManager.linkingController}
|
||||
/>
|
||||
<PurchaseFlowWrapper application={application} viewControllerManager={viewControllerManager} />
|
||||
<ConfirmSignoutContainer
|
||||
|
||||
@@ -358,6 +358,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
||||
filesController={filesController}
|
||||
featuresController={featuresController}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
/>
|
||||
) : (
|
||||
<ContentList
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { FilesController } from '@/Controllers/FilesController'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
@@ -9,36 +11,47 @@ import FileMenuOptions from './FileMenuOptions'
|
||||
type Props = {
|
||||
filesController: FilesController
|
||||
selectionController: SelectedItemsController
|
||||
linkingController: LinkingController
|
||||
navigationController: NavigationController
|
||||
}
|
||||
|
||||
const FileContextMenu: FunctionComponent<Props> = observer(({ filesController, selectionController }) => {
|
||||
const { showFileContextMenu, setShowFileContextMenu, fileContextMenuLocation } = filesController
|
||||
const { selectedFiles } = selectionController
|
||||
const FileContextMenu: FunctionComponent<Props> = observer(
|
||||
({ filesController, selectionController, linkingController, navigationController }) => {
|
||||
const { showFileContextMenu, setShowFileContextMenu, fileContextMenuLocation } = filesController
|
||||
const { selectedFiles } = selectionController
|
||||
|
||||
return (
|
||||
<Popover
|
||||
open={showFileContextMenu}
|
||||
anchorPoint={fileContextMenuLocation}
|
||||
togglePopover={() => setShowFileContextMenu(!showFileContextMenu)}
|
||||
align="start"
|
||||
className="py-2"
|
||||
>
|
||||
<Menu a11yLabel="File context menu" isOpen={showFileContextMenu}>
|
||||
<FileMenuOptions
|
||||
filesController={filesController}
|
||||
selectedFiles={selectedFiles}
|
||||
closeMenu={() => setShowFileContextMenu(false)}
|
||||
shouldShowRenameOption={false}
|
||||
shouldShowAttachOption={false}
|
||||
/>
|
||||
</Menu>
|
||||
</Popover>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<Popover
|
||||
open={showFileContextMenu}
|
||||
anchorPoint={fileContextMenuLocation}
|
||||
togglePopover={() => setShowFileContextMenu(!showFileContextMenu)}
|
||||
align="start"
|
||||
className="py-2"
|
||||
>
|
||||
<Menu a11yLabel="File context menu" isOpen={showFileContextMenu}>
|
||||
<FileMenuOptions
|
||||
filesController={filesController}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
selectedFiles={selectedFiles}
|
||||
closeMenu={() => setShowFileContextMenu(false)}
|
||||
shouldShowRenameOption={false}
|
||||
shouldShowAttachOption={false}
|
||||
/>
|
||||
</Menu>
|
||||
</Popover>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
FileContextMenu.displayName = 'FileContextMenu'
|
||||
|
||||
const FileContextMenuWrapper: FunctionComponent<Props> = ({ filesController, selectionController }) => {
|
||||
const FileContextMenuWrapper: FunctionComponent<Props> = ({
|
||||
filesController,
|
||||
linkingController,
|
||||
navigationController,
|
||||
selectionController,
|
||||
}) => {
|
||||
const { showFileContextMenu } = filesController
|
||||
const { selectedFiles } = selectionController
|
||||
|
||||
@@ -48,7 +61,14 @@ const FileContextMenuWrapper: FunctionComponent<Props> = ({ filesController, sel
|
||||
return null
|
||||
}
|
||||
|
||||
return <FileContextMenu filesController={filesController} selectionController={selectionController} />
|
||||
return (
|
||||
<FileContextMenu
|
||||
filesController={filesController}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
selectionController={selectionController}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(FileContextMenuWrapper)
|
||||
|
||||
@@ -11,10 +11,16 @@ import MenuItem from '../Menu/MenuItem'
|
||||
import { FileContextMenuBackupOption } from './FileContextMenuBackupOption'
|
||||
import MenuSwitchButtonItem from '../Menu/MenuSwitchButtonItem'
|
||||
import { FileItem } from '@standardnotes/snjs'
|
||||
import AddTagOption from '../NotesOptions/AddTagOption'
|
||||
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
|
||||
type Props = {
|
||||
closeMenu: () => void
|
||||
filesController: FilesController
|
||||
linkingController: LinkingController
|
||||
navigationController: NavigationController
|
||||
isFileAttachedToNote?: boolean
|
||||
renameToggleCallback?: (isRenamingFile: boolean) => void
|
||||
shouldShowRenameOption: boolean
|
||||
@@ -25,6 +31,8 @@ type Props = {
|
||||
const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
closeMenu,
|
||||
filesController,
|
||||
linkingController,
|
||||
navigationController,
|
||||
isFileAttachedToNote,
|
||||
renameToggleCallback,
|
||||
shouldShowRenameOption,
|
||||
@@ -82,6 +90,12 @@ const FileMenuOptions: FunctionComponent<Props> = ({
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
<AddTagOption
|
||||
navigationController={navigationController}
|
||||
linkingController={linkingController}
|
||||
selectedItems={selectedFiles}
|
||||
iconClassName={`text-neutral mr-2 ${MenuItemIconSize}`}
|
||||
/>
|
||||
<MenuSwitchButtonItem
|
||||
checked={hasProtectedFiles}
|
||||
onChange={(hasProtectedFiles) => {
|
||||
|
||||
@@ -6,13 +6,22 @@ import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||
import Popover from '../Popover/Popover'
|
||||
import RoundIconButton from '../Button/RoundIconButton'
|
||||
import Menu from '../Menu/Menu'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
|
||||
type Props = {
|
||||
filesController: FilesController
|
||||
selectionController: SelectedItemsController
|
||||
linkingController: LinkingController
|
||||
navigationController: NavigationController
|
||||
}
|
||||
|
||||
const FilesOptionsPanel = ({ filesController, selectionController }: Props) => {
|
||||
const FilesOptionsPanel = ({
|
||||
filesController,
|
||||
linkingController,
|
||||
navigationController,
|
||||
selectionController,
|
||||
}: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
@@ -25,6 +34,8 @@ const FilesOptionsPanel = ({ filesController, selectionController }: Props) => {
|
||||
<Menu a11yLabel="File options panel" isOpen={isOpen}>
|
||||
<FileMenuOptions
|
||||
filesController={filesController}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
selectedFiles={selectionController.selectedFiles}
|
||||
closeMenu={() => {
|
||||
setIsOpen(false)
|
||||
|
||||
@@ -112,6 +112,8 @@ const FileViewWithoutProtection = ({ application, viewControllerManager, file }:
|
||||
<FileOptionsPanel
|
||||
filesController={viewControllerManager.filesController}
|
||||
selectionController={viewControllerManager.selectionController}
|
||||
linkingController={viewControllerManager.linkingController}
|
||||
navigationController={viewControllerManager.navigationController}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,8 +31,19 @@ import { LinkingController } from '@/Controllers/LinkingController'
|
||||
import { FeaturesController } from '@/Controllers/FeaturesController'
|
||||
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
|
||||
const ContextMenuCell = ({ files, filesController }: { files: FileItem[]; filesController: FilesController }) => {
|
||||
const ContextMenuCell = ({
|
||||
files,
|
||||
filesController,
|
||||
navigationController,
|
||||
linkingController,
|
||||
}: {
|
||||
files: FileItem[]
|
||||
filesController: FilesController
|
||||
navigationController: NavigationController
|
||||
linkingController: LinkingController
|
||||
}) => {
|
||||
const [contextMenuVisible, setContextMenuVisible] = useState(false)
|
||||
const anchorElementRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
@@ -61,6 +72,8 @@ const ContextMenuCell = ({ files, filesController }: { files: FileItem[]; filesC
|
||||
>
|
||||
<Menu a11yLabel="File context menu" isOpen={contextMenuVisible}>
|
||||
<FileMenuOptions
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
closeMenu={() => {
|
||||
setContextMenuVisible(false)
|
||||
}}
|
||||
@@ -160,9 +173,16 @@ type Props = {
|
||||
filesController: FilesController
|
||||
featuresController: FeaturesController
|
||||
linkingController: LinkingController
|
||||
navigationController: NavigationController
|
||||
}
|
||||
|
||||
const FilesTableView = ({ application, filesController, featuresController, linkingController }: Props) => {
|
||||
const FilesTableView = ({
|
||||
application,
|
||||
filesController,
|
||||
featuresController,
|
||||
linkingController,
|
||||
navigationController,
|
||||
}: Props) => {
|
||||
const files = application.items
|
||||
.getDisplayableNotesAndFiles()
|
||||
.filter((item) => item.content_type === ContentType.File) as FileItem[]
|
||||
@@ -312,12 +332,22 @@ const FilesTableView = ({ application, filesController, featuresController, link
|
||||
featuresController={featuresController}
|
||||
linkingController={linkingController}
|
||||
/>
|
||||
<ContextMenuCell files={[file]} filesController={filesController} />
|
||||
<ContextMenuCell
|
||||
files={[file]}
|
||||
filesController={filesController}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
selectionActions: (fileIds) => (
|
||||
<ContextMenuCell files={files.filter((file) => fileIds.includes(file.uuid))} filesController={filesController} />
|
||||
<ContextMenuCell
|
||||
files={files.filter((file) => fileIds.includes(file.uuid))}
|
||||
filesController={filesController}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
/>
|
||||
),
|
||||
showSelectionActions: true,
|
||||
})
|
||||
@@ -347,6 +377,8 @@ const FilesTableView = ({ application, filesController, featuresController, link
|
||||
shouldShowRenameOption={false}
|
||||
shouldShowAttachOption={false}
|
||||
selectedFiles={[contextMenuFile]}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
/>
|
||||
</Menu>
|
||||
</Popover>
|
||||
|
||||
@@ -5,13 +5,22 @@ import { useCallback } from 'react'
|
||||
import FileOptionsPanel from '../FileContextMenu/FileOptionsPanel'
|
||||
import { FilesController } from '@/Controllers/FilesController'
|
||||
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
|
||||
type Props = {
|
||||
filesController: FilesController
|
||||
selectionController: SelectedItemsController
|
||||
linkingController: LinkingController
|
||||
navigationController: NavigationController
|
||||
}
|
||||
|
||||
const MultipleSelectedFiles = ({ filesController, selectionController }: Props) => {
|
||||
const MultipleSelectedFiles = ({
|
||||
filesController,
|
||||
selectionController,
|
||||
linkingController,
|
||||
navigationController,
|
||||
}: Props) => {
|
||||
const count = selectionController.selectedFilesCount
|
||||
|
||||
const cancelMultipleSelection = useCallback(() => {
|
||||
@@ -23,7 +32,12 @@ const MultipleSelectedFiles = ({ filesController, selectionController }: Props)
|
||||
<div className="flex w-full items-center justify-between p-4">
|
||||
<h1 className="m-0 text-lg font-bold">{count} selected files</h1>
|
||||
<div>
|
||||
<FileOptionsPanel filesController={filesController} selectionController={selectionController} />
|
||||
<FileOptionsPanel
|
||||
filesController={filesController}
|
||||
selectionController={selectionController}
|
||||
linkingController={linkingController}
|
||||
navigationController={navigationController}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-h-full w-full max-w-md flex-grow flex-col items-center justify-center">
|
||||
|
||||
@@ -118,6 +118,8 @@ class NoteGroupView extends AbstractComponent<Props, State> {
|
||||
<MultipleSelectedFiles
|
||||
filesController={this.viewControllerManager.filesController}
|
||||
selectionController={this.viewControllerManager.selectionController}
|
||||
navigationController={this.viewControllerManager.navigationController}
|
||||
linkingController={this.viewControllerManager.linkingController}
|
||||
/>
|
||||
)}
|
||||
{shouldNotShowMultipleSelectedItems && hasControllers && (
|
||||
|
||||
@@ -2,22 +2,28 @@ import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import Popover from '../Popover/Popover'
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
import { classNames, DecryptedItemInterface, IconType, SNTag } from '@standardnotes/snjs'
|
||||
import { getTitleForLinkedTag } from '@/Utils/Items/Display/getTitleForLinkedTag'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import MenuItem from '../Menu/MenuItem'
|
||||
import Menu from '../Menu/Menu'
|
||||
import { LinkingController } from '@/Controllers/LinkingController'
|
||||
|
||||
type Props = {
|
||||
navigationController: NavigationController
|
||||
notesController: NotesController
|
||||
linkingController: LinkingController
|
||||
selectedItems: DecryptedItemInterface[]
|
||||
iconClassName: string
|
||||
}
|
||||
|
||||
const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesController, iconClassName }) => {
|
||||
const AddTagOption: FunctionComponent<Props> = ({
|
||||
navigationController,
|
||||
linkingController,
|
||||
selectedItems,
|
||||
iconClassName,
|
||||
}) => {
|
||||
const application = useApplication()
|
||||
const menuContainerRef = useRef<HTMLDivElement>(null)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
@@ -28,6 +34,18 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
|
||||
setIsOpen((isOpen) => !isOpen)
|
||||
}, [])
|
||||
|
||||
const isTagLinkedToSelectedItems = (tag: SNTag) => {
|
||||
return selectedItems.every((item) => application.getItemTags(item).find((itemTag) => itemTag.uuid === tag.uuid))
|
||||
}
|
||||
|
||||
const linkTagToSelectedItems = (tag: SNTag) => {
|
||||
selectedItems.forEach((item) => linkingController.linkItems(item, tag))
|
||||
}
|
||||
|
||||
const unlinkTagFromSelectedItems = (tag: SNTag) => {
|
||||
selectedItems.forEach((item) => linkingController.unlinkItems(item, tag))
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={menuContainerRef}>
|
||||
<MenuItem
|
||||
@@ -59,9 +77,7 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
|
||||
<MenuItem
|
||||
key={tag.uuid}
|
||||
onClick={() => {
|
||||
notesController.isTagInSelectedNotes(tag)
|
||||
? notesController.removeTagFromSelectedNotes(tag).catch(console.error)
|
||||
: notesController.addTagToSelectedNotes(tag).catch(console.error)
|
||||
isTagLinkedToSelectedItems(tag) ? unlinkTagFromSelectedItems(tag) : linkTagToSelectedItems(tag)
|
||||
}}
|
||||
>
|
||||
{tag.iconString && (
|
||||
@@ -72,8 +88,10 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className={`overflow-hidden overflow-ellipsis whitespace-nowrap
|
||||
${notesController.isTagInSelectedNotes(tag) ? 'font-bold' : ''}`}
|
||||
className={classNames(
|
||||
'overflow-hidden overflow-ellipsis whitespace-nowrap',
|
||||
isTagLinkedToSelectedItems(tag) ? 'font-bold' : '',
|
||||
)}
|
||||
>
|
||||
{getTitleForLinkedTag(tag, application)?.longTitle}
|
||||
</span>
|
||||
|
||||
@@ -42,6 +42,7 @@ const NotesOptions = ({
|
||||
application,
|
||||
navigationController,
|
||||
notesController,
|
||||
linkingController,
|
||||
historyModalController,
|
||||
closeMenu,
|
||||
}: NotesOptionsProps) => {
|
||||
@@ -214,7 +215,8 @@ const NotesOptions = ({
|
||||
<AddTagOption
|
||||
iconClassName={iconClass}
|
||||
navigationController={navigationController}
|
||||
notesController={notesController}
|
||||
selectedItems={notes}
|
||||
linkingController={linkingController}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
|
||||
Reference in New Issue
Block a user