From dd821c95e61c8cd82317ad9fb3788daff7241942 Mon Sep 17 00:00:00 2001 From: Mo Date: Wed, 2 Nov 2022 11:06:19 -0500 Subject: [PATCH] feat: dedicated files layout (#1928) --- .../src/Domain/Syncable/Tag/TagPreferences.ts | 1 + .../ContentListView/ContentList.tsx | 47 +++++++----- .../ContentListView/ContentListItem.tsx | 9 +-- .../ContentListView/ContentListView.tsx | 36 ++++++---- .../ContentListView/Daily/DailyItemCell.tsx | 4 +- .../ContentListView/FileListItem.tsx | 52 +++++++++----- .../ContentListView/ListItemFlagIcons.tsx | 6 +- .../ListItemNotePreviewText.tsx | 5 +- .../ContentListView/ListItemTags.tsx | 3 +- .../ContentListView/NoteListItem.tsx | 20 ++++-- .../Types/AbstractListItemProps.ts | 21 ++++-- .../Types/DisplayableListItemProps.ts | 3 +- .../Types/ListableContentItem.ts | 13 +--- .../ItemList/ItemListController.ts | 72 ++++++++++++++++--- .../Navigation/NavigationController.ts | 9 +++ .../Controllers/SelectedItemsController.ts | 13 +++- packages/web/src/javascripts/Logging.ts | 4 +- packages/web/src/stylesheets/_sn.scss | 6 ++ 18 files changed, 226 insertions(+), 98 deletions(-) diff --git a/packages/models/src/Domain/Syncable/Tag/TagPreferences.ts b/packages/models/src/Domain/Syncable/Tag/TagPreferences.ts index 6f59ef030..5f85ceb2f 100644 --- a/packages/models/src/Domain/Syncable/Tag/TagPreferences.ts +++ b/packages/models/src/Domain/Syncable/Tag/TagPreferences.ts @@ -17,4 +17,5 @@ export interface TagPreferences { customNoteTitleFormat?: string editorIdentifier?: FeatureIdentifier | string entryMode?: 'normal' | 'daily' + panelWidth?: number } diff --git a/packages/web/src/javascripts/Components/ContentListView/ContentList.tsx b/packages/web/src/javascripts/Components/ContentListView/ContentList.tsx index 9bd7827e0..475c9abb9 100644 --- a/packages/web/src/javascripts/Components/ContentListView/ContentList.tsx +++ b/packages/web/src/javascripts/Components/ContentListView/ContentList.tsx @@ -12,7 +12,7 @@ import { NavigationController } from '@/Controllers/Navigation/NavigationControl import { NotesController } from '@/Controllers/NotesController' import { ElementIds } from '@/Constants/ElementIDs' import { classNames } from '@/Utils/ConcatenateClassNames' -import { SNTag } from '@standardnotes/snjs' +import { ContentType, SNTag } from '@standardnotes/snjs' type Props = { application: WebApplication @@ -95,35 +95,44 @@ const ContentList: FunctionComponent = ({ [hideTags, selectedTag, application], ) + const hasNotes = items.some((item) => item.content_type === ContentType.Note) + return (
- {items.map((item) => ( - - ))} + {items.map((item, index) => { + const previousItem = items[index - 1] + const nextItem = items[index + 1] + return ( + + ) + })}
) } diff --git a/packages/web/src/javascripts/Components/ContentListView/ContentListItem.tsx b/packages/web/src/javascripts/Components/ContentListView/ContentListItem.tsx index 09e6b636b..4221f67df 100644 --- a/packages/web/src/javascripts/Components/ContentListView/ContentListItem.tsx +++ b/packages/web/src/javascripts/Components/ContentListView/ContentListItem.tsx @@ -1,15 +1,16 @@ -import { ContentType } from '@standardnotes/snjs' +import { ContentType, FileItem, SNNote } from '@standardnotes/snjs' import React, { FunctionComponent } from 'react' import FileListItem from './FileListItem' import NoteListItem from './NoteListItem' import { AbstractListItemProps, doListItemPropsMeritRerender } from './Types/AbstractListItemProps' +import { ListableContentItem } from './Types/ListableContentItem' -const ContentListItem: FunctionComponent = (props) => { +const ContentListItem: FunctionComponent> = (props) => { switch (props.item.content_type) { case ContentType.Note: - return + return case ContentType.File: - return + return default: return null } diff --git a/packages/web/src/javascripts/Components/ContentListView/ContentListView.tsx b/packages/web/src/javascripts/Components/ContentListView/ContentListView.tsx index 73f189ba5..a1822d609 100644 --- a/packages/web/src/javascripts/Components/ContentListView/ContentListView.tsx +++ b/packages/web/src/javascripts/Components/ContentListView/ContentListView.tsx @@ -1,7 +1,7 @@ import { KeyboardKey, KeyboardModifier } from '@standardnotes/ui-services' import { WebApplication } from '@/Application/Application' import { PANEL_NAME_NOTES } from '@/Constants/Constants' -import { FileItem, PrefKey, SystemViewId } from '@standardnotes/snjs' +import { FileItem, PrefKey } from '@standardnotes/snjs' import { observer } from 'mobx-react-lite' import { FunctionComponent, useCallback, useEffect, useMemo, useRef } from 'react' import ContentList from '@/Components/ContentListView/ContentList' @@ -118,10 +118,7 @@ const ContentListView: FunctionComponent = ({ const icon = selectedTag?.iconString - const isFilesSmartView = useMemo( - () => navigationController.selected?.uuid === SystemViewId.Files, - [navigationController.selected?.uuid], - ) + const isFilesSmartView = useMemo(() => navigationController.isInFilesView, [navigationController.isInFilesView]) const addNewItem = useCallback(async () => { if (isFilesSmartView) { @@ -215,10 +212,14 @@ const ContentListView: FunctionComponent = ({ const panelResizeFinishCallback: ResizeFinishCallback = useCallback( (width, _lastLeft, _isMaxWidth, isCollapsed) => { - application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error) + if (selectedAsTag) { + navigationController.setPanelWidthForTag(selectedAsTag, width) + } else { + application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error) + } application.publishPanelDidResizeEvent(PANEL_NAME_NOTES, isCollapsed) }, - [application], + [application, selectedAsTag, navigationController], ) const addButtonLabel = useMemo( @@ -243,15 +244,26 @@ const ContentListView: FunctionComponent = ({ [selectionController], ) + useEffect(() => { + const hasEditorPane = selectedUuids.size > 0 + if (!hasEditorPane) { + itemsViewPanelRef.current?.style.removeProperty('width') + } + }, [selectedUuids, itemsViewPanelRef]) + + const hasEditorPane = selectedUuids.size > 0 || renderedItems.length === 0 + return (
= ({ ) : null}
- {itemsViewPanelRef.current && ( + {hasEditorPane && itemsViewPanelRef.current && ( - + {isNote(item) && } diff --git a/packages/web/src/javascripts/Components/ContentListView/FileListItem.tsx b/packages/web/src/javascripts/Components/ContentListView/FileListItem.tsx index 5f9f980d6..0ad59b835 100644 --- a/packages/web/src/javascripts/Components/ContentListView/FileListItem.tsx +++ b/packages/web/src/javascripts/Components/ContentListView/FileListItem.tsx @@ -3,15 +3,16 @@ import { observer } from 'mobx-react-lite' import { FunctionComponent, useCallback, useRef } from 'react' import { getFileIconComponent } from '../FilePreview/getFileIconComponent' import ListItemConflictIndicator from './ListItemConflictIndicator' -import ListItemFlagIcons from './ListItemFlagIcons' import ListItemTags from './ListItemTags' import ListItemMetadata from './ListItemMetadata' import { DisplayableListItemProps } from './Types/DisplayableListItemProps' import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider' import { AppPaneId } from '../ResponsivePane/AppPaneMetadata' import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent' +import { classNames } from '@/Utils/ConcatenateClassNames' +import { formatSizeToReadableString } from '@standardnotes/filepicker' -const FileListItem: FunctionComponent = ({ +const FileListItem: FunctionComponent> = ({ application, filesController, hideDate, @@ -67,7 +68,7 @@ const FileListItem: FunctionComponent = ({ const IconComponent = () => getFileIconComponent( application.iconsController.getIconForFileType((item as FileItem).mimeType), - 'w-5 h-5 flex-shrink-0', + 'w-10 h-10 flex-shrink-0', ) useContextMenuEvent(listItemRef, openContextMenu) @@ -75,28 +76,41 @@ const FileListItem: FunctionComponent = ({ return (
- {!hideIcon ? ( -
- +
+
+ {!hideIcon ? ( +
+ +
+ ) : ( +
+ )} +
+
+
{item.title}
+
+ + + +
- ) : ( -
- )} -
-
-
{item.title}
+
+ {formatSizeToReadableString(item.decryptedSize)}
- - -
-
) } diff --git a/packages/web/src/javascripts/Components/ContentListView/ListItemFlagIcons.tsx b/packages/web/src/javascripts/Components/ContentListView/ListItemFlagIcons.tsx index 25de7dd9e..63beb562a 100644 --- a/packages/web/src/javascripts/Components/ContentListView/ListItemFlagIcons.tsx +++ b/packages/web/src/javascripts/Components/ContentListView/ListItemFlagIcons.tsx @@ -11,11 +11,13 @@ type Props = { starred: ListableContentItem['starred'] } hasFiles?: boolean + hasBorder?: boolean + className?: string } -const ListItemFlagIcons: FunctionComponent = ({ item, hasFiles = false }) => { +const ListItemFlagIcons: FunctionComponent = ({ item, hasFiles = false, hasBorder = true, className }) => { return ( -
+
{item.locked && ( diff --git a/packages/web/src/javascripts/Components/ContentListView/ListItemNotePreviewText.tsx b/packages/web/src/javascripts/Components/ContentListView/ListItemNotePreviewText.tsx index a3a8cde8e..6b42b7a18 100644 --- a/packages/web/src/javascripts/Components/ContentListView/ListItemNotePreviewText.tsx +++ b/packages/web/src/javascripts/Components/ContentListView/ListItemNotePreviewText.tsx @@ -1,9 +1,8 @@ -import { sanitizeHtmlString } from '@standardnotes/snjs' +import { sanitizeHtmlString, SNNote } from '@standardnotes/snjs' import { FunctionComponent } from 'react' -import { ListableContentItem } from './Types/ListableContentItem' type Props = { - item: ListableContentItem + item: SNNote hidePreview: boolean lineLimit?: number } diff --git a/packages/web/src/javascripts/Components/ContentListView/ListItemTags.tsx b/packages/web/src/javascripts/Components/ContentListView/ListItemTags.tsx index 740cf5920..84808eac3 100644 --- a/packages/web/src/javascripts/Components/ContentListView/ListItemTags.tsx +++ b/packages/web/src/javascripts/Components/ContentListView/ListItemTags.tsx @@ -1,10 +1,11 @@ import { FunctionComponent } from 'react' import Icon from '@/Components/Icon/Icon' import { DisplayableListItemProps } from './Types/DisplayableListItemProps' +import { ListableContentItem } from './Types/ListableContentItem' type Props = { hideTags: boolean - tags: DisplayableListItemProps['tags'] + tags: DisplayableListItemProps['tags'] } const ListItemTags: FunctionComponent = ({ hideTags, tags }) => { diff --git a/packages/web/src/javascripts/Components/ContentListView/NoteListItem.tsx b/packages/web/src/javascripts/Components/ContentListView/NoteListItem.tsx index 9dc9fa832..c5635741c 100644 --- a/packages/web/src/javascripts/Components/ContentListView/NoteListItem.tsx +++ b/packages/web/src/javascripts/Components/ContentListView/NoteListItem.tsx @@ -14,8 +14,9 @@ import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent' import ListItemNotePreviewText from './ListItemNotePreviewText' import { ListItemTitle } from './ListItemTitle' import { log, LoggingDomain } from '@/Logging' +import { classNames } from '@/Utils/ConcatenateClassNames' -const NoteListItem: FunctionComponent = ({ +const NoteListItem: FunctionComponent> = ({ application, notesController, onSelect, @@ -27,6 +28,8 @@ const NoteListItem: FunctionComponent = ({ selected, sortBy, tags, + isPreviousItemTiled, + isNextItemTiled, }) => { const { toggleAppPane } = useResponsiveAppPane() @@ -73,12 +76,17 @@ const NoteListItem: FunctionComponent = ({ log(LoggingDomain.ItemsList, 'Rendering note list item', item.title) + const hasOffsetBorder = !isNextItemTiled + return (
@@ -89,14 +97,14 @@ const NoteListItem: FunctionComponent = ({ ) : (
)} -
+
- +
) } diff --git a/packages/web/src/javascripts/Components/ContentListView/Types/AbstractListItemProps.ts b/packages/web/src/javascripts/Components/ContentListView/Types/AbstractListItemProps.ts index c0da66dde..cf6646ece 100644 --- a/packages/web/src/javascripts/Components/ContentListView/Types/AbstractListItemProps.ts +++ b/packages/web/src/javascripts/Components/ContentListView/Types/AbstractListItemProps.ts @@ -4,7 +4,9 @@ import { NotesController } from '@/Controllers/NotesController' import { SortableItem, SNTag, Uuids } from '@standardnotes/snjs' import { ListableContentItem } from './ListableContentItem' -export type AbstractListItemProps = { +type KeysOfUnion = T extends T ? keyof T : never + +export type AbstractListItemProps = { application: WebApplication filesController: FilesController notesController: NotesController @@ -13,14 +15,19 @@ export type AbstractListItemProps = { hideIcon: boolean hideTags: boolean hidePreview: boolean - item: ListableContentItem + item: I selected: boolean sortBy: keyof SortableItem | undefined tags: SNTag[] + isPreviousItemTiled?: boolean + isNextItemTiled?: boolean } -export function doListItemPropsMeritRerender(previous: AbstractListItemProps, next: AbstractListItemProps): boolean { - const simpleComparison: (keyof AbstractListItemProps)[] = [ +export function doListItemPropsMeritRerender( + previous: AbstractListItemProps, + next: AbstractListItemProps, +): boolean { + const simpleComparison: (keyof AbstractListItemProps)[] = [ 'onSelect', 'hideDate', 'hideIcon', @@ -28,6 +35,8 @@ export function doListItemPropsMeritRerender(previous: AbstractListItemProps, ne 'hidePreview', 'selected', 'sortBy', + 'isPreviousItemTiled', + 'isNextItemTiled', ] for (const key of simpleComparison) { @@ -83,7 +92,7 @@ function doesItemChangeMeritRerender(previous: ListableContentItem, next: Listab return true } - const propertiesMeritingRerender: (keyof ListableContentItem)[] = [ + const propertiesMeritingRerender: KeysOfUnion[] = [ 'title', 'protected', 'updatedAtString', @@ -97,7 +106,7 @@ function doesItemChangeMeritRerender(previous: ListableContentItem, next: Listab ] for (const key of propertiesMeritingRerender) { - if (previous[key] !== next[key]) { + if (previous[key as keyof ListableContentItem] !== next[key as keyof ListableContentItem]) { return true } } diff --git a/packages/web/src/javascripts/Components/ContentListView/Types/DisplayableListItemProps.ts b/packages/web/src/javascripts/Components/ContentListView/Types/DisplayableListItemProps.ts index 49f1e57ea..0a1c9bb03 100644 --- a/packages/web/src/javascripts/Components/ContentListView/Types/DisplayableListItemProps.ts +++ b/packages/web/src/javascripts/Components/ContentListView/Types/DisplayableListItemProps.ts @@ -1,7 +1,8 @@ import { SNTag } from '@standardnotes/snjs' import { AbstractListItemProps } from './AbstractListItemProps' +import { ListableContentItem } from './ListableContentItem' -export type DisplayableListItemProps = AbstractListItemProps & { +export type DisplayableListItemProps = AbstractListItemProps & { tags: { uuid: SNTag['uuid'] title: SNTag['title'] diff --git a/packages/web/src/javascripts/Components/ContentListView/Types/ListableContentItem.ts b/packages/web/src/javascripts/Components/ContentListView/Types/ListableContentItem.ts index daa0e149e..f24446616 100644 --- a/packages/web/src/javascripts/Components/ContentListView/Types/ListableContentItem.ts +++ b/packages/web/src/javascripts/Components/ContentListView/Types/ListableContentItem.ts @@ -1,12 +1,3 @@ -import { DecryptedItem, ItemContent } from '@standardnotes/snjs' +import { FileItem, SNNote } from '@standardnotes/snjs' -export type ListableContentItem = DecryptedItem & { - title: string - protected: boolean - updatedAtString?: string - createdAtString?: string - hidePreview?: boolean - preview_html?: string - preview_plain?: string - text?: string -} +export type ListableContentItem = SNNote | FileItem diff --git a/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts b/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts index 80a9a8cda..0fec5544c 100644 --- a/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts +++ b/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts @@ -22,6 +22,7 @@ import { useBoolean, TemplateNoteViewAutofocusBehavior, isTag, + isFile, } from '@standardnotes/snjs' import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx' import { WebApplication } from '../../Application/Application' @@ -37,6 +38,7 @@ import dayjs from 'dayjs' import { LinkingController } from '../LinkingController' import { AbstractViewController } from '../Abstract/AbstractViewController' import { Persistable } from '../Abstract/Persistable' +import { log, LoggingDomain } from '@/Logging' const MinNoteCellHeight = 51.0 const DefaultListNumNotes = 20 @@ -380,11 +382,42 @@ export class ItemListController ) } + /** + * In some cases we want to keep the selected item open even if it doesn't appear in results, + * for example if you are inside tag Foo and remove tag Foo from the note, we want to keep the note open. + */ private shouldCloseActiveItem = (activeItem: SNNote | FileItem | undefined) => { - const isSearching = this.noteFilterText.length > 0 - const itemExistsInUpdatedResults = this.items.find((item) => item.uuid === activeItem?.uuid) + const activeItemExistsInUpdatedResults = this.items.find((item) => item.uuid === activeItem?.uuid) - return !itemExistsInUpdatedResults && !isSearching && this.navigationController.isInAnySystemView() + const closeBecauseActiveItemIsFileAndDoesntExistInUpdatedResults = + activeItem && isFile(activeItem) && !activeItemExistsInUpdatedResults + + if (closeBecauseActiveItemIsFileAndDoesntExistInUpdatedResults) { + log(LoggingDomain.Selection, 'shouldCloseActiveItem closeBecauseActiveItemIsFileAndDoesntExistInUpdatedResults') + return true + } + + const firstItemInNewResults = this.getFirstNonProtectedItem() + + const closePreviousItemWhenSwitchingToFilesBasedView = + firstItemInNewResults && isFile(firstItemInNewResults) && !activeItemExistsInUpdatedResults + + if (closePreviousItemWhenSwitchingToFilesBasedView) { + log(LoggingDomain.Selection, 'shouldCloseActiveItem closePreviousItemWhenSwitchingToFilesBasedView') + return true + } + + const isSearching = this.noteFilterText.length > 0 + + const closeBecauseActiveItemDoesntExistInCurrentSystemView = + !activeItemExistsInUpdatedResults && !isSearching && this.navigationController.isInAnySystemView() + + if (closeBecauseActiveItemDoesntExistInCurrentSystemView) { + log(LoggingDomain.Selection, 'shouldCloseActiveItem closePreviousItemWhenSwitchingToFilesBasedView') + return true + } + + return false } private shouldSelectNextItemOrCreateNewNote = (activeItem: SNNote | FileItem | undefined) => { @@ -404,6 +437,11 @@ export class ItemListController } private shouldSelectFirstItem = (itemsReloadSource: ItemsReloadSource) => { + const item = this.getFirstNonProtectedItem() + if (item && isFile(item)) { + return false + } + const selectedTag = this.navigationController.selected const isDailyEntry = selectedTag && isTag(selectedTag) && selectedTag.isDailyEntry if (isDailyEntry) { @@ -424,10 +462,17 @@ export class ItemListController const activeItem = activeController?.item - if (this.shouldCloseActiveItem(activeItem) && activeController) { + if (activeController && activeItem && this.shouldCloseActiveItem(activeItem)) { this.closeItemController(activeController) - this.selectionController.selectNextItem() + + this.selectionController.deselectItem(activeItem) + + if (this.shouldSelectFirstItem(itemsReloadSource)) { + log(LoggingDomain.Selection, 'Selecting next item after closing active one') + this.selectionController.selectNextItem() + } } else if (this.shouldSelectActiveItem(activeItem) && activeItem) { + log(LoggingDomain.Selection, 'Selecting active item') await this.selectionController.selectItem(activeItem.uuid).catch(console.error) } else if (this.shouldSelectFirstItem(itemsReloadSource)) { await this.selectFirstItem() @@ -547,9 +592,11 @@ export class ItemListController this.displayOptions = newDisplayOptions this.webDisplayOptions = newWebDisplayOptions - const newWidth = this.application.getPreference(PrefKey.NotesPanelWidth) - if (newWidth && newWidth !== this.panelWidth) { - this.panelWidth = newWidth + const listColumnWidth = + selectedTag?.preferences?.panelWidth || this.application.getPreference(PrefKey.NotesPanelWidth) + + if (listColumnWidth && listColumnWidth !== this.panelWidth) { + this.panelWidth = listColumnWidth } if (!displayOptionsChanged) { @@ -560,7 +607,10 @@ export class ItemListController await this.reloadItems(ItemsReloadSource.DisplayOptionsChange) - if (newDisplayOptions.sortBy !== currentSortBy) { + if ( + newDisplayOptions.sortBy !== currentSortBy && + this.shouldSelectFirstItem(ItemsReloadSource.DisplayOptionsChange) + ) { await this.selectFirstItem() } @@ -689,6 +739,8 @@ export class ItemListController const item = this.getFirstNonProtectedItem() if (item) { + log(LoggingDomain.Selection, 'Selecting first item', item.uuid) + await this.selectionController.selectItemWithScrollHandling(item, { userTriggered: false, scrollIntoView: false, @@ -702,6 +754,7 @@ export class ItemListController const item = this.getFirstNonProtectedItem() if (item) { + log(LoggingDomain.Selection, 'selectNextItemOrCreateNewNote') await this.selectionController .selectItemWithScrollHandling(item, { userTriggered: false, @@ -746,6 +799,7 @@ export class ItemListController } private closeItemController(controller: NoteViewController | FileViewController): void { + log(LoggingDomain.Selection, 'Closing item controller', controller.runtimeId) this.application.itemControllerGroup.closeItemController(controller) } diff --git a/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts b/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts index 2b5e5d2a9..c0aaf68a3 100644 --- a/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts +++ b/packages/web/src/javascripts/Controllers/Navigation/NavigationController.ts @@ -431,6 +431,15 @@ export class NavigationController return this.selected_ } + public async setPanelWidthForTag(tag: SNTag, width: number): Promise { + await this.application.mutator.changeAndSaveItem(tag, (mutator) => { + mutator.preferences = { + ...mutator.preferences, + panelWidth: width, + } + }) + } + public async setSelectedTag(tag: AnyTag | undefined, { userTriggered } = { userTriggered: false }) { if (tag && tag.conflictOf) { this.application.mutator diff --git a/packages/web/src/javascripts/Controllers/SelectedItemsController.ts b/packages/web/src/javascripts/Controllers/SelectedItemsController.ts index bf56d374b..f73773685 100644 --- a/packages/web/src/javascripts/Controllers/SelectedItemsController.ts +++ b/packages/web/src/javascripts/Controllers/SelectedItemsController.ts @@ -1,4 +1,5 @@ import { ListableContentItem } from '@/Components/ContentListView/Types/ListableContentItem' +import { log, LoggingDomain } from '@/Logging' import { ChallengeReason, ContentType, @@ -8,6 +9,7 @@ import { UuidString, InternalEventBus, isFile, + Uuids, } from '@standardnotes/snjs' import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx' import { WebApplication } from '../Application/Application' @@ -248,12 +250,15 @@ export class SelectedItemsController didSelect: boolean }> => { const item = this.application.items.findItem(uuid) + if (!item) { return { didSelect: false, } } + log(LoggingDomain.Selection, 'selectItem', item.uuid) + const hasMeta = this.io.activeModifiers.has(KeyboardModifier.Meta) const hasCtrl = this.io.activeModifiers.has(KeyboardModifier.Ctrl) const hasShift = this.io.activeModifiers.has(KeyboardModifier.Shift) @@ -304,14 +309,18 @@ export class SelectedItemsController } selectUuids = async (uuids: UuidString[], userTriggered = false) => { - const itemsForUuids = this.application.items.findItems(uuids) + const itemsForUuids = this.application.items.findItems(uuids).filter((item) => !isFile(item)) + if (itemsForUuids.length < 1) { return } + if (!userTriggered && itemsForUuids.some((item) => item.protected && isFile(item))) { return } - this.setSelectedUuids(new Set(uuids)) + + this.setSelectedUuids(new Set(Uuids(itemsForUuids))) + if (itemsForUuids.length === 1) { void this.openSingleSelectedItem() } diff --git a/packages/web/src/javascripts/Logging.ts b/packages/web/src/javascripts/Logging.ts index be9c94cd8..7b128cd3e 100644 --- a/packages/web/src/javascripts/Logging.ts +++ b/packages/web/src/javascripts/Logging.ts @@ -7,6 +7,7 @@ export enum LoggingDomain { ItemsList, NavigationList, Viewport, + Selection, } const LoggingStatus: Record = { @@ -14,7 +15,8 @@ const LoggingStatus: Record = { [LoggingDomain.NoteView]: false, [LoggingDomain.ItemsList]: false, [LoggingDomain.NavigationList]: false, - [LoggingDomain.Viewport]: true, + [LoggingDomain.Viewport]: false, + [LoggingDomain.Selection]: false, } export function log(domain: LoggingDomain, ...args: any[]): void { diff --git a/packages/web/src/stylesheets/_sn.scss b/packages/web/src/stylesheets/_sn.scss index 62909a991..84bc4052d 100644 --- a/packages/web/src/stylesheets/_sn.scss +++ b/packages/web/src/stylesheets/_sn.scss @@ -109,6 +109,12 @@ -webkit-line-clamp: 1; } +.line-clamp-2 { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + @mixin DimmedBackground($color, $opacity) { content: ''; width: 100%;