Compare commits
1 Commits
main
...
957c3782dc
| Author | SHA1 | Date | |
|---|---|---|---|
|
957c3782dc
|
12
CHANGELOG.md
12
CHANGELOG.md
@@ -2,7 +2,17 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [0.1.1] - 2024-02-31
|
## [0.1.2] - 2024-02-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add "Plan task" action for notification created from task
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix "Complete task" action
|
||||||
|
|
||||||
|
## [0.1.1] - 2024-02-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
|||||||
@@ -62,12 +62,16 @@ export function NotificationActions({ notification, detailsTarget, mutate }: Not
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteNotification(notification: Notification, mutate: MutatePromise<Page<Notification> | undefined>) {
|
export async function deleteNotification(
|
||||||
|
notification: Notification,
|
||||||
|
mutate: MutatePromise<Page<Notification> | undefined>,
|
||||||
|
) {
|
||||||
const preferences = getPreferenceValues<UniversalInboxPreferences>();
|
const preferences = getPreferenceValues<UniversalInboxPreferences>();
|
||||||
const toast = await showToast({ style: Toast.Style.Animated, title: "Deleting notification" });
|
const toast = await showToast({ style: Toast.Style.Animated, title: "Deleting notification" });
|
||||||
try {
|
try {
|
||||||
if (isNotificationBuiltFromTask(notification) && notification.task) {
|
if (isNotificationBuiltFromTask(notification) && notification.task) {
|
||||||
await mutate(
|
await mutate(
|
||||||
|
handleErrors(
|
||||||
fetch(`${preferences.universalInboxBaseUrl}/api/tasks/${notification.task.id}`, {
|
fetch(`${preferences.universalInboxBaseUrl}/api/tasks/${notification.task.id}`, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: JSON.stringify({ status: TaskStatus.Deleted }),
|
body: JSON.stringify({ status: TaskStatus.Deleted }),
|
||||||
@@ -84,6 +88,7 @@ async function deleteNotification(notification: Notification, mutate: MutateProm
|
|||||||
return page;
|
return page;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await mutate(
|
await mutate(
|
||||||
@@ -118,7 +123,7 @@ async function deleteNotification(notification: Notification, mutate: MutateProm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unsubscribeFromNotification(
|
export async function unsubscribeFromNotification(
|
||||||
notification: Notification,
|
notification: Notification,
|
||||||
mutate: MutatePromise<Page<Notification> | undefined>,
|
mutate: MutatePromise<Page<Notification> | undefined>,
|
||||||
) {
|
) {
|
||||||
@@ -156,7 +161,10 @@ async function unsubscribeFromNotification(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function snoozeNotification(notification: Notification, mutate: MutatePromise<Page<Notification> | undefined>) {
|
export async function snoozeNotification(
|
||||||
|
notification: Notification,
|
||||||
|
mutate: MutatePromise<Page<Notification> | undefined>,
|
||||||
|
) {
|
||||||
const preferences = getPreferenceValues<UniversalInboxPreferences>();
|
const preferences = getPreferenceValues<UniversalInboxPreferences>();
|
||||||
const toast = await showToast({ style: Toast.Style.Animated, title: "Snoozing notification" });
|
const toast = await showToast({ style: Toast.Style.Animated, title: "Snoozing notification" });
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,24 +1,13 @@
|
|||||||
import { Notification, getNotificationHtmlUrl } from "../notification";
|
import { deleteNotification, snoozeNotification, unsubscribeFromNotification } from "./NotificationActions";
|
||||||
import { Action, ActionPanel, Icon } from "@raycast/api";
|
import { Notification, getNotificationHtmlUrl, isNotificationBuiltFromTask } from "../notification";
|
||||||
|
import { Action, ActionPanel, Icon, Toast, getPreferenceValues, showToast } from "@raycast/api";
|
||||||
import { MutatePromise } from "@raycast/utils";
|
import { MutatePromise } from "@raycast/utils";
|
||||||
import { useMemo, ReactElement } from "react";
|
import { useMemo, ReactElement } from "react";
|
||||||
|
import { PlanTask } from "./PlanTask";
|
||||||
|
import { handleErrors } from "../api";
|
||||||
|
import { TaskStatus } from "../task";
|
||||||
import { Page } from "../types";
|
import { Page } from "../types";
|
||||||
|
import fetch from "node-fetch";
|
||||||
function deleteNotification(notification: Notification) {
|
|
||||||
console.log(`Deleting notification ${notification.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function unsubscribeFromNotification(notification: Notification) {
|
|
||||||
console.log(`Unsubcribing from notification ${notification.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function snoozeNotification(notification: Notification) {
|
|
||||||
console.log(`Snoozing notification ${notification.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function completeTask(notification: Notification) {
|
|
||||||
console.log(`Completing task ${notification.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NotificationTaskActionsProps {
|
interface NotificationTaskActionsProps {
|
||||||
notification: Notification;
|
notification: Notification;
|
||||||
@@ -26,7 +15,7 @@ interface NotificationTaskActionsProps {
|
|||||||
mutate: MutatePromise<Page<Notification> | undefined>;
|
mutate: MutatePromise<Page<Notification> | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NotificationTaskActions({ notification, detailsTarget }: NotificationTaskActionsProps) {
|
export function NotificationTaskActions({ notification, detailsTarget, mutate }: NotificationTaskActionsProps) {
|
||||||
const notificationHtmlUrl = useMemo(() => {
|
const notificationHtmlUrl = useMemo(() => {
|
||||||
return getNotificationHtmlUrl(notification);
|
return getNotificationHtmlUrl(notification);
|
||||||
}, [notification]);
|
}, [notification]);
|
||||||
@@ -39,26 +28,71 @@ export function NotificationTaskActions({ notification, detailsTarget }: Notific
|
|||||||
title="Delete Notification"
|
title="Delete Notification"
|
||||||
icon={Icon.Trash}
|
icon={Icon.Trash}
|
||||||
shortcut={{ modifiers: ["ctrl"], key: "d" }}
|
shortcut={{ modifiers: ["ctrl"], key: "d" }}
|
||||||
onAction={() => deleteNotification(notification)}
|
onAction={() => deleteNotification(notification, mutate)}
|
||||||
/>
|
/>
|
||||||
<Action
|
<Action
|
||||||
title="Unsubscribe From Notification"
|
title="Unsubscribe From Notification"
|
||||||
icon={Icon.BellDisabled}
|
icon={Icon.BellDisabled}
|
||||||
shortcut={{ modifiers: ["ctrl"], key: "u" }}
|
shortcut={{ modifiers: ["ctrl"], key: "u" }}
|
||||||
onAction={() => unsubscribeFromNotification(notification)}
|
onAction={() => unsubscribeFromNotification(notification, mutate)}
|
||||||
/>
|
/>
|
||||||
<Action
|
<Action
|
||||||
title="Snooze"
|
title="Snooze"
|
||||||
icon={Icon.Clock}
|
icon={Icon.Clock}
|
||||||
shortcut={{ modifiers: ["ctrl"], key: "s" }}
|
shortcut={{ modifiers: ["ctrl"], key: "s" }}
|
||||||
onAction={() => snoozeNotification(notification)}
|
onAction={() => snoozeNotification(notification, mutate)}
|
||||||
/>
|
/>
|
||||||
<Action
|
<Action
|
||||||
title="Complete Task"
|
title="Complete Task"
|
||||||
icon={Icon.Calendar}
|
icon={Icon.Calendar}
|
||||||
shortcut={{ modifiers: ["ctrl"], key: "c" }}
|
shortcut={{ modifiers: ["ctrl"], key: "c" }}
|
||||||
onAction={() => completeTask(notification)}
|
onAction={() => completeTask(notification, mutate)}
|
||||||
|
/>
|
||||||
|
<Action.Push
|
||||||
|
title="Plan Task..."
|
||||||
|
icon={Icon.Calendar}
|
||||||
|
shortcut={{ modifiers: ["ctrl"], key: "t" }}
|
||||||
|
target={<PlanTask notification={notification} mutate={mutate} />}
|
||||||
/>
|
/>
|
||||||
</ActionPanel>
|
</ActionPanel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function completeTask(notification: Notification, mutate: MutatePromise<Page<Notification> | undefined>) {
|
||||||
|
if (!isNotificationBuiltFromTask(notification) || !notification.task) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const preferences = getPreferenceValues<UniversalInboxPreferences>();
|
||||||
|
const toast = await showToast({ style: Toast.Style.Animated, title: "Marking task as Done" });
|
||||||
|
try {
|
||||||
|
await mutate(
|
||||||
|
handleErrors(
|
||||||
|
fetch(`${preferences.universalInboxBaseUrl}/api/tasks/${notification.task.id}`, {
|
||||||
|
method: "PATCH",
|
||||||
|
body: JSON.stringify({ status: TaskStatus.Done }),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${preferences.apiKey}`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
{
|
||||||
|
optimisticUpdate(page) {
|
||||||
|
if (page) {
|
||||||
|
page.content = page.content.filter((n) => n.id !== notification.id);
|
||||||
|
}
|
||||||
|
return page;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
toast.style = Toast.Style.Success;
|
||||||
|
toast.title = "Task successfully marked as Done";
|
||||||
|
} catch (error) {
|
||||||
|
toast.style = Toast.Style.Failure;
|
||||||
|
toast.title = "Failed to mark task as Done";
|
||||||
|
toast.message = (error as Error).message;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
155
src/action/PlanTask.tsx
Normal file
155
src/action/PlanTask.tsx
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
import { Action, useNavigation, ActionPanel, Form, Icon, getPreferenceValues, showToast, Toast } from "@raycast/api";
|
||||||
|
import { MutatePromise, useForm, FormValidation, useFetch } from "@raycast/utils";
|
||||||
|
import { Notification, isNotificationBuiltFromTask } from "../notification";
|
||||||
|
import { Page, UniversalInboxPreferences } from "../types";
|
||||||
|
import { default as dayjs, extend } from "dayjs";
|
||||||
|
import { TaskPriority } from "../task";
|
||||||
|
import { handleErrors } from "../api";
|
||||||
|
import utc from "dayjs/plugin/utc";
|
||||||
|
import { useState } from "react";
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
|
||||||
|
extend(utc);
|
||||||
|
|
||||||
|
interface PlanTaskProps {
|
||||||
|
notification: Notification;
|
||||||
|
mutate: MutatePromise<Page<Notification> | undefined>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TaskPlanningFormValues {
|
||||||
|
project: string;
|
||||||
|
dueAt: Date | null;
|
||||||
|
priority: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProjectSummary {
|
||||||
|
name: string;
|
||||||
|
source_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TaskPlanning {
|
||||||
|
project: ProjectSummary;
|
||||||
|
due_at?: { type: "DateTimeWithTz"; content: string };
|
||||||
|
priority: TaskPriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PlanTask({ notification, mutate }: PlanTaskProps) {
|
||||||
|
const preferences = getPreferenceValues<UniversalInboxPreferences>();
|
||||||
|
const { pop } = useNavigation();
|
||||||
|
const [searchText, setSearchText] = useState("");
|
||||||
|
|
||||||
|
const { isLoading, data: projects } = useFetch<Array<ProjectSummary>>(
|
||||||
|
`${preferences.universalInboxBaseUrl}/api/tasks/projects/search?matches=${searchText}`,
|
||||||
|
{
|
||||||
|
keepPreviousData: true,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${preferences.apiKey}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { handleSubmit, itemProps } = useForm<TaskPlanningFormValues>({
|
||||||
|
initialValues: {
|
||||||
|
dueAt: new Date(),
|
||||||
|
priority: `${TaskPriority.P4 as number}`,
|
||||||
|
},
|
||||||
|
async onSubmit(values) {
|
||||||
|
const project = projects?.find((p) => p.source_id === values.project);
|
||||||
|
if (!project) {
|
||||||
|
throw new Error("Project not found");
|
||||||
|
}
|
||||||
|
const taskPlanning: TaskPlanning = {
|
||||||
|
project: project,
|
||||||
|
due_at: values.dueAt ? { type: "DateTimeWithTz", content: dayjs(values.dueAt).utc().format() } : undefined,
|
||||||
|
priority: parseInt(values.priority) as TaskPriority,
|
||||||
|
};
|
||||||
|
await planTask(taskPlanning, notification, mutate);
|
||||||
|
pop();
|
||||||
|
},
|
||||||
|
validation: {
|
||||||
|
project: FormValidation.Required,
|
||||||
|
priority: FormValidation.Required,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
navigationTitle="Plan task"
|
||||||
|
actions={
|
||||||
|
<ActionPanel>
|
||||||
|
<Action.SubmitForm title="Plan Task" icon={Icon.Calendar} onSubmit={handleSubmit} />
|
||||||
|
</ActionPanel>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Form.Description title="Task title" text={notification.title} />
|
||||||
|
<Form.Dropdown
|
||||||
|
title="Project"
|
||||||
|
placeholder="Search project..."
|
||||||
|
filtering={true}
|
||||||
|
throttle={true}
|
||||||
|
isLoading={isLoading}
|
||||||
|
onSearchTextChange={setSearchText}
|
||||||
|
{...itemProps.project}
|
||||||
|
>
|
||||||
|
<Form.Dropdown.Item value="" title="" key={0} />
|
||||||
|
{projects?.map((project) => {
|
||||||
|
return <Form.Dropdown.Item title={project.name} value={project.source_id} key={project.source_id} />;
|
||||||
|
})}
|
||||||
|
</Form.Dropdown>
|
||||||
|
<Form.DatePicker title="Due at" min={new Date()} type={Form.DatePicker.Type.Date} {...itemProps.dueAt} />
|
||||||
|
<Form.Dropdown title="Priority" {...itemProps.priority}>
|
||||||
|
<Form.Dropdown.Item title="Priority 1" value={`${TaskPriority.P1 as number}`} key={TaskPriority.P1} />
|
||||||
|
<Form.Dropdown.Item title="Priority 2" value={`${TaskPriority.P2 as number}`} key={TaskPriority.P2} />
|
||||||
|
<Form.Dropdown.Item title="Priority 3" value={`${TaskPriority.P3 as number}`} key={TaskPriority.P3} />
|
||||||
|
<Form.Dropdown.Item title="Priority 4" value={`${TaskPriority.P4 as number}`} key={TaskPriority.P4} />
|
||||||
|
</Form.Dropdown>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function planTask(
|
||||||
|
taskPlanning: TaskPlanning,
|
||||||
|
notification: Notification,
|
||||||
|
mutate: MutatePromise<Page<Notification> | undefined>,
|
||||||
|
) {
|
||||||
|
if (!isNotificationBuiltFromTask(notification) || !notification.task) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const preferences = getPreferenceValues<UniversalInboxPreferences>();
|
||||||
|
const toast = await showToast({ style: Toast.Style.Animated, title: "Planning task" });
|
||||||
|
try {
|
||||||
|
await mutate(
|
||||||
|
handleErrors(
|
||||||
|
fetch(`${preferences.universalInboxBaseUrl}/api/tasks/${notification.task.id}`, {
|
||||||
|
method: "PATCH",
|
||||||
|
body: JSON.stringify({
|
||||||
|
project: taskPlanning.project.name,
|
||||||
|
due_at: taskPlanning.due_at,
|
||||||
|
priority: taskPlanning.priority,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${preferences.apiKey}`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
{
|
||||||
|
optimisticUpdate(page) {
|
||||||
|
if (page) {
|
||||||
|
page.content = page.content.filter((n) => n.id !== notification.id);
|
||||||
|
}
|
||||||
|
return page;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
toast.style = Toast.Style.Success;
|
||||||
|
toast.title = "Task successfully planned";
|
||||||
|
} catch (error) {
|
||||||
|
toast.style = Toast.Style.Failure;
|
||||||
|
toast.title = "Failed to plan task";
|
||||||
|
toast.message = (error as Error).message;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user