cedarstalking with keyboard shortcuts
0
fork

Configure Feed

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

feat: toggle demo mode

+97 -54
+12 -1
package.json
··· 15 15 "name": "search-directory", 16 16 "title": "Search Cedarville Directory", 17 17 "description": "Search the Cedarville directory by name", 18 - "mode": "view" 18 + "mode": "view", 19 + "preferences": [ 20 + { 21 + "name": "demoMode", 22 + "title": "Demo Mode", 23 + "description": "Redact personal information for screenshots and demos", 24 + "type": "checkbox", 25 + "label": "Hide personal data", 26 + "required": false, 27 + "default": false 28 + } 29 + ] 19 30 } 20 31 ], 21 32 "dependencies": {
+4 -1
raycast-env.d.ts
··· 14 14 15 15 declare namespace Preferences { 16 16 /** Preferences accessible in the `search-directory` command */ 17 - export type SearchDirectory = ExtensionPreferences & {} 17 + export type SearchDirectory = ExtensionPreferences & { 18 + /** Demo Mode - Redact personal information for screenshots and demos */ 19 + "demoMode": boolean 20 + } 18 21 } 19 22 20 23 declare namespace Arguments {
+81 -52
src/search-directory.tsx
··· 3 3 ActionPanel, 4 4 Color, 5 5 Detail, 6 + getPreferenceValues, 6 7 Icon, 7 8 Image, 8 9 List, ··· 92 93 return !!person.Title && FACULTY_TITLE_KEYWORDS.test(person.Title); 93 94 } 94 95 96 + const DEMO_NAMES_STUDENT = ["Alex Johnson", "Jordan Smith", "Taylor Williams"]; 97 + const DEMO_NAMES_STAFF = ["Dr. Chris Brown", "Pat Miller", "Sam Davis"]; 98 + 99 + function demoName(person: DirectoryPerson): string { 100 + const isStaffPerson = !person.StudentType || !!(person.Title?.trim() && person.OfficeBuildingCode); 101 + const pool = isStaffPerson ? DEMO_NAMES_STAFF : DEMO_NAMES_STUDENT; 102 + return pool[parseInt(person.Id.slice(-2), 10) % pool.length]; 103 + } 104 + 95 105 function parseSearchQuery(query: string): { 96 106 firstName: string; 97 107 lastName: string; ··· 115 125 photoPath, 116 126 cookie, 117 127 onSignOut, 128 + demo, 118 129 }: { 119 130 person: DirectoryPerson; 120 131 photoPath: string | null; 121 132 cookie: string; 122 133 onSignOut: () => void; 134 + demo: boolean; 123 135 }) { 124 - const name = displayName(person, true); 136 + const name = demo ? demoName(person) : displayName(person, true); 125 137 const [photoDataUrl, setPhotoDataUrl] = useState<string | null>(null); 126 138 const [info, setInfo] = useState<PersonInfo | null>(null); 127 139 ··· 159 171 160 172 // Photo full-width at top, name + italic tags below 161 173 const md: string[] = []; 162 - if (photoDataUrl) md.push(`![Photo](${photoDataUrl})`); 174 + if (!demo && photoDataUrl) md.push(`![Photo](${photoDataUrl})`); 163 175 md.push(`# ${name}`); 164 176 const isStaff = !person.StudentType || !!(person.Title?.trim() && person.OfficeBuildingCode); 165 177 const tags: string[] = []; ··· 195 207 if (scheduleItems.length) { 196 208 md.push(`## Schedule${termDesc ? ` — ${termDesc}` : ""}`); 197 209 for (const item of scheduleItems) { 210 + const title = demo ? "DEPT 000" : item.title; 211 + const desc = demo ? "Course Name" : item.description; 198 212 md.push( 199 - `**${item.title}** — ${item.description} \n${item.day} ${formatTime(item.startTime)}–${formatTime(item.endTime)}`, 213 + `**${title}** — ${desc} \n${item.day} ${formatTime(item.startTime)}–${formatTime(item.endTime)}`, 200 214 ); 201 215 } 202 216 } ··· 211 225 {person.Username && ( 212 226 <Detail.Metadata.Label 213 227 title="Email" 214 - text={email(person.Username)} 228 + text={demo ? "username@cedarville.edu" : email(person.Username)} 215 229 /> 216 230 )} 217 231 {person.DepartmentDescription && ( ··· 274 288 <Detail.Metadata.Label 275 289 title="Dorm" 276 290 text={ 277 - person.DormRoom 278 - ? `${person.DormName}, Room ${person.DormRoom}` 279 - : person.DormName 291 + demo 292 + ? "Residence Hall, Room 000" 293 + : person.DormRoom 294 + ? `${person.DormName}, Room ${person.DormRoom}` 295 + : person.DormName 280 296 } 281 297 /> 282 298 )} ··· 293 309 {person.OfficePhone && ( 294 310 <Detail.Metadata.Label 295 311 title="Phone" 296 - text={formatPhone(person.OfficePhone)} 312 + text={demo ? "ext. ****" : formatPhone(person.OfficePhone)} 297 313 /> 298 314 )} 299 315 {!!(person.AddressCity || person.AddressState) && ( ··· 302 318 {!!(person.AddressCity || person.AddressState) && ( 303 319 <Detail.Metadata.Label 304 320 title="Hometown" 305 - text={[person.AddressCity, person.AddressState] 306 - .filter(Boolean) 307 - .join(", ")} 321 + text={demo ? "City, OH" : [person.AddressCity, person.AddressState].filter(Boolean).join(", ")} 308 322 /> 309 323 )} 310 324 {info?.student?.isStudent && info.student.majors.length > 0 && ( ··· 335 349 <Detail.Metadata.Label 336 350 key={a.id} 337 351 title="Advisor" 338 - text={a.name} 352 + text={demo ? "Advisor Name" : a.name} 339 353 /> 340 354 ))} 341 355 </> ··· 353 367 </> 354 368 )} 355 369 <Detail.Metadata.Separator /> 356 - <Detail.Metadata.Label title="ID" text={person.Id} /> 370 + <Detail.Metadata.Label title="ID" text={demo ? "000000000" : person.Id} /> 357 371 </Detail.Metadata> 358 372 } 359 373 actions={ ··· 361 375 {person.Username && ( 362 376 <Action.CopyToClipboard 363 377 title="Copy Email" 364 - content={email(person.Username)} 378 + content={demo ? "username@cedarville.edu" : email(person.Username)} 365 379 /> 366 380 )} 367 381 {person.OfficePhone && ( 368 382 <Action.CopyToClipboard 369 383 title="Copy Phone" 370 - content={formatPhone(person.OfficePhone)} 384 + content={demo ? "ext. ****" : formatPhone(person.OfficePhone)} 371 385 /> 372 386 )} 373 387 <Action.CopyToClipboard 374 388 title="Copy ID" 375 - content={person.Id} 389 + content={demo ? "000000000" : person.Id} 376 390 icon={Icon.Person} 377 391 /> 378 - <Action.OpenInBrowser 379 - title="Open Info Page" 380 - url={`https://selfservice.cedarville.edu/Cedarinfo/Info?id=${person.Id}`} 381 - icon={Icon.Globe} 382 - /> 383 - <Action.CopyToClipboard 384 - title="Export as JSON" 385 - icon={Icon.Code} 386 - content={JSON.stringify(person, null, 2)} 387 - shortcut={{ modifiers: ["cmd", "shift"], key: "j" }} 388 - /> 392 + {!demo && ( 393 + <Action.OpenInBrowser 394 + title="Open Info Page" 395 + url={`https://selfservice.cedarville.edu/Cedarinfo/Info?id=${person.Id}`} 396 + icon={Icon.Globe} 397 + /> 398 + )} 399 + {!demo && ( 400 + <Action.CopyToClipboard 401 + title="Export as JSON" 402 + icon={Icon.Code} 403 + content={JSON.stringify(person, null, 2)} 404 + shortcut={{ modifiers: ["cmd", "shift"], key: "j" }} 405 + /> 406 + )} 389 407 <Action 390 408 title="Sign Out" 391 409 icon={Icon.ArrowLeft} ··· 405 423 photoPath, 406 424 cookie, 407 425 onSignOut, 426 + demo, 408 427 }: { 409 428 person: DirectoryPerson; 410 429 photoPath: string | null; 411 430 cookie: string; 412 431 onSignOut: () => void; 432 + demo: boolean; 413 433 }) { 414 - const name = displayName(person); 434 + const name = demo ? demoName(person) : displayName(person); 415 435 416 436 // Subtitle: title for staff, dorm for students 417 437 const isStudent = ··· 419 439 !!person.StudentType && 420 440 !(person.Title?.trim() && person.OfficeBuildingCode); 421 441 const hasOffice = !isStudent && !!person.OfficeBuildingCode; 422 - const rawTitle = isStudent 423 - ? person.DormName 424 - ? `${person.DormName}${person.DormRoom ? ` ${person.DormRoom}` : ""}` 425 - : person.Username 426 - ? email(person.Username) 427 - : undefined 428 - : ((person.Title?.trim() || 429 - (person.Username ? email(person.Username) : undefined)) ?? 430 - undefined); 442 + const rawTitle = demo 443 + ? undefined 444 + : isStudent 445 + ? person.DormName 446 + ? `${person.DormName}${person.DormRoom ? ` ${person.DormRoom}` : ""}` 447 + : person.Username 448 + ? email(person.Username) 449 + : undefined 450 + : ((person.Title?.trim() || 451 + (person.Username ? email(person.Username) : undefined)) ?? 452 + undefined); 431 453 const subtitle = 432 454 hasOffice && rawTitle && rawTitle.length > 30 433 455 ? `${rawTitle.slice(0, 29)}…` ··· 468 490 title={name} 469 491 subtitle={subtitle} 470 492 icon={ 471 - photoPath 493 + !demo && photoPath 472 494 ? { 473 495 source: photoPath, 474 496 mask: Image.Mask.Circle, ··· 488 510 photoPath={photoPath} 489 511 cookie={cookie} 490 512 onSignOut={onSignOut} 513 + demo={demo} 491 514 /> 492 515 } 493 516 /> 494 517 {person.Username && ( 495 518 <Action.CopyToClipboard 496 519 title="Copy Email" 497 - content={email(person.Username)} 520 + content={demo ? "username@cedarville.edu" : email(person.Username)} 498 521 /> 499 522 )} 500 523 {person.OfficePhone && ( 501 524 <Action.CopyToClipboard 502 525 title="Copy Phone" 503 - content={formatPhone(person.OfficePhone)} 526 + content={demo ? "ext. ****" : formatPhone(person.OfficePhone)} 504 527 /> 505 528 )} 506 529 <Action.CopyToClipboard 507 530 title="Copy ID" 508 - content={person.Id} 531 + content={demo ? "000000000" : person.Id} 509 532 icon={Icon.Person} 510 533 /> 511 - <Action.OpenInBrowser 512 - title="Open Info Page" 513 - url={`https://selfservice.cedarville.edu/Cedarinfo/Info?id=${person.Id}`} 514 - icon={Icon.Globe} 515 - /> 516 - <Action.CopyToClipboard 517 - title="Export as JSON" 518 - icon={Icon.Code} 519 - content={JSON.stringify(person, null, 2)} 520 - shortcut={{ modifiers: ["cmd", "shift"], key: "j" }} 521 - /> 534 + {!demo && ( 535 + <Action.OpenInBrowser 536 + title="Open Info Page" 537 + url={`https://selfservice.cedarville.edu/Cedarinfo/Info?id=${person.Id}`} 538 + icon={Icon.Globe} 539 + /> 540 + )} 541 + {!demo && ( 542 + <Action.CopyToClipboard 543 + title="Export as JSON" 544 + icon={Icon.Code} 545 + content={JSON.stringify(person, null, 2)} 546 + shortcut={{ modifiers: ["cmd", "shift"], key: "j" }} 547 + /> 548 + )} 522 549 <Action 523 550 title="Sign Out" 524 551 icon={Icon.ArrowLeft} ··· 534 561 // ─── Main command ────────────────────────────────────────────────────────── 535 562 536 563 export default function SearchDirectory() { 564 + const { demoMode: demo } = getPreferenceValues<{ demoMode: boolean }>(); 537 565 const [authState, setAuthState] = useState<AuthState>({ kind: "loading" }); 538 566 const [query, setQuery] = useState(""); 539 567 const [filter, setFilter] = useState(""); ··· 818 846 photoPath={photoPaths[person.Id] ?? null} 819 847 cookie={authState.cookie} 820 848 onSignOut={handleSignOut} 849 + demo={demo} 821 850 /> 822 851 ))} 823 852 </List.Section>