From ea40258f48d84ac0f8d46e35e7b77eacf4fbe9e9 Mon Sep 17 00:00:00 2001 From: David Rousselie Date: Sat, 27 Jan 2024 00:09:50 +0100 Subject: [PATCH] feat: Add Todoist notification list item --- assets/linear-project-backlog.svg | 1 + assets/linear-project-canceled.svg | 1 + assets/linear-project-completed.svg | 1 + assets/linear-project-paused.svg | 1 + assets/linear-project-planned.svg | 1 + assets/linear-project-started.svg | 1 + src/action/CreateTaskFromNotification.tsx | 4 +- src/action/NotificationActions.tsx | 9 ++-- src/action/NotificationTaskActions.tsx | 5 +- src/index.tsx | 2 +- src/integrations/github/accessories.ts | 5 +- .../GithubDiscussionNotificationListItem.tsx | 4 +- .../listitem/GithubNotificationListItem.tsx | 11 ---- .../GithubPullRequestNotificationListItem.tsx | 4 +- .../preview/GithubDiscussionPreview.tsx | 4 +- .../preview/GithubPullRequestPreview.tsx | 4 +- .../GoogleMailNotificationListItem.tsx | 10 ---- .../listitem/GoogleMailThreadListItem.tsx | 10 +--- .../preview/GoogleMailThreadPreview.tsx | 4 +- src/integrations/linear/accessories.ts | 3 +- .../LinearIssueNotificationListItem.tsx | 4 +- .../listitem/LinearNotificationListItem.tsx | 11 ---- .../LinearProjectNotificationListItem.tsx | 37 +++++++++++-- .../linear/preview/LinearIssuePreview.tsx | 4 +- .../linear/preview/LinearProjectPreview.tsx | 4 +- .../todoist/TodoistNotificationListItem.tsx | 29 ----------- .../listitem/TodoistNotificationListItem.tsx | 52 +++++++++++++++++++ .../todoist/preview/TodoistTaskPreview.tsx | 24 +++++++++ src/notification.ts | 2 +- 29 files changed, 146 insertions(+), 106 deletions(-) create mode 100644 assets/linear-project-backlog.svg create mode 100644 assets/linear-project-canceled.svg create mode 100644 assets/linear-project-completed.svg create mode 100644 assets/linear-project-paused.svg create mode 100644 assets/linear-project-planned.svg create mode 100644 assets/linear-project-started.svg delete mode 100644 src/integrations/todoist/TodoistNotificationListItem.tsx create mode 100644 src/integrations/todoist/listitem/TodoistNotificationListItem.tsx create mode 100644 src/integrations/todoist/preview/TodoistTaskPreview.tsx diff --git a/assets/linear-project-backlog.svg b/assets/linear-project-backlog.svg new file mode 100644 index 0000000..6729424 --- /dev/null +++ b/assets/linear-project-backlog.svg @@ -0,0 +1 @@ + diff --git a/assets/linear-project-canceled.svg b/assets/linear-project-canceled.svg new file mode 100644 index 0000000..0410953 --- /dev/null +++ b/assets/linear-project-canceled.svg @@ -0,0 +1 @@ + diff --git a/assets/linear-project-completed.svg b/assets/linear-project-completed.svg new file mode 100644 index 0000000..5a939e7 --- /dev/null +++ b/assets/linear-project-completed.svg @@ -0,0 +1 @@ + diff --git a/assets/linear-project-paused.svg b/assets/linear-project-paused.svg new file mode 100644 index 0000000..099dac5 --- /dev/null +++ b/assets/linear-project-paused.svg @@ -0,0 +1 @@ + diff --git a/assets/linear-project-planned.svg b/assets/linear-project-planned.svg new file mode 100644 index 0000000..f401046 --- /dev/null +++ b/assets/linear-project-planned.svg @@ -0,0 +1 @@ + diff --git a/assets/linear-project-started.svg b/assets/linear-project-started.svg new file mode 100644 index 0000000..ccf1843 --- /dev/null +++ b/assets/linear-project-started.svg @@ -0,0 +1 @@ + diff --git a/src/action/CreateTaskFromNotification.tsx b/src/action/CreateTaskFromNotification.tsx index cc9892d..bbf6d4e 100644 --- a/src/action/CreateTaskFromNotification.tsx +++ b/src/action/CreateTaskFromNotification.tsx @@ -1,15 +1,15 @@ import { Action, useNavigation, ActionPanel, Form, Icon, getPreferenceValues, showToast, Toast } from "@raycast/api"; import { MutatePromise, useForm, FormValidation, useFetch } from "@raycast/utils"; import { Page, UniversalInboxPreferences } from "../types"; +import { default as dayjs, extend } from "dayjs"; import { Notification } from "../notification"; import { TaskPriority } from "../task"; import { handleErrors } from "../api"; import utc from "dayjs/plugin/utc"; import { useState } from "react"; import fetch from "node-fetch"; -import dayjs from "dayjs"; -dayjs.extend(utc); +extend(utc); interface CreateTaskFromNotificationProps { notification: Notification; diff --git a/src/action/NotificationActions.tsx b/src/action/NotificationActions.tsx index 1f4385e..9fa4cd2 100644 --- a/src/action/NotificationActions.tsx +++ b/src/action/NotificationActions.tsx @@ -3,15 +3,15 @@ import { Action, ActionPanel, Icon, getPreferenceValues, showToast, Toast } from import { CreateTaskFromNotification } from "./CreateTaskFromNotification"; import { LinkNotificationToTask } from "./LinkNotificationToTask"; import { Page, UniversalInboxPreferences } from "../types"; +import { default as dayjs, extend } from "dayjs"; import { MutatePromise } from "@raycast/utils"; import { useMemo, ReactElement } from "react"; import { handleErrors } from "../api"; import { TaskStatus } from "../task"; import utc from "dayjs/plugin/utc"; import fetch from "node-fetch"; -import dayjs from "dayjs"; -dayjs.extend(utc); +extend(utc); interface NotificationActionsProps { notification: Notification; @@ -20,14 +20,13 @@ interface NotificationActionsProps { } export function NotificationActions({ notification, detailsTarget, mutate }: NotificationActionsProps) { - const notification_html_url = useMemo(() => { + const notificationHtmlUrl = useMemo(() => { return getNotificationHtmlUrl(notification); }, [notification]); - console.log("notification_html_url", notification_html_url, notification); return ( - + | undefined>; } + export function NotificationTaskActions({ notification, detailsTarget }: NotificationTaskActionsProps) { - const notification_html_url = useMemo(() => { + const notificationHtmlUrl = useMemo(() => { return getNotificationHtmlUrl(notification); }, [notification]); return ( - + | undefined>; } export function GithubDiscussionNotificationListItem({ - icon, notification, githubDiscussion, mutate, @@ -45,7 +43,7 @@ export function GithubDiscussionNotificationListItem({ { - if (environment.appearance === "dark") { - return "github-logo-light.svg"; - } - return "github-logo-dark.svg"; - }, [environment]); - switch (notification.details?.type) { case "GithubPullRequest": return ( | undefined>; } export function GithubPullRequestNotificationListItem({ - icon, notification, githubPullRequest, mutate, @@ -64,7 +62,7 @@ export function GithubPullRequestNotificationListItem({ { + const notificationHtmlUrl = useMemo(() => { return getNotificationHtmlUrl(notification); }, [notification]); @@ -18,7 +18,7 @@ export function GithubDiscussionPreview({ notification, githubDiscussion }: Gith markdown={`# ${githubDiscussion.title}`} actions={ - + } /> diff --git a/src/integrations/github/preview/GithubPullRequestPreview.tsx b/src/integrations/github/preview/GithubPullRequestPreview.tsx index 8c332ae..0b0ece6 100644 --- a/src/integrations/github/preview/GithubPullRequestPreview.tsx +++ b/src/integrations/github/preview/GithubPullRequestPreview.tsx @@ -9,7 +9,7 @@ interface GithubPullRequestPreviewProps { } export function GithubPullRequestPreview({ notification, githubPullRequest }: GithubPullRequestPreviewProps) { - const notification_html_url = useMemo(() => { + const notificationHtmlUrl = useMemo(() => { return getNotificationHtmlUrl(notification); }, [notification]); @@ -18,7 +18,7 @@ export function GithubPullRequestPreview({ notification, githubPullRequest }: Gi markdown={`# ${githubPullRequest.title}`} actions={ - + } /> diff --git a/src/integrations/google-mail/listitem/GoogleMailNotificationListItem.tsx b/src/integrations/google-mail/listitem/GoogleMailNotificationListItem.tsx index 208fc7e..b4161ef 100644 --- a/src/integrations/google-mail/listitem/GoogleMailNotificationListItem.tsx +++ b/src/integrations/google-mail/listitem/GoogleMailNotificationListItem.tsx @@ -1,21 +1,11 @@ import { GoogleMailThreadListItem } from "./GoogleMailThreadListItem"; import { NotificationListItemProps } from "../../../notification"; -import { environment } from "@raycast/api"; -import { useMemo } from "react"; export function GoogleMailNotificationListItem({ notification, mutate }: NotificationListItemProps) { - const icon = useMemo(() => { - if (environment.appearance === "dark") { - return "google-mail-logo-light.svg"; - } - return "google-mail-logo-dark.svg"; - }, [environment]); - if (notification.metadata.type !== "GoogleMail") return null; return ( | undefined>; } -export function GoogleMailThreadListItem({ - icon, - notification, - googleMailThread, - mutate, -}: GoogleMailThreadListItemProps) { +export function GoogleMailThreadListItem({ notification, googleMailThread, mutate }: GoogleMailThreadListItemProps) { const isStarred = googleMailThread.messages.some((message) => message.labelIds?.includes("STARRED")); const isImportant = googleMailThread.messages.some((message) => message.labelIds?.includes("IMPORTANT")); const fromAddress = googleMailThread.messages[0].payload.headers.find((header) => header.name === "From")?.value; @@ -50,7 +44,7 @@ export function GoogleMailThreadListItem({ { + const notificationHtmlUrl = useMemo(() => { return getNotificationHtmlUrl(notification); }, [notification]); @@ -18,7 +18,7 @@ export function GoogleMailThreadPreview({ notification }: GoogleMailThreadPrevie markdown={`# ${notification.title}`} actions={ - + } /> diff --git a/src/integrations/linear/accessories.ts b/src/integrations/linear/accessories.ts index 9bdffbf..aad852f 100644 --- a/src/integrations/linear/accessories.ts +++ b/src/integrations/linear/accessories.ts @@ -1,10 +1,11 @@ import { Icon, Image, List } from "@raycast/api"; +import { getAvatarIcon } from "@raycast/utils"; import { LinearUser } from "./types"; export function getLinearUserAccessory(user?: LinearUser): List.Item.Accessory { if (user) { return { - icon: user.avatar_url ? { source: user.avatar_url, mask: Image.Mask.Circle } : Icon.Person, + icon: user.avatar_url ? { source: user.avatar_url, mask: Image.Mask.Circle } : getAvatarIcon(user.name), tooltip: user.name, }; } diff --git a/src/integrations/linear/listitem/LinearIssueNotificationListItem.tsx b/src/integrations/linear/listitem/LinearIssueNotificationListItem.tsx index c2302ea..f819c25 100644 --- a/src/integrations/linear/listitem/LinearIssueNotificationListItem.tsx +++ b/src/integrations/linear/listitem/LinearIssueNotificationListItem.tsx @@ -9,14 +9,12 @@ import { List } from "@raycast/api"; import { match } from "ts-pattern"; interface LinearIssueNotificationListItemProps { - icon: string; notification: Notification; linearIssueNotification: LinearIssueNotification; mutate: MutatePromise | undefined>; } export function LinearIssueNotificationListItem({ - icon, notification, linearIssueNotification, mutate, @@ -39,7 +37,7 @@ export function LinearIssueNotificationListItem({ { - if (environment.appearance === "dark") { - return "linear-logo-light.svg"; - } - return "linear-logo-dark.svg"; - }, [environment]); - if (notification.metadata.type !== "Linear") return null; switch (notification.metadata.content.type) { case "IssueNotification": return ( | undefined>; } export function LinearProjectNotificationListItem({ - icon, notification, linearProjectNotification, mutate, }: LinearProjectNotificationListItemProps) { const subtitle = linearProjectNotification.project.name; + const state = getLinearProjectStateAccessory(linearProjectNotification.project); const lead = getLinearUserAccessory(linearProjectNotification.project.lead); const accessories: List.Item.Accessory[] = [ + state, lead, { date: new Date(linearProjectNotification.updated_at), @@ -36,7 +37,7 @@ export function LinearProjectNotificationListItem({ ); } + +export function getLinearProjectStateAccessory(project: LinearProject): List.Item.Accessory { + return { + icon: match(project) + .with({ state: LinearProjectState.Planned }, () => { + return { source: "linear-project-planned.svg", tintColor: Color.SecondaryText }; + }) + .with({ state: LinearProjectState.Backlog }, () => { + return { source: "linear-project-backlog.svg", tintColor: Color.PrimaryText }; + }) + .with({ state: LinearProjectState.Started }, () => { + return { source: "linear-project-started.svg", tintColor: Color.Blue }; + }) + .with({ state: LinearProjectState.Paused }, () => { + return { source: "linear-project-paused.svg", tintColor: Color.PrimaryText }; + }) + .with({ state: LinearProjectState.Completed }, () => { + return { source: "linear-project-completed.svg", tintColor: Color.Magenta }; + }) + .with({ state: LinearProjectState.Canceled }, () => { + return { source: "linear-project-canceled.svg", tintColor: Color.SecondaryText }; + }) + .exhaustive(), + tooltip: project.state, + }; +} diff --git a/src/integrations/linear/preview/LinearIssuePreview.tsx b/src/integrations/linear/preview/LinearIssuePreview.tsx index 136ea92..5aeb74a 100644 --- a/src/integrations/linear/preview/LinearIssuePreview.tsx +++ b/src/integrations/linear/preview/LinearIssuePreview.tsx @@ -9,7 +9,7 @@ interface LinearIssuePreviewProps { } export function LinearIssuePreview({ notification, linearIssue }: LinearIssuePreviewProps) { - const notification_html_url = useMemo(() => { + const notificationHtmlUrl = useMemo(() => { return getNotificationHtmlUrl(notification); }, [notification]); @@ -18,7 +18,7 @@ export function LinearIssuePreview({ notification, linearIssue }: LinearIssuePre markdown={`# ${linearIssue.title}`} actions={ - + } /> diff --git a/src/integrations/linear/preview/LinearProjectPreview.tsx b/src/integrations/linear/preview/LinearProjectPreview.tsx index 9be85e1..dbe453a 100644 --- a/src/integrations/linear/preview/LinearProjectPreview.tsx +++ b/src/integrations/linear/preview/LinearProjectPreview.tsx @@ -9,7 +9,7 @@ interface LinearProjectPreviewProps { } export function LinearProjectPreview({ notification, linearProject }: LinearProjectPreviewProps) { - const notification_html_url = useMemo(() => { + const notificationHtmlUrl = useMemo(() => { return getNotificationHtmlUrl(notification); }, [notification]); @@ -18,7 +18,7 @@ export function LinearProjectPreview({ notification, linearProject }: LinearProj markdown={`# ${linearProject.name}`} actions={ - + } /> diff --git a/src/integrations/todoist/TodoistNotificationListItem.tsx b/src/integrations/todoist/TodoistNotificationListItem.tsx deleted file mode 100644 index c8a2840..0000000 --- a/src/integrations/todoist/TodoistNotificationListItem.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { NotificationTaskActions } from "../../action/NotificationTaskActions"; -import { NotificationListItemProps } from "../../notification"; -import { Detail, List, environment } from "@raycast/api"; -import { useMemo } from "react"; - -export function TodoistNotificationListItem({ notification, mutate }: NotificationListItemProps) { - const icon = useMemo(() => { - if (environment.appearance === "dark") { - return "todoist-icon-light.svg"; - } - return "todoist-icon-dark.svg"; - }, [environment]); - - return ( - } - mutate={mutate} - /> - } - /> - ); -} diff --git a/src/integrations/todoist/listitem/TodoistNotificationListItem.tsx b/src/integrations/todoist/listitem/TodoistNotificationListItem.tsx new file mode 100644 index 0000000..a143a3d --- /dev/null +++ b/src/integrations/todoist/listitem/TodoistNotificationListItem.tsx @@ -0,0 +1,52 @@ +import { NotificationTaskActions } from "../../../action/NotificationTaskActions"; +import { TodoistTaskPreview } from "../preview/TodoistTaskPreview"; +import { NotificationListItemProps } from "../../../notification"; +import { Icon, List, Color } from "@raycast/api"; +import { TaskPriority } from "../../../task"; +import { match } from "ts-pattern"; +import dayjs from "dayjs"; + +export function TodoistNotificationListItem({ notification, mutate }: NotificationListItemProps) { + const dueAt = notification.task?.due_at?.content; + const subtitle = dueAt ? dayjs(dueAt).format("YYYY-MM-DD") : undefined; + + const color = match(notification) + .with({ task: { priority: TaskPriority.P1 } }, () => Color.Red) + .with({ task: { priority: TaskPriority.P2 } }, () => Color.Orange) + .with({ task: { priority: TaskPriority.P3 } }, () => Color.Yellow) + .otherwise(() => null); + + const accessories: List.Item.Accessory[] = [ + { + icon: { source: Icon.Circle, tintColor: color }, + }, + { + date: new Date(notification.updated_at), + tooltip: `Updated at ${notification.updated_at}`, + }, + ]; + + const task = notification.task; + if (task) { + for (const tag of task.tags) { + accessories.unshift({ tag: { value: tag } }); + } + } + + return ( + } + mutate={mutate} + /> + } + /> + ); +} diff --git a/src/integrations/todoist/preview/TodoistTaskPreview.tsx b/src/integrations/todoist/preview/TodoistTaskPreview.tsx new file mode 100644 index 0000000..180ae8f --- /dev/null +++ b/src/integrations/todoist/preview/TodoistTaskPreview.tsx @@ -0,0 +1,24 @@ +import { Notification, getNotificationHtmlUrl } from "../../../notification"; +import { Detail, ActionPanel, Action } from "@raycast/api"; +import { useMemo } from "react"; + +interface TodoistTaskPreviewProps { + notification: Notification; +} + +export function TodoistTaskPreview({ notification }: TodoistTaskPreviewProps) { + const notificationHtmlUrl = useMemo(() => { + return getNotificationHtmlUrl(notification); + }, [notification]); + + return ( + + + + } + /> + ); +} diff --git a/src/notification.ts b/src/notification.ts index 5e7fc11..7f4ba87 100644 --- a/src/notification.ts +++ b/src/notification.ts @@ -64,7 +64,7 @@ export function getNotificationHtmlUrl(notification: Notification) { (googleMailThread) => `https://mail.google.com/mail/u/${googleMailThread.user_email_address}/#inbox/${googleMailThread.id}`, ) - .with({ metadata: { type: "Todoist", content: P.select() } }, () => "") + .with({ metadata: { type: "Todoist" } }, () => `https://todoist.com/showTask?id=${notification.source_id}`) .with({ metadata: { type: "Github" } }, () => "https://github.com") .exhaustive(); }