diff --git a/packages/web/src/javascripts/Components/ContentListView/NoteListItem.tsx b/packages/web/src/javascripts/Components/ContentListView/NoteListItem.tsx index 5e104f93f..6ca75a73f 100644 --- a/packages/web/src/javascripts/Components/ContentListView/NoteListItem.tsx +++ b/packages/web/src/javascripts/Components/ContentListView/NoteListItem.tsx @@ -13,6 +13,8 @@ import { ListItemTitle } from './ListItemTitle' import { log, LoggingDomain } from '@/Logging' import { classNames } from '@standardnotes/utils' import { getIconAndTintForNoteType } from '@/Utils/Items/Icons/getIconAndTintForNoteType' +import { NoteDragDataFormat } from '../Tags/DragNDrop' +import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery' const NoteListItem: FunctionComponent> = ({ application, @@ -70,6 +72,34 @@ const NoteListItem: FunctionComponent> = ({ const hasOffsetBorder = !isNextItemTiled + const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm) + const dragPreview = useRef() + + const createDragPreview = () => { + if (!listItemRef.current) { + throw new Error('List item ref is not set') + } + + const element = listItemRef.current.cloneNode(true) + // Only keep icon & title in drag preview + Array.from(element.childNodes[1].childNodes).forEach((node, key) => { + if (key !== 0) { + node.remove() + } + }) + element.childNodes[2].remove() + if (element instanceof HTMLDivElement) { + element.style.width = `${listItemRef.current.clientWidth}px` + element.style.position = 'absolute' + element.style.top = '0' + element.style.left = '0' + element.style.zIndex = '-100000' + document.body.appendChild(element) + dragPreview.current = element + } + return element as HTMLDivElement + } + return (
> = ({ )} id={item.uuid} onClick={onClick} + draggable={!isMobileScreen} + onDragStart={(event) => { + if (!listItemRef.current) { + return + } + + const { dataTransfer } = event + + const element = createDragPreview() + dataTransfer.setDragImage(element, 0, 0) + dataTransfer.setData(NoteDragDataFormat, item.uuid) + }} + onDragLeave={() => { + if (dragPreview.current) { + dragPreview.current.remove() + } + }} > {!hideIcon ? (
diff --git a/packages/web/src/javascripts/Components/Tags/DragNDrop.ts b/packages/web/src/javascripts/Components/Tags/DragNDrop.ts index 6ecfb1dc9..098a040a8 100644 --- a/packages/web/src/javascripts/Components/Tags/DragNDrop.ts +++ b/packages/web/src/javascripts/Components/Tags/DragNDrop.ts @@ -1 +1,2 @@ export const TagDragDataFormat = 'application/x-sn-drag-tag' +export const NoteDragDataFormat = 'application/x-sn-drag-note' diff --git a/packages/web/src/javascripts/Components/Tags/TagsListItem.tsx b/packages/web/src/javascripts/Components/Tags/TagsListItem.tsx index fd62d7b2a..d4d3cfd8c 100644 --- a/packages/web/src/javascripts/Components/Tags/TagsListItem.tsx +++ b/packages/web/src/javascripts/Components/Tags/TagsListItem.tsx @@ -3,7 +3,7 @@ import { FOCUSABLE_BUT_NOT_TABBABLE, TAG_FOLDERS_FEATURE_NAME } from '@/Constant import { KeyboardKey } from '@standardnotes/ui-services' import { FeaturesController } from '@/Controllers/FeaturesController' import { NavigationController } from '@/Controllers/Navigation/NavigationController' -import { IconType, SNTag } from '@standardnotes/snjs' +import { IconType, SNNote, SNTag } from '@standardnotes/snjs' import { computed } from 'mobx' import { observer } from 'mobx-react-lite' import { @@ -23,8 +23,9 @@ import { useFileDragNDrop } from '../FileDragNDropProvider' import { LinkingController } from '@/Controllers/LinkingController' import { TagListSectionType } from './TagListSection' import { log, LoggingDomain } from '@/Logging' -import { TagDragDataFormat } from './DragNDrop' +import { NoteDragDataFormat, TagDragDataFormat } from './DragNDrop' import { usePremiumModal } from '@/Hooks/usePremiumModal' +import { useApplication } from '../ApplicationProvider' type Props = { tag: SNTag @@ -40,7 +41,9 @@ const PADDING_BASE_PX = 14 const PADDING_PER_LEVEL_PX = 21 export const TagsListItem: FunctionComponent = observer( - ({ tag, type, features, navigationController: navigationController, level, onContextMenu, linkingController }) => { + ({ tag, type, features, navigationController, level, onContextMenu, linkingController }) => { + const application = useApplication() + const [title, setTitle] = useState(tag.title || '') const [subtagTitle, setSubtagTitle] = useState('') const inputRef = useRef(null) @@ -200,7 +203,10 @@ export const TagsListItem: FunctionComponent = observer( ) const onDragEnter: DragEventHandler = useCallback((event): void => { - if (event.dataTransfer.types.includes(TagDragDataFormat)) { + if ( + event.dataTransfer.types.includes(TagDragDataFormat) || + event.dataTransfer.types.includes(NoteDragDataFormat) + ) { event.preventDefault() setIsBeingDraggedOver(true) } @@ -211,30 +217,42 @@ export const TagsListItem: FunctionComponent = observer( }, []) const onDragOver: DragEventHandler = useCallback((event): void => { - if (event.dataTransfer.types.includes(TagDragDataFormat)) { + if ( + event.dataTransfer.types.includes(TagDragDataFormat) || + event.dataTransfer.types.includes(NoteDragDataFormat) + ) { event.preventDefault() } }, []) const onDrop: DragEventHandler = useCallback( - (event): void => { + async (event) => { setIsBeingDraggedOver(false) const draggedTagUuid = event.dataTransfer.getData(TagDragDataFormat) - if (!draggedTagUuid) { - return - } - if (!navigationController.isValidTagParent(tag, { uuid: draggedTagUuid } as SNTag)) { - return - } - if (!hasFolders) { - premiumModal.activate(TAG_FOLDERS_FEATURE_NAME) - return - } + const draggedNoteUuid = event.dataTransfer.getData(NoteDragDataFormat) if (draggedTagUuid) { + if (!navigationController.isValidTagParent(tag, { uuid: draggedTagUuid } as SNTag)) { + return + } + if (!hasFolders) { + premiumModal.activate(TAG_FOLDERS_FEATURE_NAME) + return + } + void navigationController.assignParent(draggedTagUuid, tag.uuid) + return + } else if (draggedNoteUuid) { + const currentTag = navigationController.selected + const shouldSwapTags = currentTag instanceof SNTag && currentTag.uuid !== tag.uuid + const note = application.items.findSureItem(draggedNoteUuid) + await linkingController.linkItems(note, tag) + if (shouldSwapTags) { + await linkingController.unlinkItems(note, currentTag) + } + return } }, - [hasFolders, navigationController, premiumModal, tag], + [application.items, hasFolders, linkingController, navigationController, premiumModal, tag], ) return (