一些检查失败了
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
150 行
4.9 KiB
TypeScript
150 行
4.9 KiB
TypeScript
import * as React from 'react';
|
||
import { _ } from '@joplin/lib/locale';
|
||
import CommandService from '@joplin/lib/services/CommandService';
|
||
import { ChangeEvent, useCallback, useContext, useRef } from 'react';
|
||
import NoteToolbar from '../../NoteToolbar/NoteToolbar';
|
||
import { buildStyle } from '@joplin/lib/theme';
|
||
import time from '@joplin/lib/time';
|
||
import Setting from '@joplin/lib/models/Setting';
|
||
import { WindowIdContext } from '../../NewWindowOrIFrame';
|
||
|
||
interface Props {
|
||
themeId: number;
|
||
noteUserUpdatedTime: number;
|
||
noteTitle: string;
|
||
noteIsTodo: number;
|
||
isProvisional: boolean;
|
||
titleInputRef: React.RefObject<HTMLInputElement>;
|
||
onTitleChange(event: ChangeEvent<HTMLInputElement>): void;
|
||
disabled: boolean;
|
||
}
|
||
|
||
function styles_(props: Props) {
|
||
return buildStyle(['NoteEditorTitleBar'], props.themeId, theme => {
|
||
return {
|
||
titleInput: {
|
||
flex: 1,
|
||
display: 'inline-block',
|
||
paddingTop: 4,
|
||
minHeight: 38,
|
||
boxSizing: 'border-box',
|
||
fontWeight: '700',
|
||
paddingBottom: 4,
|
||
paddingLeft: 0,
|
||
paddingRight: 8,
|
||
color: theme.textStyle.color,
|
||
fontSize: Math.round(theme.textStyle.fontSize * 1.75),
|
||
backgroundColor: 'transparent',
|
||
border: 'none',
|
||
outline: 'none',
|
||
width: '100%',
|
||
},
|
||
|
||
titleDate: {
|
||
...theme.textStyle,
|
||
color: theme.colorFaded,
|
||
paddingLeft: 8,
|
||
whiteSpace: 'nowrap',
|
||
},
|
||
toolbarStyle: {
|
||
marginBottom: 0,
|
||
minWidth: 0,
|
||
},
|
||
};
|
||
});
|
||
}
|
||
|
||
const useReselectHandlers = () => {
|
||
const lastTitleFocus = useRef([0, 0]);
|
||
const lastTitleValue = useRef('');
|
||
|
||
const onTitleBlur: React.FocusEventHandler<HTMLInputElement> = useCallback((event) => {
|
||
const titleElement = event.currentTarget;
|
||
lastTitleFocus.current = [titleElement.selectionStart, titleElement.selectionEnd];
|
||
lastTitleValue.current = titleElement.value;
|
||
}, []);
|
||
|
||
const onTitleFocus: React.FocusEventHandler<HTMLInputElement> = useCallback((event) => {
|
||
const titleElement = event.currentTarget;
|
||
// By default, focusing the note title bar can cause its content to become selected. We override
|
||
// this with a more reasonable default:
|
||
if (titleElement.selectionStart === 0 && titleElement.selectionEnd === titleElement.value.length) {
|
||
if (lastTitleValue.current !== titleElement.value) {
|
||
titleElement.selectionStart = titleElement.value.length;
|
||
} else {
|
||
titleElement.selectionStart = lastTitleFocus.current[0];
|
||
titleElement.selectionEnd = lastTitleFocus.current[1];
|
||
}
|
||
}
|
||
}, []);
|
||
|
||
return { onTitleBlur, onTitleFocus };
|
||
};
|
||
|
||
export default function NoteTitleBar(props: Props) {
|
||
const styles = styles_(props);
|
||
|
||
const onTitleKeydown: React.KeyboardEventHandler<HTMLInputElement> = useCallback((event) => {
|
||
const titleElement = event.currentTarget;
|
||
const selectionAtEnd = titleElement.selectionEnd === titleElement.value.length;
|
||
const isNavigationShortcut = (event.key === 'ArrowDown' && selectionAtEnd) || (event.key === 'Enter' && !event.shiftKey);
|
||
const composing = event.nativeEvent.isComposing;
|
||
|
||
// Don't change focus if the navigation shortcut is fired during composition. See
|
||
// https://github.com/laurent22/joplin/issues/11485.
|
||
if (!composing && isNavigationShortcut) {
|
||
event.preventDefault();
|
||
const moveCursorToStart = event.key === 'ArrowDown' || event.key === 'Enter';
|
||
void CommandService.instance().execute('focusElement', 'noteBody', { moveCursorToStart });
|
||
}
|
||
}, []);
|
||
|
||
const { onTitleFocus, onTitleBlur } = useReselectHandlers();
|
||
|
||
function renderTitleBarDate() {
|
||
const dateFormat = Setting.value('dateFormat');
|
||
const timeFormat = Setting.value('timeFormat') || 'HH:mm';
|
||
// 在中文语境或默认英式格式(DD/MM/YYYY)下,优先采用中文及国际标准时间格式 YYYY-MM-DD HH:mm
|
||
let format = `${dateFormat} ${timeFormat}`;
|
||
const isDefaultOrChinese = !dateFormat || dateFormat === Setting.DATE_FORMAT_1 || Setting.value('locale')?.toLowerCase().startsWith('zh');
|
||
if (isDefaultOrChinese) {
|
||
format = `YYYY-MM-DD ${timeFormat}`;
|
||
}
|
||
return <span className="updated-time-label" style={styles.titleDate}>{time.formatMsToLocal(props.noteUserUpdatedTime, format)}</span>;
|
||
}
|
||
|
||
const windowId = useContext(WindowIdContext);
|
||
|
||
function renderNoteToolbar() {
|
||
return <NoteToolbar
|
||
themeId={props.themeId}
|
||
style={styles.toolbarStyle}
|
||
disabled={props.disabled}
|
||
windowId={windowId}
|
||
/>;
|
||
}
|
||
|
||
return (
|
||
<div className='note-title-wrapper'>
|
||
<input
|
||
className="title-input"
|
||
type="text"
|
||
ref={props.titleInputRef}
|
||
placeholder={props.isProvisional ? (props.noteIsTodo ? _('Creating new to-do...') : _('Creating new note...')) : ''}
|
||
aria-label={props.isProvisional ? undefined : _('Note title')}
|
||
style={styles.titleInput}
|
||
readOnly={props.disabled}
|
||
onChange={props.onTitleChange}
|
||
onKeyDown={onTitleKeydown}
|
||
onFocus={onTitleFocus}
|
||
onBlur={onTitleBlur}
|
||
value={props.noteTitle}
|
||
/>
|
||
<div className='note-title-info-group'>
|
||
{renderTitleBarDate()}
|
||
{renderNoteToolbar()}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|