Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 2 additions & 25 deletions resources/js/components/commit-composer-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@ import {
DialogDescription,
DialogTitle,
} from '@/components/ui/dialog';
import { useSharedState } from '@/hooks/use-shared-state';

const bodyId = 'commit-dialog-body';

const closedCountKey = 'commit-dialog:closed_count';

/**
* Focuses the composer at the top of the log when it is on screen, rather than opening the dialog over it.
*/
Expand All @@ -33,35 +30,15 @@ export function focusVisibleComposer(): boolean {
return true;
}

/**
* The dialog writes to the same stash as the composer on the log, so that composer remounts with this as its key to
* show the draft as the dialog left it.
*/
export function useCommitDialogClosedCount(): number {
const [closedCount] = useSharedState<number>(closedCountKey, 0);

return closedCount;
}

export function CommitComposerDialog({
open,
onOpenChange,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
}) {
const [, setClosedCount] = useSharedState<number>(closedCountKey, 0);

const close = () => {
onOpenChange(false);
setClosedCount((count) => count + 1);
};

return (
<Dialog
open={open}
onOpenChange={(isOpen) => (isOpen ? onOpenChange(true) : close())}
>
<Dialog open={open} onOpenChange={onOpenChange}>
{open && (
<DialogContent
onOpenAutoFocus={(event) => {
Expand Down Expand Up @@ -99,7 +76,7 @@ export function CommitComposerDialog({
<CommitComposer
id={bodyId}
isInDialog
onCommitted={close}
onCommitted={() => onOpenChange(false)}
/>
</DialogContent>
)}
Expand Down
62 changes: 39 additions & 23 deletions resources/js/components/commit-composer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Form, Link, usePage } from '@inertiajs/react';
import { ChartBar, Clock3 } from 'lucide-react';
import type { KeyboardEvent } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useEffect, useRef } from 'react';
import CommitController from '@/actions/App/Http/Controllers/CommitController';
import ScheduledCommitController from '@/actions/App/Http/Controllers/ScheduledCommitController';
import { ActionTooltip } from '@/components/action-tooltip';
Expand Down Expand Up @@ -46,6 +46,7 @@ import type { UseStashReturn } from '@/hooks/use-stash';
import {
discardSeriesStash,
moveCaretToEnd,
useDraftPart,
useStash,
} from '@/hooks/use-stash';
import { useUser } from '@/hooks/use-user';
Expand All @@ -63,10 +64,18 @@ function focusBody(id: string): void {
requestAnimationFrame(() => document.getElementById(id)?.focus());
}

function useCommitSchedule() {
const [isScheduling, setIsScheduling] = useState(false);
const [scheduledAt, setScheduledAt] = useState('');
const [timezone, setTimezone] = useState('');
function useCommitSchedule(userId: string) {
const [isScheduling, setIsScheduling] = useDraftPart(
userId,
'commit',
'isScheduling',
);
const [scheduledAt, setScheduledAt] = useDraftPart(
userId,
'commit',
'scheduledAt',
);
const [timezone, setTimezone] = useDraftPart(userId, 'commit', 'timezone');

return {
isScheduling,
Expand Down Expand Up @@ -479,25 +488,21 @@ export function CommitComposer({
isInDialog?: boolean;
onCommitted?: () => void;
}) {
const schedule = useCommitSchedule();
const { isScheduling, scheduledAt, setScheduledAt, setIsScheduling } =
schedule;
const { user } = useUser();
const schedule = useCommitSchedule(user.id);
const { isScheduling, scheduledAt } = schedule;
const stash = useStash(user.id, 'commit');
const { body } = stash;
const linkCard = useLinkCard(body);
const [draftVersion, setDraftVersion] = useState(0);
const drop = () => {
stash.drop();
linkCard.reset();
setDraftVersion((version) => version + 1);
};
const linkCard = useLinkCard(
body,
useDraftPart(user.id, 'commit', 'linkCard'),
);
const attachments = useImageAttachments(
stash.images,
stash.descriptions,
stash.updateImages,
);
const [poll, setPoll] = useState<PollDraft | null>(null);
const [poll, setPoll] = useDraftPart(user.id, 'commit', 'poll');
const secrets = useSecretWarning(body);

const hasImages = attachments.images.length > 0;
Expand All @@ -511,13 +516,27 @@ export function CommitComposer({
(!isScheduling ||
(scheduledAt !== '' && isExistingLocalTime(scheduledAt)));

const [issueKind, setIssueKind] = useDraftPart(
user.id,
'commit',
'issueKind',
);
const [isOpeningIssue, setIsOpeningIssue] = useDraftPart(
user.id,
'commit',
'isOpeningIssue',
);
const issue = useIssueCommand({
body,
updateBody: stash.updateBody,
kind: issueKind,
setKind: setIssueKind,
isOpening: isOpeningIssue,
setIsOpening: setIsOpeningIssue,
enabled: !hasImages && poll === null && !isScheduling,
hasSecrets: secrets.findings.length > 0,
onCreated: () => {
drop();
stash.drop();
focusBody(id);
},
});
Expand Down Expand Up @@ -559,11 +578,8 @@ export function CommitComposer({
return;
}

drop();
setScheduledAt('');
setIsScheduling(false);
stash.drop();
attachments.clear();
setPoll(null);

if (onCommitted) {
onCommitted();
Expand All @@ -576,7 +592,7 @@ export function CommitComposer({
{({ processing, errors }) => (
<fieldset disabled={issue.processing} className="contents">
<CommitComposerBody
key={draftVersion}
key={stash.dropCount}
id={id}
isInDialog={isInDialog}
user={user}
Expand All @@ -592,7 +608,7 @@ export function CommitComposer({
issue={issue}
errors={errors}
onKeyDown={onKeyDown}
drop={drop}
drop={stash.drop}
/>
</fieldset>
)}
Expand Down
16 changes: 13 additions & 3 deletions resources/js/components/issue-command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,24 @@ function isHintDismissed(): boolean {
export function useIssueCommand({
body,
updateBody,
kind,
setKind,
isOpening,
setIsOpening,
enabled,
hasSecrets,
onCreated,
}: {
body: string;
updateBody: (body: string) => void;
kind: IssueKind | null;
setKind: (kind: IssueKind | null) => void;
isOpening: boolean;
setIsOpening: (isOpening: boolean) => void;
enabled: boolean;
hasSecrets: boolean;
onCreated: () => void;
}) {
const [kind, setKind] = useState<IssueKind | null>(null);
const [selected, setSelected] = useState(0);
const [dismissed, setDismissed] = useState(false);
const [hintDismissed, setHintDismissed] = useState(isHintDismissed);
Expand Down Expand Up @@ -73,7 +80,7 @@ export function useIssueCommand({
};

const submit = async () => {
if (command === null || request.processing) {
if (command === null || request.processing || isOpening) {
return;
}

Expand All @@ -85,6 +92,7 @@ export function useIssueCommand({
}

request.transform(() => ({ command: command.name, body }));
setIsOpening(true);

try {
const issue = await request.submit(store(), {
Expand Down Expand Up @@ -118,6 +126,8 @@ export function useIssueCommand({
});
} catch {
// The callbacks explain the failure; the draft stays an issue for a retry.
} finally {
setIsOpening(false);
}
};

Expand Down Expand Up @@ -199,7 +209,7 @@ export function useIssueCommand({
}
},
selected: selected % Math.max(suggestions.length, 1),
processing: request.processing,
processing: request.processing || isOpening,
change,
start,
exit: () => setKind(null),
Expand Down
13 changes: 10 additions & 3 deletions resources/js/hooks/use-link-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,17 @@ export type UseLinkCardReturn = {

/**
* Keeps which link of a commit being written gets the link card: the first one, unless the author chose another
* link that is still in the body, or no card at all.
* link that is still in the body, or no card at all. A composer that shares its draft passes the shared choice.
*/
export function useLinkCard(body: string): UseLinkCardReturn {
const [choice, setChoice] = useState<string | null>();
export function useLinkCard(
body: string,
shared?: [
string | null | undefined,
(choice: string | null | undefined) => void,
],
): UseLinkCardReturn {
const local = useState<string | null>();
const [choice, setChoice] = shared ?? local;
const links = typedLinks(body);
const chosen =
choice === null || (choice !== undefined && links.includes(choice))
Expand Down
Loading
Loading