addons/isl/src/CommitInfoView/TextArea.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {ReactNode, RefObject} from 'react';
b69ab319
b69ab3110import {TextArea} from 'isl-components/TextArea';
b69ab3111import {useEffect, useRef} from 'react';
b69ab3112import {InternalFieldName} from 'shared/constants';
b69ab3113import {
b69ab3114 FilePicker,
b69ab3115 ImageDropZone,
b69ab3116 PendingImageUploads,
b69ab3117 useUploadFilesCallback,
b69ab3118} from '../ImageUpload';
b69ab3119import {Internal} from '../Internal';
b69ab3120import {MinHeightTextField} from './MinHeightTextField';
b69ab3121import {convertFieldNameToKey} from './utils';
b69ab3122
b69ab3123function moveCursorToEnd(element: HTMLTextAreaElement) {
b69ab3124 element.setSelectionRange(element.value.length, element.value.length);
b69ab3125}
b69ab3126
b69ab3127export function CommitInfoTextArea({
b69ab3128 kind,
b69ab3129 name,
b69ab3130 autoFocus,
b69ab3131 editedMessage,
b69ab3132 setEditedField,
b69ab3133}: {
b69ab3134 kind: 'title' | 'textarea' | 'field';
b69ab3135 name: string;
b69ab3136 autoFocus: boolean;
b69ab3137 editedMessage: string;
b69ab3138 setEditedField: (fieldValue: string) => unknown;
b69ab3139}) {
b69ab3140 const ref = useRef<HTMLTextAreaElement>(null);
b69ab3141 useEffect(() => {
b69ab3142 if (ref.current && autoFocus) {
b69ab3143 ref.current.focus();
b69ab3144 moveCursorToEnd(ref.current);
b69ab3145 }
b69ab3146 }, [autoFocus, ref]);
b69ab3147 const Component = kind === 'field' || kind === 'title' ? MinHeightTextField : TextArea;
b69ab3148 const props =
b69ab3149 kind === 'field' || kind === 'title'
b69ab3150 ? {}
b69ab3151 : ({
b69ab3152 rows: 15,
b69ab3153 resize: 'vertical',
b69ab3154 } as const);
b69ab3155
b69ab3156 // The gh cli does not support uploading images for commit messages,
b69ab3157 // see https://github.com/cli/cli/issues/1895#issuecomment-718899617
b69ab3158 // for now, this is internal-only.
b69ab3159 const supportsImageUpload =
b69ab3160 kind === 'textarea' &&
b69ab3161 (Internal.supportsImageUpload === true ||
b69ab3162 // image upload is always enabled in tests
b69ab3163 process.env.NODE_ENV === 'test');
b69ab3164
b69ab3165 const onInput = (event: {currentTarget: HTMLTextAreaElement}) => {
b69ab3166 setEditedField(event.currentTarget?.value);
b69ab3167 };
b69ab3168
b69ab3169 const uploadFiles = useUploadFilesCallback(name, ref, onInput);
b69ab3170
b69ab3171 const fieldKey = convertFieldNameToKey(name);
b69ab3172
b69ab3173 const rendered = (
b69ab3174 <div className="commit-info-field">
b69ab3175 <Component
b69ab3176 ref={ref}
b69ab3177 {...props}
b69ab3178 onPaste={
b69ab3179 !supportsImageUpload
b69ab3180 ? undefined
b69ab3181 : (event: React.ClipboardEvent<HTMLTextAreaElement>) => {
b69ab3182 if (event.clipboardData != null && event.clipboardData.files.length > 0) {
b69ab3183 uploadFiles([...event.clipboardData.files]);
b69ab3184 event.preventDefault();
b69ab3185 }
b69ab3186 }
b69ab3187 }
b69ab3188 value={editedMessage}
b69ab3189 data-testid={`commit-info-${fieldKey}-field`}
b69ab3190 onInput={onInput}
b69ab3191 />
b69ab3192 <EditorToolbar
b69ab3193 fieldName={name}
b69ab3194 uploadFiles={supportsImageUpload ? uploadFiles : undefined}
b69ab3195 textAreaRef={ref}
b69ab3196 />
b69ab3197 </div>
b69ab3198 );
b69ab3199 return !supportsImageUpload ? (
b69ab31100 rendered
b69ab31101 ) : (
b69ab31102 <ImageDropZone onDrop={uploadFiles}>{rendered}</ImageDropZone>
b69ab31103 );
b69ab31104}
b69ab31105
b69ab31106/**
b69ab31107 * Floating button list at the bottom corner of the text area
b69ab31108 */
b69ab31109export function EditorToolbar({
b69ab31110 fieldName,
b69ab31111 textAreaRef,
b69ab31112 uploadFiles,
b69ab31113}: {
b69ab31114 fieldName: string;
b69ab31115 uploadFiles?: (files: Array<File>) => unknown;
b69ab31116 textAreaRef: RefObject<HTMLTextAreaElement>;
b69ab31117}) {
b69ab31118 const parts: Array<ReactNode> = [];
b69ab31119 if (uploadFiles != null) {
b69ab31120 parts.push(
b69ab31121 <PendingImageUploads fieldName={fieldName} key="pending-uploads" textAreaRef={textAreaRef} />,
b69ab31122 );
b69ab31123 }
b69ab31124 if (fieldName === InternalFieldName.TestPlan && Internal.RecommendTestPlanButton) {
b69ab31125 parts.push(<Internal.RecommendTestPlanButton key="recommend-test-plan" />);
b69ab31126 }
b69ab31127 if (fieldName === InternalFieldName.Summary && Internal.GenerateSummaryButton) {
b69ab31128 parts.push(<Internal.GenerateSummaryButton key="generate-summary" />);
b69ab31129 }
b69ab31130 if (uploadFiles != null) {
b69ab31131 parts.push(<FilePicker key="picker" uploadFiles={uploadFiles} />);
b69ab31132 }
b69ab31133 if (parts.length === 0) {
b69ab31134 return null;
b69ab31135 }
b69ab31136 return <div className="text-area-toolbar">{parts}</div>;
b69ab31137}