文件
chen 301194963b
一些检查失败了
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
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

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;