refactor: files table view -> content table view
This commit is contained in:
@@ -36,9 +36,10 @@ import { FeatureName } from '@/Controllers/FeatureName'
|
|||||||
import { PanelResizedData } from '@/Types/PanelResizedData'
|
import { PanelResizedData } from '@/Types/PanelResizedData'
|
||||||
import { useForwardedRef } from '@/Hooks/useForwardedRef'
|
import { useForwardedRef } from '@/Hooks/useForwardedRef'
|
||||||
import FloatingAddButton from './FloatingAddButton'
|
import FloatingAddButton from './FloatingAddButton'
|
||||||
import FilesTableView from '../FilesTableView/FilesTableView'
|
import ContentTableView from '../ContentTableView/ContentTableView'
|
||||||
import { FeaturesController } from '@/Controllers/FeaturesController'
|
import { FeaturesController } from '@/Controllers/FeaturesController'
|
||||||
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||||
|
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
accountMenuController: AccountMenuController
|
accountMenuController: AccountMenuController
|
||||||
@@ -52,6 +53,7 @@ type Props = {
|
|||||||
searchOptionsController: SearchOptionsController
|
searchOptionsController: SearchOptionsController
|
||||||
linkingController: LinkingController
|
linkingController: LinkingController
|
||||||
featuresController: FeaturesController
|
featuresController: FeaturesController
|
||||||
|
historyModalController: HistoryModalController
|
||||||
className?: string
|
className?: string
|
||||||
id: string
|
id: string
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
@@ -72,6 +74,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
|||||||
searchOptionsController,
|
searchOptionsController,
|
||||||
linkingController,
|
linkingController,
|
||||||
featuresController,
|
featuresController,
|
||||||
|
historyModalController,
|
||||||
className,
|
className,
|
||||||
id,
|
id,
|
||||||
children,
|
children,
|
||||||
@@ -353,12 +356,15 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
|||||||
) : null}
|
) : null}
|
||||||
{!dailyMode && renderedItems.length ? (
|
{!dailyMode && renderedItems.length ? (
|
||||||
shouldShowFilesTableView ? (
|
shouldShowFilesTableView ? (
|
||||||
<FilesTableView
|
<ContentTableView
|
||||||
|
items={items}
|
||||||
application={application}
|
application={application}
|
||||||
filesController={filesController}
|
filesController={filesController}
|
||||||
featuresController={featuresController}
|
featuresController={featuresController}
|
||||||
linkingController={linkingController}
|
linkingController={linkingController}
|
||||||
navigationController={navigationController}
|
navigationController={navigationController}
|
||||||
|
notesController={notesController}
|
||||||
|
historyModalController={historyModalController}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ContentList
|
<ContentList
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { formatDateForContextMenu } from '@/Utils/DateUtils'
|
|||||||
import { getIconForFileType } from '@/Utils/Items/Icons/getIconForFileType'
|
import { getIconForFileType } from '@/Utils/Items/Icons/getIconForFileType'
|
||||||
import { formatSizeToReadableString } from '@standardnotes/filepicker'
|
import { formatSizeToReadableString } from '@standardnotes/filepicker'
|
||||||
import {
|
import {
|
||||||
ContentType,
|
|
||||||
FileItem,
|
FileItem,
|
||||||
SortableItem,
|
SortableItem,
|
||||||
PrefKey,
|
PrefKey,
|
||||||
@@ -13,6 +12,8 @@ import {
|
|||||||
naturalSort,
|
naturalSort,
|
||||||
FileBackupRecord,
|
FileBackupRecord,
|
||||||
SystemViewId,
|
SystemViewId,
|
||||||
|
DecryptedItemInterface,
|
||||||
|
SNNote,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
|
import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
|
||||||
import { FileItemActionType } from '../AttachedFilesPopover/PopoverFileItemAction'
|
import { FileItemActionType } from '../AttachedFilesPopover/PopoverFileItemAction'
|
||||||
@@ -32,21 +33,42 @@ import { FeaturesController } from '@/Controllers/FeaturesController'
|
|||||||
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||||
import { useApplication } from '../ApplicationProvider'
|
import { useApplication } from '../ApplicationProvider'
|
||||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
|
import { getIconAndTintForNoteType } from '@/Utils/Items/Icons/getIconAndTintForNoteType'
|
||||||
|
import NotesOptions from '../NotesOptions/NotesOptions'
|
||||||
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
|
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
||||||
|
|
||||||
const ContextMenuCell = ({
|
const ContextMenuCell = ({
|
||||||
files,
|
items,
|
||||||
filesController,
|
filesController,
|
||||||
navigationController,
|
navigationController,
|
||||||
linkingController,
|
linkingController,
|
||||||
|
notesController,
|
||||||
|
historyModalController,
|
||||||
}: {
|
}: {
|
||||||
files: FileItem[]
|
items: DecryptedItemInterface[]
|
||||||
filesController: FilesController
|
filesController: FilesController
|
||||||
navigationController: NavigationController
|
navigationController: NavigationController
|
||||||
linkingController: LinkingController
|
linkingController: LinkingController
|
||||||
|
notesController: NotesController
|
||||||
|
historyModalController: HistoryModalController
|
||||||
}) => {
|
}) => {
|
||||||
|
const application = useApplication()
|
||||||
const [contextMenuVisible, setContextMenuVisible] = useState(false)
|
const [contextMenuVisible, setContextMenuVisible] = useState(false)
|
||||||
const anchorElementRef = useRef<HTMLButtonElement>(null)
|
const anchorElementRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
|
const allItemsAreNotes = useMemo(() => {
|
||||||
|
return items.every((item) => item instanceof SNNote)
|
||||||
|
}, [items])
|
||||||
|
|
||||||
|
const allItemsAreFiles = useMemo(() => {
|
||||||
|
return items.every((item) => item instanceof FileItem)
|
||||||
|
}, [items])
|
||||||
|
|
||||||
|
if (!allItemsAreNotes && !allItemsAreFiles) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
@@ -71,30 +93,45 @@ const ContextMenuCell = ({
|
|||||||
className="py-2"
|
className="py-2"
|
||||||
>
|
>
|
||||||
<Menu a11yLabel="File context menu" isOpen={contextMenuVisible}>
|
<Menu a11yLabel="File context menu" isOpen={contextMenuVisible}>
|
||||||
<FileMenuOptions
|
{allItemsAreFiles && (
|
||||||
linkingController={linkingController}
|
<FileMenuOptions
|
||||||
navigationController={navigationController}
|
linkingController={linkingController}
|
||||||
closeMenu={() => {
|
navigationController={navigationController}
|
||||||
setContextMenuVisible(false)
|
closeMenu={() => {
|
||||||
}}
|
setContextMenuVisible(false)
|
||||||
filesController={filesController}
|
}}
|
||||||
shouldShowRenameOption={false}
|
filesController={filesController}
|
||||||
shouldShowAttachOption={false}
|
shouldShowRenameOption={false}
|
||||||
selectedFiles={files}
|
shouldShowAttachOption={false}
|
||||||
/>
|
selectedFiles={items as FileItem[]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{allItemsAreNotes && (
|
||||||
|
<NotesOptions
|
||||||
|
notes={items as SNNote[]}
|
||||||
|
application={application}
|
||||||
|
navigationController={navigationController}
|
||||||
|
notesController={notesController}
|
||||||
|
linkingController={linkingController}
|
||||||
|
historyModalController={historyModalController}
|
||||||
|
closeMenu={() => {
|
||||||
|
setContextMenuVisible(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
</Popover>
|
</Popover>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileLinksCell = ({
|
const ItemLinksCell = ({
|
||||||
file,
|
item,
|
||||||
filesController,
|
filesController,
|
||||||
linkingController,
|
linkingController,
|
||||||
featuresController,
|
featuresController,
|
||||||
}: {
|
}: {
|
||||||
file: FileItem
|
item: DecryptedItemInterface
|
||||||
filesController: FilesController
|
filesController: FilesController
|
||||||
linkingController: LinkingController
|
linkingController: LinkingController
|
||||||
featuresController: FeaturesController
|
featuresController: FeaturesController
|
||||||
@@ -130,25 +167,38 @@ const FileLinksCell = ({
|
|||||||
filesController={filesController}
|
filesController={filesController}
|
||||||
featuresController={featuresController}
|
featuresController={featuresController}
|
||||||
isOpen={contextMenuVisible}
|
isOpen={contextMenuVisible}
|
||||||
item={file}
|
item={item}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileNameCell = ({ file }: { file: FileItem }) => {
|
const ItemNameCell = ({ item }: { item: DecryptedItemInterface }) => {
|
||||||
const application = useApplication()
|
const application = useApplication()
|
||||||
const [backupInfo, setBackupInfo] = useState<FileBackupRecord | undefined>(undefined)
|
const [backupInfo, setBackupInfo] = useState<FileBackupRecord | undefined>(undefined)
|
||||||
|
const isItemFile = item instanceof FileItem
|
||||||
|
|
||||||
|
const noteType =
|
||||||
|
item instanceof SNNote
|
||||||
|
? item.noteType || application.componentManager.editorForNote(item)?.package_info.note_type
|
||||||
|
: undefined
|
||||||
|
const [noteIcon, noteIconTint] = getIconAndTintForNoteType(noteType)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void application.fileBackups?.getFileBackupInfo(file).then(setBackupInfo)
|
if (isItemFile) {
|
||||||
}, [application, file])
|
void application.fileBackups?.getFileBackupInfo(item).then(setBackupInfo)
|
||||||
|
}
|
||||||
|
}, [application, isItemFile, item])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-3 whitespace-normal">
|
<div className="flex items-center gap-3 whitespace-normal">
|
||||||
<span className="relative">
|
<span className="relative">
|
||||||
{getFileIconComponent(getIconForFileType(file.mimeType), 'w-6 h-6 flex-shrink-0')}
|
{isItemFile ? (
|
||||||
|
getFileIconComponent(getIconForFileType(item.mimeType), 'w-6 h-6 flex-shrink-0')
|
||||||
|
) : (
|
||||||
|
<Icon type={noteIcon} className={`text-accessory-tint-${noteIconTint}`} />
|
||||||
|
)}
|
||||||
{backupInfo && (
|
{backupInfo && (
|
||||||
<div
|
<div
|
||||||
className="absolute bottom-1 right-1 translate-x-1/2 translate-y-1/2 rounded-full bg-default text-success"
|
className="absolute bottom-1 right-1 translate-x-1/2 translate-y-1/2 rounded-full bg-default text-success"
|
||||||
@@ -158,8 +208,8 @@ const FileNameCell = ({ file }: { file: FileItem }) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap text-sm font-medium">{file.title}</span>
|
<span className="overflow-hidden text-ellipsis whitespace-nowrap text-sm font-medium">{item.title}</span>
|
||||||
{file.protected && (
|
{item.protected && (
|
||||||
<span className="flex items-center" title="File is protected">
|
<span className="flex items-center" title="File is protected">
|
||||||
<Icon ariaLabel="File is protected" type="lock-filled" className="h-3.5 w-3.5 text-passive-1" size="custom" />
|
<Icon ariaLabel="File is protected" type="lock-filled" className="h-3.5 w-3.5 text-passive-1" size="custom" />
|
||||||
</span>
|
</span>
|
||||||
@@ -170,22 +220,26 @@ const FileNameCell = ({ file }: { file: FileItem }) => {
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
|
items: DecryptedItemInterface[]
|
||||||
filesController: FilesController
|
filesController: FilesController
|
||||||
featuresController: FeaturesController
|
featuresController: FeaturesController
|
||||||
linkingController: LinkingController
|
linkingController: LinkingController
|
||||||
navigationController: NavigationController
|
navigationController: NavigationController
|
||||||
|
notesController: NotesController
|
||||||
|
historyModalController: HistoryModalController
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilesTableView = ({
|
const ContentTableView = ({
|
||||||
application,
|
application,
|
||||||
|
items,
|
||||||
filesController,
|
filesController,
|
||||||
featuresController,
|
featuresController,
|
||||||
linkingController,
|
linkingController,
|
||||||
navigationController,
|
navigationController,
|
||||||
|
notesController,
|
||||||
|
historyModalController,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const files = application.items
|
const listHasFiles = items.some((item) => item instanceof FileItem)
|
||||||
.getDisplayableNotesAndFiles()
|
|
||||||
.filter((item) => item.content_type === ContentType.File) as FileItem[]
|
|
||||||
|
|
||||||
const getSortByPreference = useCallback(() => {
|
const getSortByPreference = useCallback(() => {
|
||||||
const globalPrefValue = application.getPreference(PrefKey.SortNotesBy, PrefDefaults[PrefKey.SortNotesBy])
|
const globalPrefValue = application.getPreference(PrefKey.SortNotesBy, PrefDefaults[PrefKey.SortNotesBy])
|
||||||
@@ -231,48 +285,48 @@ const FilesTableView = ({
|
|||||||
[application],
|
[application],
|
||||||
)
|
)
|
||||||
|
|
||||||
const [contextMenuFile, setContextMenuFile] = useState<FileItem | undefined>(undefined)
|
const [contextMenuItem, setContextMenuItem] = useState<DecryptedItemInterface | undefined>(undefined)
|
||||||
const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | undefined>(undefined)
|
const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | undefined>(undefined)
|
||||||
|
|
||||||
const isSmallBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
const isSmallBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||||
const isMediumBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.md)
|
const isMediumBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.md)
|
||||||
const isLargeBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.lg)
|
const isLargeBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.lg)
|
||||||
|
|
||||||
const columnDefs: TableColumn<FileItem>[] = useMemo(
|
const columnDefs: TableColumn<DecryptedItemInterface>[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
sortBy: 'title',
|
sortBy: 'title',
|
||||||
cell: (file) => <FileNameCell file={file} />,
|
cell: (item) => <ItemNameCell item={item} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Upload date',
|
name: 'Upload date',
|
||||||
sortBy: 'created_at',
|
sortBy: 'created_at',
|
||||||
cell: (file) => {
|
cell: (item) => {
|
||||||
return formatDateForContextMenu(file.created_at)
|
return formatDateForContextMenu(item.created_at)
|
||||||
},
|
},
|
||||||
hidden: isSmallBreakpoint,
|
hidden: isSmallBreakpoint,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Size',
|
name: 'Size',
|
||||||
sortBy: 'decryptedSize',
|
sortBy: 'decryptedSize',
|
||||||
cell: (file) => {
|
cell: (item) => {
|
||||||
return formatSizeToReadableString(file.decryptedSize)
|
return item instanceof FileItem ? formatSizeToReadableString(item.decryptedSize) : null
|
||||||
},
|
},
|
||||||
hidden: isSmallBreakpoint,
|
hidden: isSmallBreakpoint || !listHasFiles,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Attached to',
|
name: 'Attached to',
|
||||||
hidden: isSmallBreakpoint || isMediumBreakpoint || isLargeBreakpoint,
|
hidden: isSmallBreakpoint || isMediumBreakpoint || isLargeBreakpoint,
|
||||||
cell: (file) => {
|
cell: (item) => {
|
||||||
const links = [
|
const links = [
|
||||||
...naturalSort(application.items.referencesForItem(file), 'title').map((item) =>
|
...naturalSort(application.items.referencesForItem(item), 'title').map((item) =>
|
||||||
createLinkFromItem(item, 'linked'),
|
createLinkFromItem(item, 'linked'),
|
||||||
),
|
),
|
||||||
...naturalSort(application.items.itemsReferencingItem(file), 'title').map((item) =>
|
...naturalSort(application.items.itemsReferencingItem(item), 'title').map((item) =>
|
||||||
createLinkFromItem(item, 'linked-by'),
|
createLinkFromItem(item, 'linked-by'),
|
||||||
),
|
),
|
||||||
...application.items.getSortedTagsForItem(file).map((item) => createLinkFromItem(item, 'linked')),
|
...application.items.getSortedTagsForItem(item).map((item) => createLinkFromItem(item, 'linked')),
|
||||||
]
|
]
|
||||||
|
|
||||||
if (!links.length) {
|
if (!links.length) {
|
||||||
@@ -286,7 +340,7 @@ const FilesTableView = ({
|
|||||||
link={links[0]}
|
link={links[0]}
|
||||||
key={links[0].id}
|
key={links[0].id}
|
||||||
unlinkItem={async (itemToUnlink) => {
|
unlinkItem={async (itemToUnlink) => {
|
||||||
void application.items.unlinkItems(file, itemToUnlink)
|
void application.items.unlinkItems(item, itemToUnlink)
|
||||||
}}
|
}}
|
||||||
isBidirectional={false}
|
isBidirectional={false}
|
||||||
/>
|
/>
|
||||||
@@ -296,13 +350,13 @@ const FilesTableView = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[application.items, isLargeBreakpoint, isMediumBreakpoint, isSmallBreakpoint],
|
[application.items, isLargeBreakpoint, isMediumBreakpoint, isSmallBreakpoint, listHasFiles],
|
||||||
)
|
)
|
||||||
|
|
||||||
const getRowId = useCallback((file: FileItem) => file.uuid, [])
|
const getRowId = useCallback((item: DecryptedItemInterface) => item.uuid, [])
|
||||||
|
|
||||||
const table = useTable({
|
const table = useTable({
|
||||||
data: files,
|
data: items,
|
||||||
sortBy,
|
sortBy,
|
||||||
sortReversed,
|
sortReversed,
|
||||||
onSortChange,
|
onSortChange,
|
||||||
@@ -310,80 +364,103 @@ const FilesTableView = ({
|
|||||||
columns: columnDefs,
|
columns: columnDefs,
|
||||||
enableRowSelection: true,
|
enableRowSelection: true,
|
||||||
enableMultipleRowSelection: true,
|
enableMultipleRowSelection: true,
|
||||||
onRowActivate(file) {
|
onRowActivate(item) {
|
||||||
void filesController.handleFileAction({
|
if (item instanceof FileItem) {
|
||||||
type: FileItemActionType.PreviewFile,
|
void filesController.handleFileAction({
|
||||||
payload: {
|
type: FileItemActionType.PreviewFile,
|
||||||
file,
|
payload: {
|
||||||
otherFiles: files,
|
file: item,
|
||||||
},
|
otherFiles: items.filter((i) => i instanceof FileItem) as FileItem[],
|
||||||
})
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onRowContextMenu(x, y, file) {
|
onRowContextMenu(x, y, file) {
|
||||||
setContextMenuPosition({ x, y })
|
setContextMenuPosition({ x, y })
|
||||||
setContextMenuFile(file)
|
setContextMenuItem(file)
|
||||||
},
|
},
|
||||||
rowActions: (file) => {
|
rowActions: (item) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FileLinksCell
|
<ItemLinksCell
|
||||||
file={file}
|
item={item}
|
||||||
filesController={filesController}
|
filesController={filesController}
|
||||||
featuresController={featuresController}
|
featuresController={featuresController}
|
||||||
linkingController={linkingController}
|
linkingController={linkingController}
|
||||||
/>
|
/>
|
||||||
<ContextMenuCell
|
<ContextMenuCell
|
||||||
files={[file]}
|
items={[item]}
|
||||||
filesController={filesController}
|
filesController={filesController}
|
||||||
linkingController={linkingController}
|
linkingController={linkingController}
|
||||||
navigationController={navigationController}
|
navigationController={navigationController}
|
||||||
|
notesController={notesController}
|
||||||
|
historyModalController={historyModalController}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
selectionActions: (fileIds) => (
|
selectionActions: (itemIds) => (
|
||||||
<ContextMenuCell
|
<ContextMenuCell
|
||||||
files={files.filter((file) => fileIds.includes(file.uuid))}
|
items={items.filter((item) => itemIds.includes(item.uuid))}
|
||||||
filesController={filesController}
|
filesController={filesController}
|
||||||
linkingController={linkingController}
|
linkingController={linkingController}
|
||||||
navigationController={navigationController}
|
navigationController={navigationController}
|
||||||
|
notesController={notesController}
|
||||||
|
historyModalController={historyModalController}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
showSelectionActions: true,
|
showSelectionActions: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const closeContextMenu = () => {
|
||||||
|
setContextMenuPosition(undefined)
|
||||||
|
setContextMenuItem(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Table table={table} />
|
<Table table={table} />
|
||||||
{contextMenuPosition && contextMenuFile && (
|
{contextMenuPosition && contextMenuItem && (
|
||||||
<Popover
|
<Popover
|
||||||
open={true}
|
open={true}
|
||||||
anchorPoint={contextMenuPosition}
|
anchorPoint={contextMenuPosition}
|
||||||
togglePopover={() => {
|
togglePopover={() => {
|
||||||
setContextMenuPosition(undefined)
|
setContextMenuPosition(undefined)
|
||||||
setContextMenuFile(undefined)
|
setContextMenuItem(undefined)
|
||||||
}}
|
}}
|
||||||
side="bottom"
|
side="bottom"
|
||||||
align="start"
|
align="start"
|
||||||
className="py-2"
|
className="py-2"
|
||||||
>
|
>
|
||||||
<Menu a11yLabel="File context menu" isOpen={true}>
|
{contextMenuItem instanceof FileItem && (
|
||||||
<FileMenuOptions
|
<Menu a11yLabel="File context menu" isOpen={true}>
|
||||||
closeMenu={() => {
|
<FileMenuOptions
|
||||||
setContextMenuPosition(undefined)
|
closeMenu={closeContextMenu}
|
||||||
setContextMenuFile(undefined)
|
filesController={filesController}
|
||||||
}}
|
shouldShowRenameOption={false}
|
||||||
filesController={filesController}
|
shouldShowAttachOption={false}
|
||||||
shouldShowRenameOption={false}
|
selectedFiles={[contextMenuItem]}
|
||||||
shouldShowAttachOption={false}
|
linkingController={linkingController}
|
||||||
selectedFiles={[contextMenuFile]}
|
navigationController={navigationController}
|
||||||
linkingController={linkingController}
|
/>
|
||||||
navigationController={navigationController}
|
</Menu>
|
||||||
/>
|
)}
|
||||||
</Menu>
|
{contextMenuItem instanceof SNNote && (
|
||||||
|
<Menu className="select-none" a11yLabel="Note context menu" isOpen={true}>
|
||||||
|
<NotesOptions
|
||||||
|
notes={[contextMenuItem]}
|
||||||
|
application={application}
|
||||||
|
navigationController={navigationController}
|
||||||
|
notesController={notesController}
|
||||||
|
linkingController={linkingController}
|
||||||
|
historyModalController={historyModalController}
|
||||||
|
closeMenu={closeContextMenu}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
</Popover>
|
</Popover>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default FilesTableView
|
export default ContentTableView
|
||||||
@@ -47,6 +47,7 @@ const NotesContextMenu = ({
|
|||||||
>
|
>
|
||||||
<Menu className="select-none" a11yLabel="Note context menu" isOpen={contextMenuOpen}>
|
<Menu className="select-none" a11yLabel="Note context menu" isOpen={contextMenuOpen}>
|
||||||
<NotesOptions
|
<NotesOptions
|
||||||
|
notes={notesController.selectedNotes}
|
||||||
application={application}
|
application={application}
|
||||||
navigationController={navigationController}
|
navigationController={navigationController}
|
||||||
notesController={notesController}
|
notesController={notesController}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const iconClassWarning = `text-warning mr-2 ${iconSize}`
|
|||||||
const iconClassSuccess = `text-success mr-2 ${iconSize}`
|
const iconClassSuccess = `text-success mr-2 ${iconSize}`
|
||||||
|
|
||||||
const NotesOptions = ({
|
const NotesOptions = ({
|
||||||
|
notes,
|
||||||
application,
|
application,
|
||||||
navigationController,
|
navigationController,
|
||||||
notesController,
|
notesController,
|
||||||
@@ -61,7 +62,6 @@ const NotesOptions = ({
|
|||||||
return notesMatchingAttribute.length > notesNotMatchingAttribute.length
|
return notesMatchingAttribute.length > notesNotMatchingAttribute.length
|
||||||
}
|
}
|
||||||
|
|
||||||
const notes = notesController.selectedNotes
|
|
||||||
const hidePreviews = toggleOn((note) => note.hidePreview)
|
const hidePreviews = toggleOn((note) => note.hidePreview)
|
||||||
const locked = toggleOn((note) => note.locked)
|
const locked = toggleOn((note) => note.locked)
|
||||||
const protect = toggleOn((note) => note.protected)
|
const protect = toggleOn((note) => note.protected)
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ const NotesOptionsPanel = ({
|
|||||||
>
|
>
|
||||||
<Menu a11yLabel="Note options menu" isOpen={isOpen}>
|
<Menu a11yLabel="Note options menu" isOpen={isOpen}>
|
||||||
<NotesOptions
|
<NotesOptions
|
||||||
|
notes={notesController.selectedNotes}
|
||||||
application={application}
|
application={application}
|
||||||
navigationController={navigationController}
|
navigationController={navigationController}
|
||||||
notesController={notesController}
|
notesController={notesController}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalCo
|
|||||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
import { LinkingController } from '@/Controllers/LinkingController'
|
import { LinkingController } from '@/Controllers/LinkingController'
|
||||||
|
import { SNNote } from '@standardnotes/snjs'
|
||||||
|
|
||||||
export type NotesOptionsProps = {
|
export type NotesOptionsProps = {
|
||||||
|
notes: SNNote[]
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
navigationController: NavigationController
|
navigationController: NavigationController
|
||||||
notesController: NotesController
|
notesController: NotesController
|
||||||
|
|||||||
@@ -304,6 +304,7 @@ const PanesSystemComponent = () => {
|
|||||||
searchOptionsController={viewControllerManager.searchOptionsController}
|
searchOptionsController={viewControllerManager.searchOptionsController}
|
||||||
linkingController={viewControllerManager.linkingController}
|
linkingController={viewControllerManager.linkingController}
|
||||||
featuresController={viewControllerManager.featuresController}
|
featuresController={viewControllerManager.featuresController}
|
||||||
|
historyModalController={viewControllerManager.historyModalController}
|
||||||
>
|
>
|
||||||
{showPanelResizers && listRef && (
|
{showPanelResizers && listRef && (
|
||||||
<PanelResizer
|
<PanelResizer
|
||||||
|
|||||||
@@ -162,7 +162,8 @@ function Table<Data>({ table }: { table: Table<Data> }) {
|
|||||||
const currentRow = Array.from(allRenderedRows).find(
|
const currentRow = Array.from(allRenderedRows).find(
|
||||||
(row) => row.getAttribute('aria-rowindex') === focusedRowIndex.current.toString(),
|
(row) => row.getAttribute('aria-rowindex') === focusedRowIndex.current.toString(),
|
||||||
)
|
)
|
||||||
const allFocusableCells = currentRow?.querySelectorAll<HTMLElement>('[tabindex]')
|
const allFocusableCells = Array.from(currentRow ? currentRow.querySelectorAll<HTMLElement>('[tabindex]') : [])
|
||||||
|
const allRenderedColumnsLength = headers.length
|
||||||
|
|
||||||
const focusCell = (rowIndex: number, colIndex: number) => {
|
const focusCell = (rowIndex: number, colIndex: number) => {
|
||||||
const row = gridElement.querySelector(`[role="row"][aria-rowindex="${rowIndex}"]`)
|
const row = gridElement.querySelector(`[role="row"][aria-rowindex="${rowIndex}"]`)
|
||||||
@@ -190,20 +191,42 @@ function Table<Data>({ table }: { table: Table<Data> }) {
|
|||||||
focusCell(nextRow, focusedCellIndex.current)
|
focusCell(nextRow, focusedCellIndex.current)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case KeyboardKey.Left:
|
case KeyboardKey.Left: {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (focusedCellIndex.current > 1) {
|
if (!allFocusableCells) {
|
||||||
const previousCell = focusedCellIndex.current - 1
|
return
|
||||||
focusCell(focusedRowIndex.current, previousCell)
|
|
||||||
}
|
}
|
||||||
|
const currentCellIndex = allFocusableCells.findIndex(
|
||||||
|
(cell) => parseInt(cell.getAttribute('aria-colindex') || '0') === focusedCellIndex.current,
|
||||||
|
)
|
||||||
|
if (currentCellIndex === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const previousCell = allFocusableCells[currentCellIndex - 1]
|
||||||
|
if (!previousCell) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
previousCell.focus()
|
||||||
break
|
break
|
||||||
case KeyboardKey.Right:
|
}
|
||||||
|
case KeyboardKey.Right: {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (focusedCellIndex.current < colCount) {
|
if (!allFocusableCells) {
|
||||||
const nextCell = focusedCellIndex.current + 1
|
return
|
||||||
focusCell(focusedRowIndex.current, nextCell)
|
|
||||||
}
|
}
|
||||||
|
const currentCellIndex = allFocusableCells.findIndex(
|
||||||
|
(cell) => parseInt(cell.getAttribute('aria-colindex') || '0') === focusedCellIndex.current,
|
||||||
|
)
|
||||||
|
if (currentCellIndex === allFocusableCells.length - 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const nextCell = allFocusableCells[currentCellIndex + 1]
|
||||||
|
if (!nextCell) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nextCell.focus()
|
||||||
break
|
break
|
||||||
|
}
|
||||||
case KeyboardKey.Home:
|
case KeyboardKey.Home:
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (event.ctrlKey) {
|
if (event.ctrlKey) {
|
||||||
@@ -225,7 +248,7 @@ function Table<Data>({ table }: { table: Table<Data> }) {
|
|||||||
case KeyboardKey.End: {
|
case KeyboardKey.End: {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (event.ctrlKey) {
|
if (event.ctrlKey) {
|
||||||
focusCell(allRenderedRows.length, headers.length || colCount)
|
focusCell(allRenderedRows.length, allRenderedColumnsLength || colCount)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!allFocusableCells) {
|
if (!allFocusableCells) {
|
||||||
@@ -338,7 +361,7 @@ function Table<Data>({ table }: { table: Table<Data> }) {
|
|||||||
return (
|
return (
|
||||||
<div className="block min-h-0 overflow-auto" onScroll={onScroll}>
|
<div className="block min-h-0 overflow-auto" onScroll={onScroll}>
|
||||||
{showSelectionActions && selectedRows.length >= 2 && (
|
{showSelectionActions && selectedRows.length >= 2 && (
|
||||||
<div className="flex items-center justify-between border-b border-border px-3 py-2">
|
<div className="sticky top-0 z-[2] flex items-center justify-between border-b border-border bg-default px-3 py-2">
|
||||||
<span className="text-info-0 text-sm font-medium">{selectedRows.length} selected</span>
|
<span className="text-info-0 text-sm font-medium">{selectedRows.length} selected</span>
|
||||||
{selectedRows.length > 0 && selectionActions}
|
{selectedRows.length > 0 && selectionActions}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user