diff --git a/packages/web/src/javascripts/Components/Icon/IconPicker.tsx b/packages/web/src/javascripts/Components/Icon/IconPicker.tsx index a9f660bcf..d05ae93fe 100644 --- a/packages/web/src/javascripts/Components/Icon/IconPicker.tsx +++ b/packages/web/src/javascripts/Components/Icon/IconPicker.tsx @@ -7,6 +7,7 @@ import { getEmojiLength } from './EmojiLength' import Icon, { isIconEmoji } from './Icon' import { IconNameToSvgMapping } from './IconNameToSvgMapping' import { IconPickerType } from './IconPickerType' +import DecoratedInput from '../Input/DecoratedInput' type Props = { selectedValue: VectorIconNameOrEmoji @@ -114,13 +115,13 @@ const IconPicker = ({ selectedValue, onIconChange, platform, className, useIconG }, []) return ( -
+
-
+
{currentType === 'icon' && (useIconGrid ? (
-
- handleEmojiChange((input as HTMLInputElement)?.value)} - /> -
+ handleEmojiChange(value)} + />
Use your keyboard to enter or paste in an emoji character.
diff --git a/packages/web/src/javascripts/Components/Tags/TagContextMenu.tsx b/packages/web/src/javascripts/Components/Tags/TagContextMenu.tsx index 9f93c4bc9..0773c414e 100644 --- a/packages/web/src/javascripts/Components/Tags/TagContextMenu.tsx +++ b/packages/web/src/javascripts/Components/Tags/TagContextMenu.tsx @@ -1,5 +1,5 @@ import { observer } from 'mobx-react-lite' -import { useCallback, useMemo } from 'react' +import { useCallback, useMemo, useRef } from 'react' import Icon from '@/Components/Icon/Icon' import Menu from '@/Components/Menu/Menu' import MenuItem from '@/Components/Menu/MenuItem' @@ -14,6 +14,8 @@ import IconPicker from '../Icon/IconPicker' import AddToVaultMenuOption from '../Vaults/AddToVaultMenuOption' import { useApplication } from '../ApplicationProvider' import MenuSection from '../Menu/MenuSection' +import DecoratedInput from '../Input/DecoratedInput' +import { KeyboardKey } from '@standardnotes/ui-services' type ContextMenuProps = { navigationController: NavigationController @@ -38,11 +40,6 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag navigationController.setAddingSubtagTo(selectedTag) }, [isEntitledToFolders, navigationController, selectedTag, premiumModal]) - const onClickRename = useCallback(() => { - navigationController.setContextMenuOpen(false) - navigationController.setEditingTag(selectedTag) - }, [navigationController, selectedTag]) - const onClickDelete = useCallback(() => { navigationController.remove(selectedTag, true).catch(console.error) }, [navigationController, selectedTag]) @@ -63,6 +60,26 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag const tagCreatedAt = useMemo(() => formatDateForContextMenu(selectedTag.created_at), [selectedTag.created_at]) + const titleInputRef = useRef(null) + + const saveTitle = useCallback( + (closeMenu = false) => { + if (!titleInputRef.current) { + return + } + const value = titleInputRef.current.value.trim() + navigationController + .save(selectedTag, value) + .catch(console.error) + .finally(() => { + if (closeMenu) { + navigationController.setContextMenuOpen(false) + } + }) + }, + [navigationController, selectedTag], + ) + return ( navigationController.setContextMenuOpen(!contextMenuOpen)} className="py-2" > +
+
Name
+
+ saveTitle()} + onKeyDown={(event) => { + if (event.key === KeyboardKey.Enter) { + saveTitle(true) + } + }} + /> + +
+
+ @@ -98,10 +143,6 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag
{!isEntitledToFolders && } - - - Rename - Delete @@ -109,7 +150,7 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag -
+
Last modified: {tagLastModified}
diff --git a/packages/web/src/javascripts/Components/Tags/TagContextMenuWrapper.tsx b/packages/web/src/javascripts/Components/Tags/TagContextMenuWrapper.tsx index 059518b65..4ef56cccc 100644 --- a/packages/web/src/javascripts/Components/Tags/TagContextMenuWrapper.tsx +++ b/packages/web/src/javascripts/Components/Tags/TagContextMenuWrapper.tsx @@ -10,7 +10,7 @@ type Props = { } const TagContextMenuWrapper = ({ navigationController, featuresController }: Props) => { - const selectedTag = navigationController.selected + const selectedTag = navigationController.contextMenuTag if (!selectedTag || !(selectedTag instanceof SNTag)) { return null diff --git a/packages/web/src/javascripts/Components/Tags/TagsList.tsx b/packages/web/src/javascripts/Components/Tags/TagsList.tsx index a1b176dd4..906108e65 100644 --- a/packages/web/src/javascripts/Components/Tags/TagsList.tsx +++ b/packages/web/src/javascripts/Components/Tags/TagsList.tsx @@ -17,25 +17,19 @@ const TagsList: FunctionComponent = ({ type }: Props) => { type === 'all' ? application.navigationController.allLocalRootTags : application.navigationController.starredTags const openTagContextMenu = useCallback( - (posX: number, posY: number) => { - application.navigationController.setContextMenuClickLocation({ - x: posX, - y: posY, - }) - application.navigationController.reloadContextMenuLayout() + (x: number, y: number) => { + application.navigationController.setContextMenuClickLocation({ x, y }) application.navigationController.setContextMenuOpen(true) }, [application], ) const onContextMenu = useCallback( - (tag: SNTag, posX: number, posY: number) => { - if (application.navigationController.selected !== tag) { - void application.navigationController.setSelectedTag(tag, type) - } + (tag: SNTag, section: TagListSectionType, posX: number, posY: number) => { + application.navigationController.setContextMenuTag(tag, section) openTagContextMenu(posX, posY) }, - [application, openTagContextMenu, type], + [application, openTagContextMenu], ) return ( diff --git a/packages/web/src/javascripts/Components/Tags/TagsListItem.tsx b/packages/web/src/javascripts/Components/Tags/TagsListItem.tsx index 34338dce9..83c990da9 100644 --- a/packages/web/src/javascripts/Components/Tags/TagsListItem.tsx +++ b/packages/web/src/javascripts/Components/Tags/TagsListItem.tsx @@ -34,7 +34,7 @@ type Props = { features: FeaturesController linkingController: LinkingController level: number - onContextMenu: (tag: SNTag, posX: number, posY: number) => void + onContextMenu: (tag: SNTag, section: TagListSectionType, posX: number, posY: number) => void } const PADDING_BASE_PX = 14 @@ -50,9 +50,17 @@ export const TagsListItem: FunctionComponent = observer( const subtagInputRef = useRef(null) const menuButtonRef = useRef(null) + const isContextMenuOpenForTag = + navigationController.contextMenuTag === tag && + navigationController.contextMenuOpen && + navigationController.contextMenuTagSection === type const isSelected = navigationController.selected === tag && navigationController.selectedLocation === type const isEditing = navigationController.editingTag === tag && navigationController.selectedLocation === type - const isAddingSubtag = navigationController.addingSubtagTo === tag && navigationController.selectedLocation === type + const isAddingSubtag = + navigationController.addingSubtagTo === tag && + (navigationController.contextMenuTag === tag + ? navigationController.contextMenuTagSection === type + : navigationController.selectedLocation === type) const noteCounts = computed(() => navigationController.getNotesCount(tag)) const childrenTags = computed(() => navigationController.getChildren(tag)).get() @@ -164,10 +172,10 @@ export const TagsListItem: FunctionComponent = observer( if (contextMenuOpen) { navigationController.setContextMenuOpen(false) } else { - onContextMenu(tag, menuButtonRect.right, menuButtonRect.top) + onContextMenu(tag, type, menuButtonRect.right, menuButtonRect.top) } }, - [onContextMenu, navigationController, tag], + [navigationController, onContextMenu, tag, type], ) const tagRef = useRef(null) @@ -262,7 +270,7 @@ export const TagsListItem: FunctionComponent = observer( tabIndex={FOCUSABLE_BUT_NOT_TABBABLE} className={classNames( 'tag group px-3.5 py-1 md:py-0', - isSelected && 'selected', + (isSelected || isContextMenuOpenForTag) && 'selected', isBeingDraggedOver && 'is-drag-over', )} onClick={selectCurrentTag} @@ -272,7 +280,7 @@ export const TagsListItem: FunctionComponent = observer( }} onContextMenu={(e) => { e.preventDefault() - onContextMenu(tag, e.clientX, e.clientY) + onContextMenu(tag, type, e.clientX, e.clientY) }} draggable={true} onDragStart={onDragStart} diff --git a/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts b/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts index 334acb181..d6cfeb632 100644 --- a/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts +++ b/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts @@ -7,7 +7,7 @@ import { VaultDisplayServiceEvent, } from '@standardnotes/ui-services' import { STRING_DELETE_TAG } from '@/Constants/Strings' -import { MAX_MENU_SIZE_MULTIPLIER, MENU_MARGIN_FROM_APP_BORDER, SMART_TAGS_FEATURE_NAME } from '@/Constants/Constants' +import { SMART_TAGS_FEATURE_NAME } from '@/Constants/Constants' import { ContentType, SmartView, @@ -61,12 +61,9 @@ export class NavigationController addingSubtagTo: SNTag | undefined = undefined contextMenuOpen = false - contextMenuPosition: { top?: number; left: number; bottom?: number } = { - top: 0, - left: 0, - } contextMenuClickLocation: { x: number; y: number } = { x: 0, y: 0 } - contextMenuMaxHeight: number | 'auto' = 'auto' + contextMenuTag: SNTag | undefined = undefined + contextMenuTagSection: TagListSectionType | undefined = undefined private readonly tagsCountsState: TagsCountsState @@ -124,13 +121,11 @@ export class NavigationController remove: action, contextMenuOpen: observable, - contextMenuPosition: observable, - contextMenuMaxHeight: observable, contextMenuClickLocation: observable, setContextMenuOpen: action, setContextMenuClickLocation: action, - setContextMenuPosition: action, - setContextMenuMaxHeight: action, + contextMenuTag: observable, + setContextMenuTag: action, isInFilesView: computed, @@ -356,58 +351,9 @@ export class NavigationController this.contextMenuClickLocation = location } - setContextMenuPosition(position: { top?: number; left: number; bottom?: number }): void { - this.contextMenuPosition = position - } - - setContextMenuMaxHeight(maxHeight: number | 'auto'): void { - this.contextMenuMaxHeight = maxHeight - } - - reloadContextMenuLayout(): void { - const { clientHeight } = document.documentElement - const defaultFontSize = window.getComputedStyle(document.documentElement).fontSize - const maxContextMenuHeight = parseFloat(defaultFontSize) * MAX_MENU_SIZE_MULTIPLIER - const footerElementRect = document.getElementById('footer-bar')?.getBoundingClientRect() - const footerHeightInPx = footerElementRect?.height - - let openUpBottom = true - - if (footerHeightInPx) { - const bottomSpace = clientHeight - footerHeightInPx - this.contextMenuClickLocation.y - const upSpace = this.contextMenuClickLocation.y - - const notEnoughSpaceToOpenUpBottom = maxContextMenuHeight > bottomSpace - if (notEnoughSpaceToOpenUpBottom) { - const enoughSpaceToOpenBottomUp = upSpace > maxContextMenuHeight - if (enoughSpaceToOpenBottomUp) { - openUpBottom = false - this.setContextMenuMaxHeight('auto') - } else { - const hasMoreUpSpace = upSpace > bottomSpace - if (hasMoreUpSpace) { - this.setContextMenuMaxHeight(upSpace - MENU_MARGIN_FROM_APP_BORDER) - openUpBottom = false - } else { - this.setContextMenuMaxHeight(bottomSpace - MENU_MARGIN_FROM_APP_BORDER) - } - } - } else { - this.setContextMenuMaxHeight('auto') - } - } - - if (openUpBottom) { - this.setContextMenuPosition({ - top: this.contextMenuClickLocation.y, - left: this.contextMenuClickLocation.x, - }) - } else { - this.setContextMenuPosition({ - bottom: clientHeight - this.contextMenuClickLocation.y, - left: this.contextMenuClickLocation.x, - }) - } + setContextMenuTag(tag: SNTag | undefined, section: TagListSectionType = 'all'): void { + this.contextMenuTag = tag + this.contextMenuTagSection = section } public get allLocalRootTags(): SNTag[] { @@ -640,6 +586,7 @@ export class NavigationController let shouldDelete = !userTriggered if (userTriggered) { shouldDelete = await confirmDialog({ + title: `Delete tag "${tag.title}"?`, text: STRING_DELETE_TAG, confirmButtonStyle: 'danger', }) @@ -654,11 +601,13 @@ export class NavigationController } public async save(tag: SNTag | SmartView, newTitle: string) { - const hasEmptyTitle = newTitle.length === 0 - const hasNotChangedTitle = newTitle === tag.title - const isTemplateChange = this.items.isTemplateItem(tag) + const latestVersion = this.items.findSureItem(tag.uuid) - const siblings = tag instanceof SNTag ? tagSiblings(this.items, tag) : [] + const hasEmptyTitle = newTitle.length === 0 + const hasNotChangedTitle = newTitle === latestVersion.title + const isTemplateChange = this.items.isTemplateItem(latestVersion) + + const siblings = latestVersion instanceof SNTag ? tagSiblings(this.items, latestVersion) : [] const hasDuplicatedTitle = siblings.some((other) => other.title.toLowerCase() === newTitle.toLowerCase()) runInAction(() => { @@ -699,7 +648,7 @@ export class NavigationController void this.setSelectedTag(insertedTag, this.selectedLocation || 'views') }) } else { - await this._changeAndSaveItem.execute(tag, (mutator) => { + await this._changeAndSaveItem.execute(latestVersion, (mutator) => { mutator.title = newTitle }) }