-
-
Save Critter/e60537ad7441fa2690455a7ab4121aa0 to your computer and use it in GitHub Desktop.
Wikipedia Top Read Widget
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Variables used by Scriptable. | |
// These must be at the very top of the file. Do not edit. | |
// icon-color: brown; icon-glyph: bookmark; | |
// Useful links | |
// https://en.wikipedia.org/api/rest_v1/#/Feed/aggregatedFeed | |
// https://design.wikimedia.org/blog/2021/04/26/bringing-wikipedia-to-the-homescreen-on-ios.html | |
// https://design.wikimedia.org/blog/assets/uploads/wikipedia-widget/wikipedia-top-read-widget.png | |
const PREFS = { | |
background: Color.dynamic(new Color("#FFF"), new Color("#000")), | |
foregroundPrimary: Color.dynamic(new Color("#00"), new Color("#FF")), | |
foregroundSecondary: Color.dynamic(new Color("#8A898D"), new Color("#8E8D93")), | |
statsBackground: Color.dynamic(new Color("#F8F7F9"), new Color("#1F1F1F")), | |
statsLines: Color.dynamic(new Color("#EBEBEB"), new Color("#373737")), | |
statsForeground: Color.dynamic(new Color("#4DAA8C"), new Color("#4DAA8C")), | |
// Not actually used at the moment | |
font: { | |
name: "San Francisco", | |
size: 16, | |
}, | |
rankColors: [ | |
new Color("#3F64C7"), | |
new Color("#3E75B5"), | |
new Color("#4188A7"), | |
new Color("#479B99") | |
], | |
debugMode: false | |
} | |
// Preview widget | |
if (PREFS.debugMode) { | |
let widget = await createWidget(); | |
await widget.presentLarge(); | |
} | |
// Running in widget | |
else if (config.runsInWidget) { | |
let widget = await createWidget() | |
Script.setWidget(widget); | |
} | |
// Not running in widget/being run directly | |
else { | |
let app = await createApp(); | |
await QuickLook.present(app); | |
} | |
Script.complete(); | |
async function createApp() { | |
let topRead = await getTopRead(4); | |
const table = new UITable() | |
for (let index = 0; index < topRead.length; index++) { | |
let article = await topRead[index]; | |
let row = new UITableRow(); | |
let textCell = row.addText(article.normalizedtitle + "\n" + article.description); | |
let imageCell = row.addImageAtURL(article.thumbnail.source); | |
textCell.widthWeight = 100; | |
imageCell.widthWeight = 10; | |
row.height = 60; | |
row.cellSpacing = 10; | |
row.onSelect = () => { | |
Safari.open(article.content_urls.desktop.page); | |
} | |
row.dismissOnSelect = false; | |
table.addRow(row); | |
} | |
return table | |
} | |
async function createWidget() { | |
let widget = new ListWidget() | |
// Light color first, dark color second | |
widget.backgroundColor = PREFS.background; | |
widget.setPadding(15, 15, 0, 15); | |
// (top, leading, bottom, trailing) | |
const line = widget.addText("Top read"); | |
line.font = Font.boldSystemFont(18); | |
line.textColor = PREFS.foregroundPrimary; | |
// widget.addSpacer(); | |
const listStack = widget.addStack(); | |
listStack.layoutVertically(); | |
listStack.setPadding(7.5, 0, 0, 0); | |
let topRead = await getTopRead(4); | |
for (let index = 0; index < topRead.length; index++) { | |
let article = topRead[index]; | |
await listItem( | |
listStack, | |
index + 1, | |
article.normalizedtitle, | |
article.description, | |
article.views, | |
await loadThumbnail(article.thumbnail.source), | |
); | |
}; | |
// await listItem(listStack, 1, "Octavia E. Butler", "American science fiction writer", 10000, "https://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/Mexico_City_New_Years_2013%21_%288333128248%29.jpg/320px-Mexico_City_New_Years_2013%21_%288333128248%29.jpg"); | |
// await listItem(listStack, 2, "Octavia E. Butler"); | |
// await listItem(listStack, 3, "Octavia E. Butler"); | |
// await listItem(listStack, 4, "Octavia E. Butler"); | |
widget.addSpacer(); | |
return widget; | |
} | |
async function listItem(listStack, rank, title, description = "Some description", views = 0, thumbnail = SFSymbol.named("xmark.square.fill").image) { | |
const itemStack = listStack.addStack(); | |
itemStack.layoutHorizontally(); | |
itemStack.centerAlignContent(); | |
const rankSymbol = SFSymbol.named(rank + ".circle"); | |
rankSymbol.applyThinWeight(); | |
const rankImage = rankSymbol.image; | |
const rankWidgetImage = itemStack.addImage(rankImage); | |
rankWidgetImage.tintColor = PREFS.rankColors[rank - 1]; | |
rankWidgetImage.imageSize = new Size(28, 28); | |
const infoStack = itemStack.addStack(); | |
infoStack.layoutVertically(); | |
infoStack.setPadding(4, 15, 4, 0); | |
infoStack.spacing = 4; | |
const itemTitle = infoStack.addText(title); | |
itemTitle.font = Font.semiboldSystemFont(16); | |
itemTitle.textColor = PREFS.foregroundPrimary; | |
const itemDescription = infoStack.addText(description); | |
itemDescription.font = Font.regularSystemFont(14); | |
itemDescription.textColor = PREFS.foregroundSecondary; | |
//infoStack.addSpacer(0); | |
const statsStack = infoStack.addStack(); | |
statsStack.backgroundColor = PREFS.statsBackground; | |
statsStack.cornerRadius = 4; | |
statsStack.size = new Size(0, 18); | |
statsStack.centerAlignContent(); | |
statsStack.setPadding(4, 4, 4, 4); | |
// Graph would go here... | |
//statsStack.addSpacer(32); | |
const itemViews = statsStack.addText(formatNumber(views) + ""); | |
itemViews.font = Font.regularSystemFont(12); | |
itemViews.textColor = PREFS.statsForeground; | |
itemStack.addSpacer(); | |
const thumbnailImage = itemStack.addImage(thumbnail); | |
thumbnailImage.cornerRadius = 8; | |
thumbnailImage.applyFillingContentMode(); | |
thumbnailImage.imageSize = new Size(60, 60); | |
} | |
async function loadThumbnail(url) { | |
let request = new Request(url); | |
return request.loadImage(); | |
} | |
async function getTopRead(maximum) { | |
const apiUrl = 'https://en.wikipedia.org/api/rest_v1/feed/featured/'; | |
// Get current UTC date | |
const currentDate = new Date().toISOString().split('T')[0].replace(/-/g, '/'); | |
// Append the current date to the API URL | |
const fullApiUrl = `${apiUrl}${currentDate}`; | |
const request = new Request(fullApiUrl); | |
const response = await request.loadJSON(fullApiUrl) | |
const articles = response.mostread.articles; | |
// Sort articles by views in descending order | |
articles.sort((a, b) => b.views - a.views); | |
// Get the top articles up until maximum | |
const topArticles = articles.slice(0, maximum); | |
return topArticles; | |
} | |
function formatNumber(n) { | |
const ranges = [ | |
{ divider: 1e6 , suffix: 'M' }, | |
{ divider: 1e3 , suffix: 'k' } | |
]; | |
for (let i = 0; i < ranges.length; i++) { | |
if (n >= ranges[i].divider) { | |
n = (n / ranges[i].divider); | |
n = Math.round(n * 10) / 10; | |
return n.toString() + ranges[i].suffix; | |
} | |
} | |
return n.toString(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment