一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
131 行
4.6 KiB
TypeScript
131 行
4.6 KiB
TypeScript
import * as React from 'react';
|
|
|
|
import { FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
|
|
import ExpandLink from './ExpandLink';
|
|
import { StyledListItemAnchor, StyledShareIcon, StyledSpanFix } from '../styles';
|
|
import { ItemClickListener, ItemContextMenuListener, ItemDragListener } from '../types';
|
|
import FolderIconBox from '../../FolderIconBox';
|
|
import { getTrashFolderIcon, getTrashFolderId } from '@joplin/lib/services/trash';
|
|
import Folder from '@joplin/lib/models/Folder';
|
|
import { ModelType } from '@joplin/lib/BaseModel';
|
|
import { _ } from '@joplin/lib/locale';
|
|
import NoteCount from './NoteCount';
|
|
import ListItemWrapper, { ItemSelectionState, ListItemRef } from './ListItemWrapper';
|
|
import { useId } from 'react';
|
|
import { ItemClickEvent } from '../hooks/useOnItemClick';
|
|
|
|
const renderFolderIcon = (folderIcon: FolderIcon) => {
|
|
if (!folderIcon) {
|
|
const defaultFolderIcon: FolderIcon = {
|
|
dataUrl: '',
|
|
emoji: '',
|
|
name: 'far fa-folder',
|
|
type: FolderIconType.FontAwesome,
|
|
};
|
|
return <div style={{ marginRight: 7, display: 'flex' }}><FolderIconBox opacity={0.7} folderIcon={defaultFolderIcon}/></div>;
|
|
}
|
|
|
|
return <div style={{ marginRight: 7, display: 'flex' }}><FolderIconBox folderIcon={folderIcon}/></div>;
|
|
};
|
|
|
|
interface FolderItemProps {
|
|
anchorRef: ListItemRef;
|
|
hasChildren: boolean;
|
|
showFolderIcon: boolean;
|
|
isExpanded: boolean;
|
|
parentId: string;
|
|
depth: number;
|
|
folderId: string;
|
|
folderTitle: string;
|
|
folderIcon: FolderIcon;
|
|
noteCount: number;
|
|
onFolderDragStart_: ItemDragListener;
|
|
onFolderDragOver_: ItemDragListener;
|
|
onFolderDrop_: ItemDragListener;
|
|
itemContextMenu: ItemContextMenuListener;
|
|
folderItem_click: (event: ItemClickEvent)=> void;
|
|
onFolderToggleClick_: ItemClickListener;
|
|
shareId: string;
|
|
isPublished: boolean;
|
|
selectionState: ItemSelectionState;
|
|
|
|
index: number;
|
|
itemCount: number;
|
|
}
|
|
|
|
function FolderItem(props: FolderItemProps) {
|
|
const { hasChildren, showFolderIcon, isExpanded, parentId, depth, selectionState, folderId, folderTitle, folderIcon, noteCount, onFolderDragStart_, onFolderDragOver_, onFolderDrop_, itemContextMenu, folderItem_click, onFolderToggleClick_, shareId, isPublished } = props;
|
|
|
|
const shareTitle = _('Shared');
|
|
const shareIcon = shareId && !parentId ? <StyledShareIcon aria-label={shareTitle} title={shareTitle} className="fas fa-share-alt"/> : null;
|
|
const publishedTitle = _('Published');
|
|
const publishedIcon = isPublished ? <StyledShareIcon aria-label={publishedTitle} title={publishedTitle} className="fas fa-globe"/> : null;
|
|
const draggable = ![getTrashFolderId(), Folder.conflictFolderId()].includes(folderId);
|
|
|
|
const doRenderFolderIcon = () => {
|
|
if (folderId === getTrashFolderId()) {
|
|
return renderFolderIcon(getTrashFolderIcon(FolderIconType.FontAwesome));
|
|
}
|
|
|
|
if (!showFolderIcon) return null;
|
|
return renderFolderIcon(folderIcon);
|
|
};
|
|
const titleId = useId();
|
|
|
|
return (
|
|
<ListItemWrapper
|
|
containerRef={props.anchorRef}
|
|
// Folders are contained within the "Notebooks" section (which has depth 0):
|
|
depth={depth + 1}
|
|
selectionState={selectionState}
|
|
itemIndex={props.index}
|
|
itemCount={props.itemCount}
|
|
expanded={hasChildren ? props.isExpanded : undefined}
|
|
className={`list-item-container list-item-depth-${depth} ${selectionState.selected ? 'selected' : ''}`}
|
|
highlightOnHover={true}
|
|
onDragStart={onFolderDragStart_}
|
|
onDragOver={onFolderDragOver_}
|
|
onDrop={onFolderDrop_}
|
|
onContextMenu={itemContextMenu}
|
|
draggable={draggable}
|
|
data-folder-id={folderId}
|
|
data-id={folderId}
|
|
data-type={ModelType.Folder}
|
|
// Accessibility labels: Don't include the expand/collapse link in the description,
|
|
// since this information is already conveyed by aria-* props.
|
|
aria-labelledby={titleId}
|
|
>
|
|
<StyledListItemAnchor
|
|
className="list-item"
|
|
id={titleId}
|
|
isConflictFolder={folderId === Folder.conflictFolderId()}
|
|
selected={selectionState.selected}
|
|
shareId={shareId}
|
|
isPublished={isPublished}
|
|
data-folder-id={folderId}
|
|
onDoubleClick={onFolderToggleClick_}
|
|
|
|
onClick={(event: React.MouseEvent) => {
|
|
folderItem_click({
|
|
id: folderId, type: ModelType.Folder, event,
|
|
});
|
|
}}
|
|
>
|
|
{doRenderFolderIcon()}<StyledSpanFix className="title">{folderTitle}</StyledSpanFix>
|
|
{publishedIcon}{shareIcon} <NoteCount count={noteCount}/>
|
|
</StyledListItemAnchor>
|
|
<ExpandLink
|
|
// The ExpandLink is included after the title so that the screen reader reads the
|
|
// title first.
|
|
className='toggle'
|
|
hasChildren={hasChildren}
|
|
folderId={folderId}
|
|
onClick={onFolderToggleClick_}
|
|
isExpanded={isExpanded}
|
|
/>
|
|
</ListItemWrapper>
|
|
);
|
|
}
|
|
|
|
export default FolderItem;
|