Compare commits

..

1 Commits

24 changed files with 391 additions and 1341 deletions

View File

@@ -2,22 +2,6 @@
## [Unreleased] ## [Unreleased]
## [0.1.4] - 2024-03-13
### Added
- Add Linear Project and Team icons
- Add Slack notifications (ie. save for later) support
- Set `Inbox` project as default when creating or planning a task
## [0.1.3] - 2024-02-05
### Added
- Display Linear notification reason
- Display Linear project on notification item
- Add missing action icons
## [0.1.2] - 2024-02-01 ## [0.1.2] - 2024-02-01
### Added ### Added

View File

@@ -1,3 +0,0 @@
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16" height="200px" width="200px" xmlns="http://www.w3.org/2000/svg">
<path d="M3.362 10.11c0 .926-.756 1.681-1.681 1.681S0 11.036 0 10.111C0 9.186.756 8.43 1.68 8.43h1.682v1.68zm.846 0c0-.924.756-1.68 1.681-1.68s1.681.756 1.681 1.68v4.21c0 .924-.756 1.68-1.68 1.68a1.685 1.685 0 0 1-1.682-1.68v-4.21zM5.89 3.362c-.926 0-1.682-.756-1.682-1.681S4.964 0 5.89 0s1.68.756 1.68 1.68v1.682H5.89zm0 .846c.924 0 1.68.756 1.68 1.681S6.814 7.57 5.89 7.57H1.68C.757 7.57 0 6.814 0 5.89c0-.926.756-1.682 1.68-1.682h4.21zm6.749 1.682c0-.926.755-1.682 1.68-1.682.925 0 1.681.756 1.681 1.681s-.756 1.681-1.68 1.681h-1.681V5.89zm-.848 0c0 .924-.755 1.68-1.68 1.68A1.685 1.685 0 0 1 8.43 5.89V1.68C8.43.757 9.186 0 10.11 0c.926 0 1.681.756 1.681 1.68v4.21zm-1.681 6.748c.926 0 1.682.756 1.682 1.681S11.036 16 10.11 16s-1.681-.756-1.681-1.68v-1.682h1.68zm0-.847c-.924 0-1.68-.755-1.68-1.68 0-.925.756-1.681 1.68-1.681h4.21c.924 0 1.68.756 1.68 1.68 0 .926-.756 1.681-1.68 1.681h-4.21z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,2 +0,0 @@
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16" height="200px" width="200px" xmlns="http://www.w3.org/2000/svg"><path d="M3.362 10.11c0 .926-.756 1.681-1.681 1.681S0 11.036 0 10.111C0 9.186.756 8.43 1.68 8.43h1.682v1.68zm.846 0c0-.924.756-1.68 1.681-1.68s1.681.756 1.681 1.68v4.21c0 .924-.756 1.68-1.68 1.68a1.685 1.685 0 0 1-1.682-1.68v-4.21zM5.89 3.362c-.926 0-1.682-.756-1.682-1.681S4.964 0 5.89 0s1.68.756 1.68 1.68v1.682H5.89zm0 .846c.924 0 1.68.756 1.68 1.681S6.814 7.57 5.89 7.57H1.68C.757 7.57 0 6.814 0 5.89c0-.926.756-1.682 1.68-1.682h4.21zm6.749 1.682c0-.926.755-1.682 1.68-1.682.925 0 1.681.756 1.681 1.681s-.756 1.681-1.68 1.681h-1.681V5.89zm-.848 0c0 .924-.755 1.68-1.68 1.68A1.685 1.685 0 0 1 8.43 5.89V1.68C8.43.757 9.186 0 10.11 0c.926 0 1.681.756 1.681 1.68v4.21zm-1.681 6.748c.926 0 1.682.756 1.682 1.681S11.036 16 10.11 16s-1.681-.756-1.681-1.68v-1.682h1.68zm0-.847c-.924 0-1.68-.755-1.68-1.68 0-.925.756-1.681 1.68-1.681h4.21c.924 0 1.68.756 1.68 1.68 0 .926-.756 1.681-1.68 1.681h-4.21z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -20,7 +20,3 @@ fix:
# Run extension in development mode # Run extension in development mode
run: run:
npm run dev npm run dev
# Publish a new version of the extension
publish:
npm run publish

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

1004
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"description": "Manage your notifications in a single Universal Inbox", "description": "Manage your notifications in a single Universal Inbox",
"icon": "ui-logo-transparent.png", "icon": "ui-logo-transparent.png",
"author": "dax42", "author": "dax42",
"version": "0.1.4", "version": "0.1.1",
"categories": [ "categories": [
"Productivity" "Productivity"
], ],
@@ -45,9 +45,9 @@
}, },
"devDependencies": { "devDependencies": {
"@raycast/eslint-config": "^1.0.6", "@raycast/eslint-config": "^1.0.6",
"@types/node": "^20.8.10", "@types/node": "20.8.10",
"@types/react": "^18.2.27", "@types/react": "18.2.27",
"@typescript-eslint/parser": "^7.2.0", "@typescript-eslint/parser": "^6.19.1",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-import-resolver-typescript": "^3.6.1", "eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",

View File

@@ -53,7 +53,6 @@ export function CreateTaskFromNotification({ notification, mutate }: CreateTaskF
const { handleSubmit, itemProps } = useForm<TaskCreationFormValues>({ const { handleSubmit, itemProps } = useForm<TaskCreationFormValues>({
initialValues: { initialValues: {
title: notification.title, title: notification.title,
project: projects?.find((p) => p.name === "Inbox")?.source_id,
dueAt: new Date(), dueAt: new Date(),
priority: `${TaskPriority.P4 as number}`, priority: `${TaskPriority.P4 as number}`,
}, },

View File

@@ -92,6 +92,7 @@ async function linkNotificationToTask(
if (page) { if (page) {
page.content = page.content.filter((n) => n.id !== notification.id); page.content = page.content.filter((n) => n.id !== notification.id);
} }
console.log(`page(link): ${notification.id}`, page);
return page; return page;
}, },
}, },

View File

@@ -27,7 +27,7 @@ export function NotificationActions({ notification, detailsTarget, mutate }: Not
return ( return (
<ActionPanel> <ActionPanel>
<Action.OpenInBrowser url={notificationHtmlUrl} /> <Action.OpenInBrowser url={notificationHtmlUrl} />
<Action.Push title="Show Details" icon={Icon.AppWindowSidebarRight} target={detailsTarget} /> <Action.Push title="Show Details" target={detailsTarget} />
<Action <Action
title="Delete Notification" title="Delete Notification"
icon={Icon.Trash} icon={Icon.Trash}
@@ -80,15 +80,15 @@ export async function deleteNotification(
Authorization: `Bearer ${preferences.apiKey}`, Authorization: `Bearer ${preferences.apiKey}`,
}, },
}), }),
), {
{ optimisticUpdate(page) {
optimisticUpdate(page) { if (page) {
if (page) { page.content = page.content.filter((n) => n.id !== notification.id);
page.content = page.content.filter((n) => n.id !== notification.id); }
} return page;
return page; },
}, },
}, ),
); );
} else { } else {
await mutate( await mutate(

View File

@@ -1,12 +1,12 @@
import { deleteNotification, snoozeNotification, unsubscribeFromNotification } from "./NotificationActions"; import { deleteNotification, snoozeNotification, unsubscribeFromNotification } from "./NotificationActions";
import { Notification, getNotificationHtmlUrl, isNotificationBuiltFromTask } from "../notification"; import { Notification, getNotificationHtmlUrl, isNotificationBuiltFromTask } from "../notification";
import { Action, ActionPanel, Icon, Toast, getPreferenceValues, showToast } from "@raycast/api"; import { Action, ActionPanel, Icon, Toast, getPreferenceValues, showToast } from "@raycast/api";
import { Page, UniversalInboxPreferences } from "../types";
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 { PlanTask } from "./PlanTask";
import { handleErrors } from "../api"; import { handleErrors } from "../api";
import { TaskStatus } from "../task"; import { TaskStatus } from "../task";
import { Page } from "../types";
import fetch from "node-fetch"; import fetch from "node-fetch";
interface NotificationTaskActionsProps { interface NotificationTaskActionsProps {
@@ -23,7 +23,7 @@ export function NotificationTaskActions({ notification, detailsTarget, mutate }:
return ( return (
<ActionPanel> <ActionPanel>
<Action.OpenInBrowser url={notificationHtmlUrl} /> <Action.OpenInBrowser url={notificationHtmlUrl} />
<Action.Push title="Show Details" icon={Icon.AppWindowSidebarRight} target={detailsTarget} /> <Action.Push title="Show Details" target={detailsTarget} />
<Action <Action
title="Delete Notification" title="Delete Notification"
icon={Icon.Trash} icon={Icon.Trash}
@@ -44,7 +44,7 @@ export function NotificationTaskActions({ notification, detailsTarget, mutate }:
/> />
<Action <Action
title="Complete Task" title="Complete Task"
icon={Icon.CheckCircle} icon={Icon.Calendar}
shortcut={{ modifiers: ["ctrl"], key: "c" }} shortcut={{ modifiers: ["ctrl"], key: "c" }}
onAction={() => completeTask(notification, mutate)} onAction={() => completeTask(notification, mutate)}
/> />

View File

@@ -51,7 +51,6 @@ export function PlanTask({ notification, mutate }: PlanTaskProps) {
const { handleSubmit, itemProps } = useForm<TaskPlanningFormValues>({ const { handleSubmit, itemProps } = useForm<TaskPlanningFormValues>({
initialValues: { initialValues: {
dueAt: new Date(), dueAt: new Date(),
project: projects?.find((p) => p.name === "Inbox")?.source_id,
priority: `${TaskPriority.P4 as number}`, priority: `${TaskPriority.P4 as number}`,
}, },
async onSubmit(values) { async onSubmit(values) {

View File

@@ -1,9 +1,8 @@
import { Action, ActionPanel, Detail, Icon, List, getPreferenceValues, openExtensionPreferences } from "@raycast/api";
import { GoogleMailNotificationListItem } from "./integrations/google-mail/listitem/GoogleMailNotificationListItem"; import { GoogleMailNotificationListItem } from "./integrations/google-mail/listitem/GoogleMailNotificationListItem";
import { Action, ActionPanel, Detail, List, getPreferenceValues, openExtensionPreferences } from "@raycast/api";
import { TodoistNotificationListItem } from "./integrations/todoist/listitem/TodoistNotificationListItem"; import { TodoistNotificationListItem } from "./integrations/todoist/listitem/TodoistNotificationListItem";
import { GithubNotificationListItem } from "./integrations/github/listitem/GithubNotificationListItem"; import { GithubNotificationListItem } from "./integrations/github/listitem/GithubNotificationListItem";
import { LinearNotificationListItem } from "./integrations/linear/listitem/LinearNotificationListItem"; import { LinearNotificationListItem } from "./integrations/linear/listitem/LinearNotificationListItem";
import { SlackNotificationListItem } from "./integrations/slack/listitem/SlackNotificationListItem";
import { Notification, NotificationListItemProps } from "./notification"; import { Notification, NotificationListItemProps } from "./notification";
import { NotificationActions } from "./action/NotificationActions"; import { NotificationActions } from "./action/NotificationActions";
import { Page, UniversalInboxPreferences } from "./types"; import { Page, UniversalInboxPreferences } from "./types";
@@ -24,7 +23,7 @@ export default function Command() {
markdown={"API key incorrect. Please update it in extension preferences and try again."} markdown={"API key incorrect. Please update it in extension preferences and try again."}
actions={ actions={
<ActionPanel> <ActionPanel>
<Action title="Open Extension Preferences" icon={Icon.Gear} onAction={openExtensionPreferences} /> <Action title="Open Extension Preferences" onAction={openExtensionPreferences} />
</ActionPanel> </ActionPanel>
} }
/> />
@@ -74,8 +73,6 @@ function NotificationListItem({ notification, mutate }: NotificationListItemProp
return <LinearNotificationListItem notification={notification} mutate={mutate} />; return <LinearNotificationListItem notification={notification} mutate={mutate} />;
case "GoogleMail": case "GoogleMail":
return <GoogleMailNotificationListItem notification={notification} mutate={mutate} />; return <GoogleMailNotificationListItem notification={notification} mutate={mutate} />;
case "Slack":
return <SlackNotificationListItem notification={notification} mutate={mutate} />;
case "Todoist": case "Todoist":
return <TodoistNotificationListItem notification={notification} mutate={mutate} />; return <TodoistNotificationListItem notification={notification} mutate={mutate} />;
default: default:
@@ -113,7 +110,6 @@ function NotificationKindDropdown({ value, onNotificationKindChange }: Notificat
<List.Dropdown.Item key="Github" title="Github" value="Github" /> <List.Dropdown.Item key="Github" title="Github" value="Github" />
<List.Dropdown.Item key="Linear" title="Linear" value="Linear" /> <List.Dropdown.Item key="Linear" title="Linear" value="Linear" />
<List.Dropdown.Item key="GoogleMail" title="Google Mail" value="GoogleMail" /> <List.Dropdown.Item key="GoogleMail" title="Google Mail" value="GoogleMail" />
<List.Dropdown.Item key="Slack" title="Slack" value="Slack" />
<List.Dropdown.Item key="Todoist" title="Todoist" value="Todoist" /> <List.Dropdown.Item key="Todoist" title="Todoist" value="Todoist" />
</List.Dropdown.Section> </List.Dropdown.Section>
</List.Dropdown> </List.Dropdown>

View File

@@ -1,7 +1,6 @@
import { Icon, Image, List } from "@raycast/api"; import { Icon, Image, List } from "@raycast/api";
import { getAvatarIcon } from "@raycast/utils"; import { getAvatarIcon } from "@raycast/utils";
import { LinearUser } from "./types"; import { LinearUser } from "./types";
import { match } from "ts-pattern";
export function getLinearUserAccessory(user?: LinearUser): List.Item.Accessory { export function getLinearUserAccessory(user?: LinearUser): List.Item.Accessory {
if (user) { if (user) {
@@ -12,26 +11,3 @@ export function getLinearUserAccessory(user?: LinearUser): List.Item.Accessory {
} }
return { icon: Icon.Person, tooltip: "Unknown" }; return { icon: Icon.Person, tooltip: "Unknown" };
} }
export function getLinearNotificationReasonAccessory(notification_type: string): List.Item.Accessory {
const reason = match(notification_type)
.with("issueAddedToTriage", () => "Added To Triage")
.with("issueAddedToView", () => "Added To View")
.with("issueAssignedToYou", () => "Assigned To You")
.with("issueBlocking", () => "Blocked")
.with("issueCommentMention", () => "Comment Mention")
.with("issueCommentReaction", () => "Comment Reaction")
.with("issueCreated", () => "Created")
.with("issueDue", () => "Due")
.with("issueEmojiReaction", () => "Reaction")
.with("issueMention", () => "Mention")
.with("issueNewComment", () => "New Comment")
.with("issueStatusChanged", () => "Status Changed")
.with("issueUnassignedFromYou", () => "Unassigned From You")
.with("projectAddedAsLead", () => "Added As Lead")
.with("projectAddedAsMember", () => "Added As Member")
.with("projectUpdateCreated", () => "Update Created")
.with("projectUpdateMentionPrompt", () => "Update Mention")
.otherwise(() => notification_type);
return { tag: { value: reason } };
}

View File

@@ -1,12 +1,12 @@
import { LinearWorkflowStateType, LinearIssueNotification, LinearWorkflowState } from "../types"; import { LinearWorkflowStateType, LinearIssueNotification, LinearWorkflowState } from "../types";
import { getLinearNotificationReasonAccessory, getLinearUserAccessory } from "../accessories";
import { NotificationActions } from "../../../action/NotificationActions"; import { NotificationActions } from "../../../action/NotificationActions";
import { LinearIssuePreview } from "../preview/LinearIssuePreview"; import { LinearIssuePreview } from "../preview/LinearIssuePreview";
import { getLinearUserAccessory } from "../accessories";
import { Notification } from "../../../notification"; import { Notification } from "../../../notification";
import { MutatePromise } from "@raycast/utils"; import { MutatePromise } from "@raycast/utils";
import { Page } from "../../../types"; import { Page } from "../../../types";
import { match, P } from "ts-pattern";
import { List } from "@raycast/api"; import { List } from "@raycast/api";
import { match } from "ts-pattern";
interface LinearIssueNotificationListItemProps { interface LinearIssueNotificationListItemProps {
notification: Notification; notification: Notification;
@@ -19,25 +19,12 @@ export function LinearIssueNotificationListItem({
linearIssueNotification, linearIssueNotification,
mutate, mutate,
}: LinearIssueNotificationListItemProps) { }: LinearIssueNotificationListItemProps) {
const projectSubtitle = match(linearIssueNotification.issue.project) const subtitle = `${linearIssueNotification.issue.team.name} #${linearIssueNotification.issue.identifier}`;
.with({ name: P.select(), icon: P.nullish }, (project_name) => `/ ${project_name}`)
.with(
{ name: P.select("project_name"), icon: P.select("icon") },
({ project_name, icon }) => `/ ${icon} ${project_name}`,
)
.otherwise(() => "");
const teamSubtitle = match(linearIssueNotification.issue.team)
.with({ name: P.select(), icon: P.nullish }, (team_name) => `${team_name}`)
.with({ name: P.select("team_name"), icon: P.select("icon") }, ({ team_name, icon }) => `${icon} ${team_name}`)
.otherwise(() => "");
const subtitle = `${teamSubtitle} ${projectSubtitle} #${linearIssueNotification.issue.identifier}`;
const state = getLinearIssueStateAccessory(linearIssueNotification.issue.state); const state = getLinearIssueStateAccessory(linearIssueNotification.issue.state);
const assignee = getLinearUserAccessory(linearIssueNotification.issue.assignee); const assignee = getLinearUserAccessory(linearIssueNotification.issue.assignee);
const reason = getLinearNotificationReasonAccessory(linearIssueNotification.type);
const accessories: List.Item.Accessory[] = [ const accessories: List.Item.Accessory[] = [
reason,
state, state,
assignee, assignee,
{ {

View File

@@ -1,12 +1,12 @@
import { getLinearNotificationReasonAccessory, getLinearUserAccessory } from "../accessories";
import { LinearProjectNotification, LinearProjectState, LinearProject } from "../types"; import { LinearProjectNotification, LinearProjectState, LinearProject } from "../types";
import { NotificationActions } from "../../../action/NotificationActions"; import { NotificationActions } from "../../../action/NotificationActions";
import { LinearProjectPreview } from "../preview/LinearProjectPreview"; import { LinearProjectPreview } from "../preview/LinearProjectPreview";
import { getLinearUserAccessory } from "../accessories";
import { Notification } from "../../../notification"; import { Notification } from "../../../notification";
import { MutatePromise } from "@raycast/utils"; import { MutatePromise } from "@raycast/utils";
import { List, Color } from "@raycast/api"; import { List, Color } from "@raycast/api";
import { Page } from "../../../types"; import { Page } from "../../../types";
import { match, P } from "ts-pattern"; import { match } from "ts-pattern";
interface LinearProjectNotificationListItemProps { interface LinearProjectNotificationListItemProps {
notification: Notification; notification: Notification;
@@ -19,20 +19,12 @@ export function LinearProjectNotificationListItem({
linearProjectNotification, linearProjectNotification,
mutate, mutate,
}: LinearProjectNotificationListItemProps) { }: LinearProjectNotificationListItemProps) {
const subtitle = match(linearProjectNotification.project) const subtitle = linearProjectNotification.project.name;
.with({ name: P.select(), icon: P.nullish }, (project_name) => `${project_name}`)
.with(
{ name: P.select("project_name"), icon: P.select("icon") },
({ project_name, icon }) => `${icon} ${project_name}`,
)
.otherwise(() => "");
const state = getLinearProjectStateAccessory(linearProjectNotification.project); const state = getLinearProjectStateAccessory(linearProjectNotification.project);
const lead = getLinearUserAccessory(linearProjectNotification.project.lead); const lead = getLinearUserAccessory(linearProjectNotification.project.lead);
const reason = getLinearNotificationReasonAccessory(linearProjectNotification.type);
const accessories: List.Item.Accessory[] = [ const accessories: List.Item.Accessory[] = [
reason,
state, state,
lead, lead,
{ {

View File

@@ -1,147 +0,0 @@
import { NotificationDetails, NotificationListItemProps } from "../../../notification";
import { NotificationActions } from "../../../action/NotificationActions";
import { SlackStarPreview } from "../preview/SlackStarPreview";
import { SlackBotInfo, SlackIcon, SlackUser } from "../types";
/* import { NotificationActions } from "../../../action/NotificationActions"; */
import { Icon, Image, List } from "@raycast/api";
import { getAvatarIcon } from "@raycast/utils";
import { match, P } from "ts-pattern";
export function SlackNotificationListItem({ notification, mutate }: NotificationListItemProps) {
const subtitle = getSlackNotificationSubtitle(notification.details);
const author = getSlackAuthorAccessory(notification.details);
const team = getSlackTeamAccessory(notification.details);
const updated_at = "2023-01-01"; // TODO
const accessories: List.Item.Accessory[] = [{ date: new Date(updated_at), tooltip: `Updated at ${updated_at}` }];
if (author) {
accessories.unshift(author);
}
if (team) {
accessories.unshift(team);
}
return (
<List.Item
key={notification.id}
title={notification.title}
icon={{ source: { light: "slack-logo-dark.svg", dark: "slack-logo-light.svg" } }}
subtitle={subtitle}
accessories={accessories}
actions={
<NotificationActions
notification={notification}
detailsTarget={<SlackStarPreview notification={notification} />}
mutate={mutate}
/>
}
/>
);
}
function getSlackNotificationSubtitle(notificationDetails?: NotificationDetails): string {
return match(notificationDetails)
.with(
{
type: P.union("SlackMessage", "SlackFile", "SlackFileComment", "SlackChannel", "SlackIm", "SlackGroup"),
content: P.select(),
},
(slackNotificationDetails) => {
const channelName = slackNotificationDetails.channel?.name;
return channelName ? `#${channelName}` : "";
},
)
.otherwise(() => "");
}
function getSlackAuthorAccessory(notificationDetails?: NotificationDetails): List.Item.Accessory | null {
return match(notificationDetails)
.with(
{
type: "SlackMessage",
content: P.select(),
},
(slackNotificationDetails) => {
return match(slackNotificationDetails.sender)
.with({ type: "User", content: P.select() }, (slackUser: SlackUser) => {
const userAvatarUrl = getSlackUserAvatarUrl(slackUser);
const userName = slackUser.real_name || "Unknown";
return {
icon: userAvatarUrl ? { source: userAvatarUrl, mask: Image.Mask.Circle } : getAvatarIcon(userName),
tooltip: userName,
};
})
.with({ type: "Bot", content: P.select() }, (slackBot: SlackBotInfo) => {
const botAvatarUrl = getSlackIconUrl(slackBot.icons);
return {
icon: botAvatarUrl ? { source: botAvatarUrl, mask: Image.Mask.Circle } : getAvatarIcon(slackBot.name),
tooltip: slackBot.name,
};
})
.otherwise(() => ({ icon: Icon.Person, tooltip: "Unknown" }));
},
)
.otherwise(() => null);
}
function getSlackTeamAccessory(notificationDetails?: NotificationDetails): List.Item.Accessory | null {
return match(notificationDetails)
.with(
{
type: P.union("SlackMessage", "SlackFile", "SlackFileComment", "SlackChannel", "SlackIm", "SlackGroup"),
content: P.select(),
},
(slackNotificationDetails) => {
const teamName = slackNotificationDetails.team.name;
const teamIconUrl = getSlackIconUrl(slackNotificationDetails.team.icon);
if (!teamName || !teamIconUrl) {
return null;
}
return { icon: { source: teamIconUrl, mask: Image.Mask.Circle }, tooltip: teamName };
},
)
.otherwise(() => null);
}
function getSlackUserAvatarUrl(slackUser: SlackUser): string | null {
if (!slackUser.profile) {
return null;
}
if (slackUser.profile.image_24) {
return slackUser.profile.image_24;
}
if (slackUser.profile.image_32) {
return slackUser.profile.image_32;
}
if (slackUser.profile.image_34) {
return slackUser.profile.image_34;
}
if (slackUser.profile.image_44) {
return slackUser.profile.image_44;
}
if (slackUser.profile.image_48) {
return slackUser.profile.image_48;
}
return null;
}
function getSlackIconUrl(slackIcon?: SlackIcon): string | null {
if (slackIcon?.image_24) {
return slackIcon.image_24;
}
if (slackIcon?.image_32) {
return slackIcon.image_32;
}
if (slackIcon?.image_34) {
return slackIcon.image_34;
}
if (slackIcon?.image_44) {
return slackIcon.image_44;
}
if (slackIcon?.image_48) {
return slackIcon.image_48;
}
return null;
}

View File

@@ -1,24 +0,0 @@
import { Notification, getNotificationHtmlUrl } from "../../../notification";
import { Detail, ActionPanel, Action } from "@raycast/api";
import { useMemo } from "react";
interface SlackStarPreviewProps {
notification: Notification;
}
export function SlackStarPreview({ notification }: SlackStarPreviewProps) {
const notificationHtmlUrl = useMemo(() => {
return getNotificationHtmlUrl(notification);
}, [notification]);
return (
<Detail
markdown={`# ${notification.title}`}
actions={
<ActionPanel>
<Action.OpenInBrowser url={notificationHtmlUrl} />
</ActionPanel>
}
/>
);
}

View File

@@ -1,400 +0,0 @@
export interface SlackPushEventCallback {
team_id: string;
api_app_id: string;
event: SlackEventCallbackBody;
event_id: string;
event_time: Date;
event_context?: string;
authed_users?: Array<string>;
authorizations?: Array<SlackEventAuthorization>;
}
export interface SlackEventAuthorization {
team_id: string;
user_id: string;
is_bot: boolean;
}
export type SlackEventCallbackBody =
| { type: "SarAdded"; content: SlackStarAddedEvent }
| { type: "StarRemoved"; content: SlackStarRemovedEvent };
export interface SlackStarAddedEvent {
user: string;
item: SlackStarsItem;
event_ts: Date;
}
export interface SlackStarRemovedEvent {
user: string;
item: SlackStarsItem;
event_ts: Date;
}
export type SlackStarsItem =
| { type: "Message"; content: SlackStarsItemMessage }
| { type: "File"; content: SlackStarsItemFile }
| { type: "FileComment"; content: SlackStarsItemFileComment }
| { type: "Channel"; content: SlackStarsItemChannel }
| { type: "Im"; content: SlackStarsItemIm }
| { type: "Group"; content: SlackStarsItemGroup };
export interface SlackStarsItemMessage {
message: SlackHistoryMessage;
channel: string;
date_create: Date;
}
export interface SlackHistoryMessage {
ts: string;
channel?: string;
channel_type?: string;
thread_ts?: string;
client_msg_id?: string;
text?: string;
blocks?: Array<SlackBlock>;
attachments?: Array<SlackMessageAttachment>;
upload?: boolean;
files?: Array<SlackFile>;
reactions?: Array<SlackReaction>;
}
export interface SlackReaction {
name: string;
count: number;
users: Array<string>;
}
export interface SlackMessageAttachment {
id?: number;
color?: string;
fallback?: string;
title?: string;
fields?: Array<SlackMessageAttachmentFieldObject>;
mrkdwn_in?: Array<string>;
text?: string;
}
export interface SlackMessageAttachmentFieldObject {
title?: string;
value?: string;
short?: boolean;
}
export type SlackBlock =
| SlackSectionBlock
| SlackHeaderBlock
| SlackDividerBlock
| SlackImageBlock
| SlackActionsBlock
| SlackContextBlock
| SlackInputBlock
| SlackFileBlock
| { type: "rich_text" }
| { type: "event" };
export interface SlackSectionBlock {
type: "section";
block_id?: string;
text?: SlackBlockText;
fields?: Array<SlackBlockText>;
// To be specified
// eslint-disable-next-line @typescript-eslint/no-explicit-any
accessory?: any;
}
export type SlackBlockText =
| { type: "plain_text"; value: string }
| { type: "mrkdwn"; text: string; verbatim?: boolean };
export interface SlackHeaderBlock {
type: "header";
block_id?: string;
text: SlackBlockText;
}
export interface SlackDividerBlock {
type: "divider";
block_id?: string;
}
export interface SlackImageBlock {
type: "image";
block_id?: string;
image_url: string;
alt_text: string;
title?: SlackBlockText;
}
export interface SlackActionsBlock {
type: "actions";
block_id?: string;
// To be specified
// eslint-disable-next-line @typescript-eslint/no-explicit-any
elements: Array<any>;
}
export interface SlackContextBlock {
type: "context";
block_id?: string;
// To be specified
// eslint-disable-next-line @typescript-eslint/no-explicit-any
elements: Array<any>;
}
export interface SlackInputBlock {
type: "input";
block_id?: string;
label: SlackBlockText;
// To be specified
// eslint-disable-next-line @typescript-eslint/no-explicit-any
element: any;
hint?: SlackBlockText;
optional?: boolean;
dispatch_action?: boolean;
}
export interface SlackFileBlock {
type: "file";
block_id?: string;
external_id: string;
source: string;
}
export interface SlackStarsItemFile {
file: SlackFile;
channel: string;
date_create: Date;
}
export interface SlackFile {
id: string;
created?: Date;
timestamp?: Date;
name?: string;
title?: string;
mimetype?: string;
filetype?: string;
pretty_type?: string;
external_type?: string;
user?: string;
username?: string;
url_private?: string;
url_private_download?: string;
permalink?: string;
permalink_public?: string;
reactions?: Array<SlackReaction>;
editable?: boolean;
is_external?: boolean;
is_public?: boolean;
public_url_shared?: boolean;
display_as_bot?: boolean;
is_starred?: boolean;
has_rich_preview?: boolean;
}
export interface SlackStarsItemFileComment {
file: SlackFile;
file_comment: string;
channel: string;
date_create: Date;
}
export interface SlackStarsItemChannel {
channel: string;
date_create: Date;
}
export interface SlackStarsItemIm {
channel: string;
date_create: Date;
}
export interface SlackStarsItemGroup {
group: string;
date_create: Date;
}
export interface SlackMessageDetails {
url: string;
message: SlackHistoryMessage;
channel: SlackChannelInfo;
sender: SlackMessageSenderDetails;
team: SlackTeamInfo;
}
export type SlackMessageSenderDetails = { type: "User"; content: SlackUser } | { type: "Bot"; content: SlackBotInfo };
export interface SlackUser {
id: string;
team_id?: string;
name?: string;
locale?: string;
profile?: SlackUserProfile;
is_admin?: boolean;
is_app_user?: boolean;
is_bot?: boolean;
is_invited_user?: boolean;
is_owner?: boolean;
is_primary_owner?: boolean;
is_restricted?: boolean;
is_stranger?: boolean;
is_ultra_restricted?: boolean;
has_2fa?: boolean;
tz?: string;
tz_label?: string;
tz_offset?: number;
updated?: Date;
deleted?: boolean;
color?: string;
real_name?: string;
enterprise_user?: SlackEnterpriseUser;
}
export interface SlackUserProfile {
id?: string;
display_name?: string;
real_name?: string;
real_name_normalized?: string;
avatar_hash?: string;
status_text?: string;
status_expiration?: Date;
status_emoji?: string;
display_name_normalized?: string;
email?: string;
team?: string;
image_original?: string;
image_default?: boolean;
image_24?: string;
image_32?: string;
image_34?: string;
image_44?: string;
image_48?: string;
image_68?: string;
image_72?: string;
image_88?: string;
image_102?: string;
image_132?: string;
image_192?: string;
image_230?: string;
image_512?: string;
}
export interface SlackEnterpriseUser {
id: string;
enterprise_id: string;
enterprise_name?: string;
teams?: Array<string>;
is_admin?: boolean;
is_app_user?: boolean;
is_bot?: boolean;
is_invited_user?: boolean;
is_owner?: boolean;
is_primary_owner?: boolean;
is_restricted?: boolean;
is_stranger?: boolean;
is_ultra_restricted?: boolean;
has_2fa?: boolean;
}
export interface SlackBotInfo {
id?: string;
name: string;
updated?: Date;
app_id: string;
user_id: string;
icons?: SlackIcon;
}
export interface SlackTeamInfo {
id: string;
name?: string;
domain?: string;
email_domain?: string;
icon?: SlackIcon;
}
export interface SlackIcon {
image_original?: string;
image_default?: boolean;
image_24?: string;
image_32?: string;
image_34?: string;
image_44?: string;
image_48?: string;
image_68?: string;
image_72?: string;
image_88?: string;
image_102?: string;
image_132?: string;
image_192?: string;
image_230?: string;
image_512?: string;
}
export interface SlackChannelInfo {
id: string;
created: Date;
creator?: string;
name?: string;
name_normalized?: string;
topic?: SlackChannelTopicInfo;
purpose?: SlackChannelPurposeInfo;
previous_names?: Array<string>;
priority?: number;
num_members?: number;
locale?: string;
is_channel?: boolean;
is_group?: boolean;
is_im?: boolean;
is_archived?: boolean;
is_general?: boolean;
is_shared?: boolean;
is_org_shared?: boolean;
is_member?: boolean;
is_private?: boolean;
is_mpim?: boolean;
is_user_deleted?: boolean;
last_read?: string;
unread_count?: number;
unread_count_display?: number;
}
export interface SlackChannelTopicInfo {
value: string;
creator?: string;
last_set?: Date;
}
export interface SlackChannelPurposeInfo {
value: string;
creator?: string;
last_set?: Date;
}
export interface SlackChannelDetails {
channel: SlackChannelInfo;
team: SlackTeamInfo;
}
export interface SlackFileDetails {
channel: SlackChannelInfo;
sender?: SlackUser;
team: SlackTeamInfo;
}
export interface SlackFileCommentDetails {
channel: SlackChannelInfo;
team: SlackTeamInfo;
}
export interface SlackImDetails {
channel: SlackChannelInfo;
team: SlackTeamInfo;
}
export interface SlackGroupDetails {
channel: SlackChannelInfo;
team: SlackTeamInfo;
}

View File

@@ -1,12 +1,3 @@
import {
SlackChannelDetails,
SlackFileCommentDetails,
SlackFileDetails,
SlackGroupDetails,
SlackImDetails,
SlackMessageDetails,
SlackPushEventCallback,
} from "./integrations/slack/types";
import { GithubDiscussion, GithubPullRequest } from "./integrations/github/types"; import { GithubDiscussion, GithubPullRequest } from "./integrations/github/types";
import { GoogleMailThread } from "./integrations/google-mail/types"; import { GoogleMailThread } from "./integrations/google-mail/types";
import { LinearNotification } from "./integrations/linear/types"; import { LinearNotification } from "./integrations/linear/types";
@@ -36,18 +27,11 @@ export type NotificationMetadata =
content: any; content: any;
} }
| { type: "Linear"; content: LinearNotification } | { type: "Linear"; content: LinearNotification }
| { type: "GoogleMail"; content: GoogleMailThread } | { type: "GoogleMail"; content: GoogleMailThread };
| { type: "Slack"; content: SlackPushEventCallback };
export type NotificationDetails = export type NotificationDetails =
| { type: "GithubPullRequest"; content: GithubPullRequest } | { type: "GithubPullRequest"; content: GithubPullRequest }
| { type: "GithubDiscussion"; content: GithubDiscussion } | { type: "GithubDiscussion"; content: GithubDiscussion };
| { type: "SlackMessage"; content: SlackMessageDetails }
| { type: "SlackFile"; content: SlackFileDetails }
| { type: "SlackFileComment"; content: SlackFileCommentDetails }
| { type: "SlackChannel"; content: SlackChannelDetails }
| { type: "SlackIm"; content: SlackImDetails }
| { type: "SlackGroup"; content: SlackGroupDetails };
export enum NotificationStatus { export enum NotificationStatus {
Unread = "Unread", Unread = "Unread",
@@ -63,17 +47,6 @@ export type NotificationListItemProps = {
export function getNotificationHtmlUrl(notification: Notification) { export function getNotificationHtmlUrl(notification: Notification) {
return match(notification) return match(notification)
.with({ details: { type: "SlackMessage", content: P.select() } }, (notificationDetails) => notificationDetails.url)
.with(
{
details: {
type: P.union("SlackChannel", "SlackFile", "SlackFileComment", "SlackGroup", "SlackIm"),
content: P.select(),
},
},
(notificationDetails) =>
`https://app.slack.com/client/${notificationDetails.team.id}/${notificationDetails.channel.id}`,
)
.with( .with(
{ details: { type: P.union("GithubPullRequest", "GithubDiscussion"), content: P.select() } }, { details: { type: P.union("GithubPullRequest", "GithubDiscussion"), content: P.select() } },
(notificationDetails) => notificationDetails.url, (notificationDetails) => notificationDetails.url,
@@ -93,7 +66,6 @@ export function getNotificationHtmlUrl(notification: Notification) {
) )
.with({ metadata: { type: "Todoist" } }, () => `https://todoist.com/showTask?id=${notification.source_id}`) .with({ metadata: { type: "Todoist" } }, () => `https://todoist.com/showTask?id=${notification.source_id}`)
.with({ metadata: { type: "Github" } }, () => "https://github.com") .with({ metadata: { type: "Github" } }, () => "https://github.com")
.with({ metadata: { type: "Slack" } }, () => "https://app.slack.com")
.exhaustive(); .exhaustive();
} }