addons/isl/src/CommitInfoView/utils.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} from 'react';
b69ab319import type {CommitInfo} from '../types';
b69ab3110import type {CommitMessageFields, FieldConfig, FieldsBeingEdited, TypeaheadKind} from './types';
b69ab3111
b69ab3112import {Subtle} from 'isl-components/Subtle';
b69ab3113import {Tooltip} from 'isl-components/Tooltip';
b69ab3114import type {TypeaheadResult} from 'isl-components/Types';
b69ab3115import {randomId} from 'shared/utils';
b69ab3116import serverApi from '../ClientToServerAPI';
b69ab3117import {InlineBadge} from '../InlineBadge';
b69ab3118import {YouAreHereLabel} from '../YouAreHereLabel';
b69ab3119import {t, T} from '../i18n';
b69ab3120import platform from '../platform';
b69ab3121import {RelativeDate} from '../relativeDate';
b69ab3122
b69ab3123export function CommitTitleByline({commit}: {commit: CommitInfo}) {
b69ab3124 const createdByInfo = (
b69ab3125 // TODO: determine if you're the author to say "you"
b69ab3126 <T replace={{$author: commit.author}}>Created by $author</T>
b69ab3127 );
b69ab3128 return (
b69ab3129 <Subtle className="commit-info-title-byline">
b69ab3130 {commit.isDot ? <YouAreHereLabel /> : null}
b69ab3131 {commit.phase === 'public' ? <PublicCommitBadge /> : null}
b69ab3132 <OverflowEllipsis shrink>
b69ab3133 <Tooltip trigger="hover" component={() => createdByInfo}>
b69ab3134 {createdByInfo}
b69ab3135 </Tooltip>
b69ab3136 </OverflowEllipsis>
b69ab3137 <OverflowEllipsis>
b69ab3138 <Tooltip trigger="hover" title={commit.date.toLocaleString()}>
b69ab3139 <RelativeDate date={commit.date} />
b69ab3140 </Tooltip>
b69ab3141 </OverflowEllipsis>
b69ab3142 </Subtle>
b69ab3143 );
b69ab3144}
b69ab3145
b69ab3146function PublicCommitBadge() {
b69ab3147 return (
b69ab3148 <Tooltip
b69ab3149 placement="bottom"
b69ab3150 title={t(
b69ab3151 "This commit has already been pushed to an append-only remote branch and can't be modified locally.",
b69ab3152 )}>
b69ab3153 <InlineBadge>
b69ab3154 <T>Public</T>
b69ab3155 </InlineBadge>
b69ab3156 </Tooltip>
b69ab3157 );
b69ab3158}
b69ab3159
b69ab3160export function OverflowEllipsis({children, shrink}: {children: ReactNode; shrink?: boolean}) {
b69ab3161 return <div className={`overflow-ellipsis${shrink ? ' overflow-shrink' : ''}`}>{children}</div>;
b69ab3162}
b69ab3163
b69ab3164export function SmallCapsTitle({children}: {children: ReactNode}) {
b69ab3165 return <div className="commit-info-small-title">{children}</div>;
b69ab3166}
b69ab3167
b69ab3168export function Section({
b69ab3169 children,
b69ab3170 className,
b69ab3171 ...rest
b69ab3172}: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>) {
b69ab3173 return (
b69ab3174 <section {...rest} className={'commit-info-section' + (className ? ' ' + className : '')}>
b69ab3175 {children}
b69ab3176 </section>
b69ab3177 );
b69ab3178}
b69ab3179
b69ab3180export function getFieldToAutofocus(
b69ab3181 fields: Array<FieldConfig>,
b69ab3182 fieldsBeingEdited: FieldsBeingEdited,
b69ab3183 lastFieldsBeingEdited: FieldsBeingEdited | undefined,
b69ab3184): keyof CommitMessageFields | undefined {
b69ab3185 for (const field of fields) {
b69ab3186 const isNewlyBeingEdited =
b69ab3187 fieldsBeingEdited[field.key] &&
b69ab3188 (lastFieldsBeingEdited == null || !lastFieldsBeingEdited[field.key]);
b69ab3189 if (isNewlyBeingEdited) {
b69ab3190 return field.key;
b69ab3191 }
b69ab3192 }
b69ab3193 return undefined;
b69ab3194}
b69ab3195
b69ab3196export function getOnClickToken(
b69ab3197 field: FieldConfig & {type: 'field'},
b69ab3198): ((token: string) => unknown) | undefined {
b69ab3199 if (field.getUrl == null) {
b69ab31100 return undefined;
b69ab31101 }
b69ab31102
b69ab31103 return token => {
b69ab31104 const url = field.getUrl?.(token);
b69ab31105 url && platform.openExternalLink(url);
b69ab31106 };
b69ab31107}
b69ab31108
b69ab31109export function convertFieldNameToKey(fieldName: string): string {
b69ab31110 return fieldName.toLowerCase().replace(/\s/g, '-');
b69ab31111}
b69ab31112
b69ab31113export async function fetchNewSuggestions(
b69ab31114 kind: TypeaheadKind,
b69ab31115 text: string,
b69ab31116): Promise<{values: Array<TypeaheadResult>; fetchStartTimestamp: number}> {
b69ab31117 const now = Date.now();
b69ab31118 if (text.trim().length < 2) {
b69ab31119 // no need to do a fetch on zero- or one-char input...
b69ab31120 // it's slow and doesn't give good suggestions anyway
b69ab31121 return {values: [], fetchStartTimestamp: now};
b69ab31122 }
b69ab31123 const id = randomId();
b69ab31124 serverApi.postMessage({type: 'typeahead', kind, id, query: text});
b69ab31125 const values = await serverApi.nextMessageMatching(
b69ab31126 'typeaheadResult',
b69ab31127 message => message.id === id,
b69ab31128 );
b69ab31129 return {values: values.result, fetchStartTimestamp: now};
b69ab31130}