feat: Initial clean release of CHM
一些检查失败了
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
一些检查失败了
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
这个提交包含在:
@@ -0,0 +1,131 @@
|
||||
import * as React from 'react';
|
||||
import { useCallback, useId } from 'react';
|
||||
import Button, { ButtonLevel } from '../Button/Button';
|
||||
import { MoveDirection } from './utils/movements';
|
||||
import styled from 'styled-components';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
|
||||
const StyledRoot = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 5px;
|
||||
background-color: ${props => props.theme.backgroundColor};
|
||||
border-radius: 5px;
|
||||
|
||||
> .label {
|
||||
// Used only for accessibility tools
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const ButtonRow = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const EmptyButton = styled(Button)`
|
||||
visibility: hidden;
|
||||
`;
|
||||
|
||||
const ArrowButton = styled(Button)`
|
||||
opacity: ${props => props.disabled ? 0.2 : 1};
|
||||
`;
|
||||
|
||||
type ButtonKey = string;
|
||||
|
||||
export interface MoveButtonClickEvent {
|
||||
direction: MoveDirection;
|
||||
itemKey: string;
|
||||
buttonKey: ButtonKey;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onClick(event: MoveButtonClickEvent): void;
|
||||
itemKey: string;
|
||||
itemLabel: string;
|
||||
canMoveLeft: boolean;
|
||||
canMoveRight: boolean;
|
||||
canMoveUp: boolean;
|
||||
canMoveDown: boolean;
|
||||
|
||||
// Specifies which button to auto-focus (if any). Clicking a "Move ..." button changes the app's layout. By default, this
|
||||
// causes focus to jump to the start of the move dialog. Providing the key of the last-clicked button allows focus
|
||||
// to be restored after changing the app layout:
|
||||
autoFocusKey: ButtonKey|null;
|
||||
}
|
||||
|
||||
export default function MoveButtons(props: Props) {
|
||||
const onButtonClick = useCallback((direction: MoveDirection) => {
|
||||
props.onClick({ direction, itemKey: props.itemKey, buttonKey: `${props.itemKey}-${direction}` });
|
||||
}, [props.onClick, props.itemKey]);
|
||||
|
||||
function canMove(dir: MoveDirection) {
|
||||
if (dir === MoveDirection.Up) return props.canMoveUp;
|
||||
if (dir === MoveDirection.Down) return props.canMoveDown;
|
||||
if (dir === MoveDirection.Left) return props.canMoveLeft;
|
||||
if (dir === MoveDirection.Right) return props.canMoveRight;
|
||||
throw new Error('Unreachable');
|
||||
}
|
||||
|
||||
const iconLabel = (dir: MoveDirection) => {
|
||||
if (dir === MoveDirection.Up) return _('Move up');
|
||||
if (dir === MoveDirection.Down) return _('Move down');
|
||||
if (dir === MoveDirection.Left) return _('Move left');
|
||||
if (dir === MoveDirection.Right) return _('Move right');
|
||||
const unreachable: never = dir;
|
||||
throw new Error(`Invalid direction: ${unreachable}`);
|
||||
};
|
||||
|
||||
const descriptionId = useId();
|
||||
|
||||
const buttonKey = (dir: MoveDirection) => `${props.itemKey}-${dir}`;
|
||||
const autoFocusDirection = (() => {
|
||||
if (!props.autoFocusKey) return undefined;
|
||||
|
||||
const buttonDirections = [MoveDirection.Up, MoveDirection.Down, MoveDirection.Left, MoveDirection.Right];
|
||||
const autoFocusDirection = buttonDirections.find(
|
||||
direction => buttonKey(direction) === props.autoFocusKey,
|
||||
);
|
||||
|
||||
if (!autoFocusDirection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const autoFocusDirectionEnabled = autoFocusDirection && canMove(autoFocusDirection);
|
||||
if (autoFocusDirectionEnabled) {
|
||||
return autoFocusDirection;
|
||||
} else {
|
||||
// Select an enabled direction instead
|
||||
return buttonDirections.find(dir => canMove(dir));
|
||||
}
|
||||
})();
|
||||
|
||||
function renderButton(dir: MoveDirection) {
|
||||
return <ArrowButton
|
||||
disabled={!canMove(dir)}
|
||||
level={ButtonLevel.Primary}
|
||||
iconName={`fas fa-arrow-${dir}`}
|
||||
iconLabel={iconLabel(dir)}
|
||||
autoFocus={autoFocusDirection === dir}
|
||||
onClick={() => onButtonClick(dir)}
|
||||
/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledRoot role='group' aria-labelledby={descriptionId}>
|
||||
<ButtonRow>
|
||||
{renderButton(MoveDirection.Up)}
|
||||
</ButtonRow>
|
||||
<ButtonRow>
|
||||
{renderButton(MoveDirection.Left)}
|
||||
<EmptyButton iconName="fas fa-arrow-down" aria-hidden={true} disabled={true}/>
|
||||
{renderButton(MoveDirection.Right)}
|
||||
</ButtonRow>
|
||||
<ButtonRow>
|
||||
{renderButton(MoveDirection.Down)}
|
||||
</ButtonRow>
|
||||
<div className='label' id={descriptionId}>{props.itemLabel}</div>
|
||||
</StyledRoot>
|
||||
);
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户