cedarstalking with keyboard shortcuts
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: format

+169 -69
+4 -1
package.json
··· 9 9 "platforms": [ 10 10 "macOS" 11 11 ], 12 - "categories": ["Productivity", "Web"], 12 + "categories": [ 13 + "Productivity", 14 + "Web" 15 + ], 13 16 "commands": [ 14 17 { 15 18 "name": "search-directory",
+6 -7
raycast-env.d.ts
··· 7 7 8 8 /* eslint-disable @typescript-eslint/ban-types */ 9 9 10 - type ExtensionPreferences = {} 10 + type ExtensionPreferences = {}; 11 11 12 12 /** Preferences accessible in all the extension's commands */ 13 - declare type Preferences = ExtensionPreferences 13 + declare type Preferences = ExtensionPreferences; 14 14 15 15 declare namespace Preferences { 16 16 /** Preferences accessible in the `search-directory` command */ 17 17 export type SearchDirectory = ExtensionPreferences & { 18 - /** Demo Mode - Redact personal information for screenshots and demos */ 19 - "demoMode": boolean 20 - } 18 + /** Demo Mode - Redact personal information for screenshots and demos */ 19 + demoMode: boolean; 20 + }; 21 21 } 22 22 23 23 declare namespace Arguments { 24 24 /** Arguments passed to the `search-directory` command */ 25 - export type SearchDirectory = {} 25 + export type SearchDirectory = {}; 26 26 } 27 -
+6 -2
src/api.ts
··· 76 76 majors: { code: string; desc: string }[]; 77 77 minors: { code: string; desc: string }[]; 78 78 concentrations: { code: string; desc: string }[]; 79 - advisors: { advisor: { id: string; name: string; email: string }; advisement: { type: string } }[]; 79 + advisors: { 80 + advisor: { id: string; name: string; email: string }; 81 + advisement: { type: string }; 82 + }[]; 80 83 term: { key: string | null; description: string | null }; 81 84 nonScheduledCourses: { code: string; title: string; methods: string }[]; 82 85 }; ··· 144 147 ): Promise<PersonInfo | null> { 145 148 const url = `${BASE_URL}/CedarInfo/Info/Json?id=${id}&term=${term}`; 146 149 const res = await fetch(url, { headers: makeHeaders(cookie) }); 147 - if (!res.ok || !res.headers.get("content-type")?.includes("json")) return null; 150 + if (!res.ok || !res.headers.get("content-type")?.includes("json")) 151 + return null; 148 152 try { 149 153 const data = await res.json(); 150 154 return data as PersonInfo;
+19 -6
src/auth.ts
··· 61 61 proc.on("error", reject); 62 62 }); 63 63 64 - const cookie = await readFile(COOKIE_FILE, "utf-8").then((s) => s.trim()).catch(() => ""); 64 + const cookie = await readFile(COOKIE_FILE, "utf-8") 65 + .then((s) => s.trim()) 66 + .catch(() => ""); 65 67 await unlink(COOKIE_FILE).catch(() => {}); 66 68 67 69 if (!cookie) throw new Error("Sign-in cancelled."); ··· 75 77 const binaryPath = path.join(environment.supportPath, "auth-browser"); 76 78 const swiftSrc = path.join(environment.assetsPath, "auth-browser.swift"); 77 79 try { 78 - const [binStat, srcStat] = await Promise.all([stat(binaryPath), stat(swiftSrc)]); 80 + const [binStat, srcStat] = await Promise.all([ 81 + stat(binaryPath), 82 + stat(swiftSrc), 83 + ]); 79 84 if (binStat.mtimeMs >= srcStat.mtimeMs) return binaryPath; 80 85 // source is newer — fall through to recompile 81 86 } catch { 82 87 // binary doesn't exist yet — fall through to compile 83 88 } 84 89 85 - const hasSwiftc = await execAsync("xcrun --find swiftc").then(() => true).catch(() => false); 90 + const hasSwiftc = await execAsync("xcrun --find swiftc") 91 + .then(() => true) 92 + .catch(() => false); 86 93 if (!hasSwiftc) { 87 94 await showToast({ 88 95 style: Toast.Style.Failure, ··· 94 101 95 102 await mkdir(environment.supportPath, { recursive: true }); 96 103 97 - const toast = await showToast({ style: Toast.Style.Animated, title: "Compiling sign-in helper…" }); 104 + const toast = await showToast({ 105 + style: Toast.Style.Animated, 106 + title: "Compiling sign-in helper…", 107 + }); 98 108 try { 99 109 // Use `xcrun swiftc` (not the raw path) so xcrun sets up DEVELOPER_DIR and 100 110 // the correct SDK — running the swiftc binary directly loses that context. ··· 117 127 await mkdir(macosDir, { recursive: true }); 118 128 await symlink(binaryPath, bundledBinary); 119 129 120 - await writeFile(plistPath, `<?xml version="1.0" encoding="UTF-8"?> 130 + await writeFile( 131 + plistPath, 132 + `<?xml version="1.0" encoding="UTF-8"?> 121 133 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 122 134 <plist version="1.0"> 123 135 <dict> ··· 129 141 \t<key>NSHighResolutionCapable</key><true/> 130 142 \t<key>LSMinimumSystemVersion</key><string>13.0</string> 131 143 </dict> 132 - </plist>`); 144 + </plist>`, 145 + ); 133 146 134 147 return appDir; 135 148 }
+134 -53
src/search-directory.tsx
··· 35 35 import { getCacheSize, mergePeopleIntoCache, searchCache } from "./cache"; 36 36 import { getCachedPhotoPath } from "./images"; 37 37 38 - 39 38 type AuthState = 40 39 | { kind: "loading" } 41 40 | { kind: "ready"; cookie: string } ··· 88 87 const digits = phone.replace(/\D/g, ""); 89 88 if (digits.length === 4) return `ext. ${digits}`; 90 89 if (digits.length === 7) return `${digits.slice(0, 3)}-${digits.slice(3)}`; 91 - if (digits.length === 10) return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`; 90 + if (digits.length === 10) 91 + return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`; 92 92 if (digits.length === 11 && digits[0] === "1") 93 93 return `+1 (${digits.slice(1, 4)}) ${digits.slice(4, 7)}-${digits.slice(7)}`; 94 94 return phone.trim(); ··· 97 97 const FACULTY_TITLE_KEYWORDS = /professor|instructor|lecturer|faculty/i; 98 98 99 99 function isFacultyHeuristic(person: DirectoryPerson): boolean { 100 - if (person.isFaculty !== undefined) return person.isFaculty; 101 - return !!person.Title && FACULTY_TITLE_KEYWORDS.test(person.Title); 100 + if (person.isFaculty !== undefined) return person.isFaculty; 101 + return !!person.Title && FACULTY_TITLE_KEYWORDS.test(person.Title); 102 102 } 103 103 104 104 const DEMO_NAMES_STUDENT = ["Alex Johnson", "Jordan Smith", "Taylor Williams"]; 105 - const DEMO_NAMES_STAFF = ["Dr. Chris Brown", "Pat Miller", "Sam Davis"]; 105 + const DEMO_NAMES_STAFF = ["Dr. Chris Brown", "Pat Miller", "Sam Davis"]; 106 106 107 107 function demoName(person: DirectoryPerson): string { 108 - const isStaffPerson = !person.StudentType || !!(person.Title?.trim() && person.OfficeBuildingCode); 108 + const isStaffPerson = 109 + !person.StudentType || 110 + !!(person.Title?.trim() && person.OfficeBuildingCode); 109 111 const pool = isStaffPerson ? DEMO_NAMES_STAFF : DEMO_NAMES_STUDENT; 110 112 return pool[parseInt(person.Id.slice(-2), 10) % pool.length]; 111 113 } ··· 128 130 return `${hour > 12 ? hour - 12 : hour || 12}:${m} ${hour >= 12 ? "PM" : "AM"}`; 129 131 } 130 132 131 - const DAY_ORDER = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]; 133 + const DAY_ORDER = [ 134 + "Monday", 135 + "Tuesday", 136 + "Wednesday", 137 + "Thursday", 138 + "Friday", 139 + "Saturday", 140 + "Sunday", 141 + ]; 132 142 const DAY_ABBR: Record<string, string> = { 133 - Monday: "M", Tuesday: "T", Wednesday: "W", Thursday: "Th", 134 - Friday: "F", Saturday: "Sa", Sunday: "Su", 143 + Monday: "M", 144 + Tuesday: "T", 145 + Wednesday: "W", 146 + Thursday: "Th", 147 + Friday: "F", 148 + Saturday: "Sa", 149 + Sunday: "Su", 135 150 }; 136 151 const TYPE_LABEL: Record<string, string> = { 137 152 Lecture: "", ··· 159 174 // Sort courses by earliest day of week 160 175 const courses = [...courseMap.entries()].sort((a, b) => { 161 176 const earliest = (slots: { days: string[] }[]) => 162 - Math.min(...slots.flatMap((s) => s.days.map((d) => DAY_ORDER.indexOf(d)))); 177 + Math.min( 178 + ...slots.flatMap((s) => s.days.map((d) => DAY_ORDER.indexOf(d))), 179 + ); 163 180 return earliest(a[1]) - earliest(b[1]); 164 181 }); 165 182 ··· 171 188 const sortedSlots = [...slots].sort((a, b) => { 172 189 const aFirst = Math.min(...a.days.map((d) => DAY_ORDER.indexOf(d))); 173 190 const bFirst = Math.min(...b.days.map((d) => DAY_ORDER.indexOf(d))); 174 - return aFirst - bFirst || a.item.startTime.localeCompare(b.item.startTime); 191 + return ( 192 + aFirst - bFirst || a.item.startTime.localeCompare(b.item.startTime) 193 + ); 175 194 }); 176 195 177 196 const timeLines = sortedSlots.map(({ item, days }) => { 178 - const sortedDays = [...days].sort((a, b) => DAY_ORDER.indexOf(a) - DAY_ORDER.indexOf(b)); 197 + const sortedDays = [...days].sort( 198 + (a, b) => DAY_ORDER.indexOf(a) - DAY_ORDER.indexOf(b), 199 + ); 179 200 const dayStr = sortedDays.map((d) => DAY_ABBR[d] ?? d).join(""); 180 201 const timeStr = `${formatTime(item.startTime)}–${formatTime(item.endTime)}`; 181 202 const typeStr = TYPE_LABEL[item.type] ?? item.type; ··· 240 261 const md: string[] = []; 241 262 if (!demo && photoDataUrl) md.push(`![Photo](${photoDataUrl})`); 242 263 md.push(`# ${name}`); 243 - const isStaff = !person.StudentType || !!(person.Title?.trim() && person.OfficeBuildingCode); 264 + const isStaff = 265 + !person.StudentType || 266 + !!(person.Title?.trim() && person.OfficeBuildingCode); 244 267 const tags: string[] = []; 245 268 if (isStaff) { 246 269 tags.push(isFacultyHeuristic(person) ? "Faculty" : "Staff"); ··· 271 294 ? info.faculty.term?.description 272 295 : info?.student?.term?.description; 273 296 274 - const nonScheduled = info?.student?.isStudent ? (info.student.nonScheduledCourses ?? []) : []; 297 + const nonScheduled = info?.student?.isStudent 298 + ? (info.student.nonScheduledCourses ?? []) 299 + : []; 275 300 276 301 if (scheduleItems.length || nonScheduled.length) { 277 302 md.push(`## Schedule${termDesc ? ` — ${termDesc}` : ""}\n`); ··· 280 305 md.push("**Online / Unscheduled**"); 281 306 md.push( 282 307 nonScheduled 283 - .map((c) => `- ${demo ? "DEPT 000" : c.code} — ${demo ? "Course Name" : c.title} *(${c.methods})*`) 308 + .map( 309 + (c) => 310 + `- ${demo ? "DEPT 000" : c.code} — ${demo ? "Course Name" : c.title} *(${c.methods})*`, 311 + ) 284 312 .join("\n"), 285 313 ); 286 314 } ··· 305 333 text={person.DepartmentDescription} 306 334 /> 307 335 )} 308 - {!!(isStaff || 309 - person.StudentClass || 310 - person.studentWorker) && <Detail.Metadata.Separator />} 336 + {!!(isStaff || person.StudentClass || person.studentWorker) && ( 337 + <Detail.Metadata.Separator /> 338 + )} 311 339 {isStaff ? ( 312 340 <Detail.Metadata.TagList title="Role"> 313 341 <Detail.Metadata.TagList.Item ··· 352 380 /> 353 381 </Detail.Metadata.TagList> 354 382 )} 355 - {!!(person.DormName || 383 + {!!( 384 + person.DormName || 356 385 person.OfficeBuildingName || 357 386 person.OfficePhone || 358 - info?.person?.box) && <Detail.Metadata.Separator />} 387 + info?.person?.box 388 + ) && <Detail.Metadata.Separator />} 359 389 {person.DormName && ( 360 390 <Detail.Metadata.Label 361 391 title="Dorm" ··· 390 420 text={demo ? "ext. ****" : formatPhone(person.OfficePhone)} 391 421 /> 392 422 )} 393 - {!!(person.AddressCity || person.AddressState || info?.address?.addresslines?.length) && ( 394 - <Detail.Metadata.Separator /> 395 - )} 423 + {!!( 424 + person.AddressCity || 425 + person.AddressState || 426 + info?.address?.addresslines?.length 427 + ) && <Detail.Metadata.Separator />} 396 428 {!!(person.AddressCity || person.AddressState) && ( 397 429 <Detail.Metadata.Label 398 430 title="Hometown" 399 - text={demo ? "City, OH" : [person.AddressCity, person.AddressState].filter(Boolean).join(", ")} 431 + text={ 432 + demo 433 + ? "City, OH" 434 + : [person.AddressCity, person.AddressState] 435 + .filter(Boolean) 436 + .join(", ") 437 + } 400 438 /> 401 439 )} 402 440 {info?.address?.addresslines?.filter(Boolean).length ? ( 403 441 <Detail.Metadata.Label 404 442 title="Address" 405 - text={demo ? "123 Example St, City, OH 00000" : info.address.addresslines.filter(Boolean).join(", ")} 443 + text={ 444 + demo 445 + ? "123 Example St, City, OH 00000" 446 + : info.address.addresslines.filter(Boolean).join(", ") 447 + } 406 448 /> 407 449 ) : null} 408 - {info?.student?.isStudent && (() => { 409 - const majors = info.student.majors.filter(m => m.desc?.trim()); 410 - const minors = info.student.minors.filter(m => m.desc?.trim()); 411 - const concentrations = info.student.concentrations.filter(c => c.desc?.trim()); 412 - const advisors = info.student.advisors.filter(a => a.advisor.name?.trim()); 413 - if (!majors.length && !minors.length && !concentrations.length && !advisors.length) return null; 414 - return ( 415 - <> 416 - <Detail.Metadata.Separator /> 417 - {majors.map((m) => ( 418 - <Detail.Metadata.Label key={m.code} title="Major" text={m.desc} /> 419 - ))} 420 - {minors.map((m) => ( 421 - <Detail.Metadata.Label key={m.code} title="Minor" text={m.desc} /> 422 - ))} 423 - {concentrations.map((c) => ( 424 - <Detail.Metadata.Label key={c.code} title="Concentration" text={c.desc} /> 425 - ))} 426 - {advisors.map((a) => ( 427 - <Detail.Metadata.Label key={a.advisor.id} title="Advisor" text={demo ? "Advisor Name" : a.advisor.name} /> 428 - ))} 429 - </> 430 - ); 431 - })()} 450 + {info?.student?.isStudent && 451 + (() => { 452 + const majors = info.student.majors.filter((m) => m.desc?.trim()); 453 + const minors = info.student.minors.filter((m) => m.desc?.trim()); 454 + const concentrations = info.student.concentrations.filter((c) => 455 + c.desc?.trim(), 456 + ); 457 + const advisors = info.student.advisors.filter((a) => 458 + a.advisor.name?.trim(), 459 + ); 460 + if ( 461 + !majors.length && 462 + !minors.length && 463 + !concentrations.length && 464 + !advisors.length 465 + ) 466 + return null; 467 + return ( 468 + <> 469 + <Detail.Metadata.Separator /> 470 + {majors.map((m) => ( 471 + <Detail.Metadata.Label 472 + key={m.code} 473 + title="Major" 474 + text={m.desc} 475 + /> 476 + ))} 477 + {minors.map((m) => ( 478 + <Detail.Metadata.Label 479 + key={m.code} 480 + title="Minor" 481 + text={m.desc} 482 + /> 483 + ))} 484 + {concentrations.map((c) => ( 485 + <Detail.Metadata.Label 486 + key={c.code} 487 + title="Concentration" 488 + text={c.desc} 489 + /> 490 + ))} 491 + {advisors.map((a) => ( 492 + <Detail.Metadata.Label 493 + key={a.advisor.id} 494 + title="Advisor" 495 + text={demo ? "Advisor Name" : a.advisor.name} 496 + /> 497 + ))} 498 + </> 499 + ); 500 + })()} 432 501 {info?.faculty?.isFaculty && info.faculty.facultyDepts.length > 0 && ( 433 502 <> 434 503 <Detail.Metadata.Separator /> ··· 442 511 </> 443 512 )} 444 513 <Detail.Metadata.Separator /> 445 - <Detail.Metadata.Label title="ID" text={demo ? "000000000" : person.Id} /> 514 + <Detail.Metadata.Label 515 + title="ID" 516 + text={demo ? "000000000" : person.Id} 517 + /> 446 518 </Detail.Metadata> 447 519 } 448 520 actions={ ··· 450 522 {person.Username && ( 451 523 <Action.CopyToClipboard 452 524 title="Copy Email" 453 - content={demo ? "username@cedarville.edu" : email(person.Username)} 525 + content={ 526 + demo ? "username@cedarville.edu" : email(person.Username) 527 + } 454 528 /> 455 529 )} 456 530 {!demo && person.Username && ( ··· 541 615 let badge: List.Item.Accessory | null = null; 542 616 if (!isStudent) { 543 617 const faculty = isFacultyHeuristic(person); 544 - badge = { tag: { value: faculty ? "Faculty" : "Staff", color: faculty ? Color.Orange : Color.Green } }; 618 + badge = { 619 + tag: { 620 + value: faculty ? "Faculty" : "Staff", 621 + color: faculty ? Color.Orange : Color.Green, 622 + }, 623 + }; 545 624 } else if (person.StudentType === "DE") { 546 625 badge = { tag: { value: "DE", color: Color.Orange } }; 547 626 } else if (person.StudentType === "GS" || person.StudentClass === "GS") { ··· 599 678 {person.Username && ( 600 679 <Action.CopyToClipboard 601 680 title="Copy Email" 602 - content={demo ? "username@cedarville.edu" : email(person.Username)} 681 + content={ 682 + demo ? "username@cedarville.edu" : email(person.Username) 683 + } 603 684 /> 604 685 )} 605 686 {!demo && person.Username && (