|
const LINEAR_API_KEY = "YOUR_API_KEY_HERE"; |
|
const WIDGET_SIZE = "medium"; // "small" | "medium" | "large" |
|
|
|
let widget = await createWidget(); |
|
|
|
if (config.runsInWidget) { |
|
Script.setWidget(widget); |
|
} else { |
|
switch (WIDGET_SIZE) { |
|
case "large": { |
|
widget.presentLarge(); |
|
break; |
|
} |
|
case "small": { |
|
widget.presentSmall(); |
|
break; |
|
} |
|
default: { |
|
widget.presentMedium(); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
Script.complete(); |
|
|
|
async function createWidget() { |
|
let w = new ListWidget(); |
|
|
|
let backgroundGradient = new LinearGradient(); |
|
backgroundGradient.colors = [new Color("#29349A"), new Color("#5F69CA")]; |
|
backgroundGradient.locations = [0, 1]; |
|
backgroundGradient.startPoint = new Point(0, 1); |
|
backgroundGradient.endPoint = new Point(1, 0); |
|
|
|
w.backgroundGradient = backgroundGradient; |
|
|
|
let main = w.addStack(); |
|
main.layoutVertically(); |
|
main.topAlignContent(); |
|
|
|
let headerStack = main.addStack(); |
|
headerStack.centerAlignContent(); |
|
|
|
let headingLeft = headerStack.addText("Linear inbox"); |
|
headingLeft.leftAlignText(); |
|
headingLeft.font = Font.boldSystemFont(18); |
|
headingLeft.textColor = new Color("#ffffff"); |
|
headingLeft.url = 'https://linear.app' |
|
|
|
if (WIDGET_SIZE !== "small") { |
|
headerStack.addSpacer(); |
|
|
|
let headingRight = headerStack.addText(`Last ${getNotificationListSize()} notifications`); |
|
headingRight.leftAlignText(); |
|
headingRight.font = Font.regularSystemFont(14); |
|
headingRight.textColor = new Color("#cccccc"); |
|
} |
|
|
|
main.addSpacer(7); |
|
|
|
const data = await getNotifications(); |
|
|
|
if (WIDGET_SIZE === "small") { |
|
const unreadNotifications = data.filter(({ isRead }) => !isRead).length; |
|
|
|
let unreadTitle = main.addText(String(unreadNotifications)); |
|
unreadTitle.leftAlignText(); |
|
unreadTitle.font = Font.boldSystemFont(45); |
|
unreadTitle.textColor = new Color("#ffffff"); |
|
unreadTitle.url = 'https://linear.app/inbox' |
|
|
|
let unreadSubtitle = main.addText("unread notifications"); |
|
unreadSubtitle.leftAlignText(); |
|
unreadSubtitle.font = Font.regularSystemFont(14); |
|
unreadSubtitle.textColor = new Color("#cccccc"); |
|
|
|
} else { |
|
let listStack = main.addStack(); |
|
listStack.layoutVertically(); |
|
listStack.spacing = 2; |
|
|
|
data.forEach((notification, index) => { |
|
let notificationStack = listStack.addStack(); |
|
notificationStack.layoutVertically(); |
|
notificationStack.spacing = 0; |
|
notificationStack.url = notification.url |
|
|
|
let titleStack = notificationStack.addStack(); |
|
titleStack.layoutHorizontally(); |
|
|
|
if (!notification.isRead) { |
|
let bullet = titleStack.addText("• "); |
|
bullet.leftAlignText(); |
|
bullet.font = Font.semiboldSystemFont(14); |
|
bullet.textColor = new Color("#ffffff"); |
|
bullet.lineLimit = 1; |
|
} |
|
|
|
let title = titleStack.addText(notification.title); |
|
title.leftAlignText(); |
|
title.font = Font.semiboldSystemFont(14); |
|
title.textColor = new Color(notification.isRead ? "#aaaaaa" : "#ffffff"); |
|
title.lineLimit = 1; |
|
|
|
let subtitleStack = notificationStack.addStack(); |
|
subtitleStack.spacing = 0; |
|
|
|
let idStack = subtitleStack.addStack(); |
|
idStack.size = new Size(80, 18); |
|
|
|
let id = idStack.addText(notification.identifier); |
|
id.leftAlignText(); |
|
id.font = Font.regularSystemFont(12); |
|
id.textColor = new Color(notification.isRead ? "#999999" : "#cccccc"); |
|
|
|
idStack.addSpacer(); |
|
|
|
let event = subtitleStack.addText(notification.event); |
|
event.leftAlignText(); |
|
event.font = Font.lightSystemFont(12); |
|
event.textColor = new Color(notification.isRead ? "#aaaaaa" : "#ffffff"); |
|
event.lineLimit = 1; |
|
}); |
|
|
|
} |
|
|
|
return w; |
|
} |
|
|
|
async function getNotifications() { |
|
const request = new Request("https://api.linear.app/graphql"); |
|
request.method = "POST"; |
|
|
|
request.headers = { |
|
"Content-Type": "application/json", |
|
Authorization: `Bearer ${LINEAR_API_KEY}`, |
|
}; |
|
|
|
request.body = JSON.stringify({ |
|
query: `query { |
|
notifications(orderBy: updatedAt) { |
|
nodes { |
|
... on IssueNotification { |
|
type |
|
actor { displayName } |
|
issue { |
|
title |
|
state { name } |
|
identifier |
|
} |
|
team { |
|
organization { |
|
urlKey |
|
} |
|
} |
|
readAt |
|
} |
|
} |
|
} |
|
}`, |
|
variables: {}, |
|
}); |
|
|
|
const response = await request.loadJSON(); |
|
|
|
const formatedNotifications = response.data.notifications.nodes |
|
.reduce((acc, notification, index) => { |
|
const identifier = notification.issue.identifier; |
|
const initialIndex = response.data.notifications.nodes.findIndex( |
|
(n) => n.issue.identifier === identifier |
|
); |
|
const shouldKeep = index === initialIndex; |
|
if (shouldKeep) { |
|
acc.push({ |
|
title: notification.issue.title, |
|
identifier: notification.issue.identifier, |
|
event: getEventLabel(notification.type, { |
|
actor: notification.actor |
|
? notification.actor.displayName |
|
: "Linear", |
|
status: notification.issue.state.name, |
|
}), |
|
url: `https://linear.app/${notification.team.organization.urlKey}/issue/${notification.issue.identifier}`, |
|
isRead: notification.readAt !== null, |
|
}); |
|
} |
|
return acc; |
|
}, []) |
|
|
|
const notificationListSize = getNotificationListSize() |
|
|
|
if (notificationListSize) { |
|
return formatedNotifications.slice(0, getNotificationListSize()); |
|
} else { |
|
return formatedNotifications; |
|
} |
|
} |
|
|
|
function getEventLabel(event, { actor, status }) { |
|
switch (event) { |
|
case "issueCommentMention": { |
|
return `Mentionned in a comment by ${actor}`; |
|
} |
|
case "issueNewComment": { |
|
return `New comment from ${actor}`; |
|
} |
|
case "issueStatusChanged": { |
|
return `Status changed to ${status}`; |
|
} |
|
case "issueDue": { |
|
return `Overdue`; |
|
} |
|
default: { |
|
return `"${event}" by ${actor}`; |
|
} |
|
} |
|
} |
|
|
|
function getNotificationListSize() { |
|
switch (WIDGET_SIZE) { |
|
case "large": { |
|
return 7; |
|
} |
|
case "small": { |
|
return false; |
|
} |
|
default: { |
|
return 3; |
|
} |
|
} |
|
} |
Comment to upload the images