granola-toolkit 0.34.5 → 0.34.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +140 -144
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -4414,10 +4414,115 @@ async function openExternalUrl(url, options = {}) {
4414
4414
  }))(command.file, command.args);
4415
4415
  }
4416
4416
  //#endregion
4417
+ //#region src/web/client-state.ts
4418
+ function parseWorkspaceTab(value) {
4419
+ switch (value) {
4420
+ case "metadata":
4421
+ case "raw":
4422
+ case "transcript": return value;
4423
+ default: return "notes";
4424
+ }
4425
+ }
4426
+ function startupSelectionFromSearch(search) {
4427
+ const params = new URLSearchParams(search);
4428
+ return {
4429
+ folderId: params.get("folder")?.trim() || "",
4430
+ meetingId: params.get("meeting")?.trim() || "",
4431
+ workspaceTab: parseWorkspaceTab(params.get("tab"))
4432
+ };
4433
+ }
4434
+ function buildBrowserUrlPath(currentHref, selection) {
4435
+ const url = new URL(currentHref);
4436
+ if (selection.selectedFolderId) url.searchParams.set("folder", selection.selectedFolderId);
4437
+ else url.searchParams.delete("folder");
4438
+ if (selection.selectedMeetingId) url.searchParams.set("meeting", selection.selectedMeetingId);
4439
+ else url.searchParams.delete("meeting");
4440
+ if (parseWorkspaceTab(selection.workspaceTab) !== "notes") url.searchParams.set("tab", parseWorkspaceTab(selection.workspaceTab));
4441
+ else url.searchParams.delete("tab");
4442
+ return `${url.pathname}${url.search}${url.hash}`;
4443
+ }
4444
+ function exportScopeLabel(scope) {
4445
+ return scope && scope.mode === "folder" ? `Folder: ${scope.folderName || scope.folderId}` : "Scope: All meetings";
4446
+ }
4447
+ function currentFilterSummary(filters) {
4448
+ const parts = [];
4449
+ if (filters.selectedFolderId) {
4450
+ const folder = filters.folders.find((candidate) => candidate.id === filters.selectedFolderId);
4451
+ parts.push(`folder "${folder ? folder.name : filters.selectedFolderId}"`);
4452
+ }
4453
+ if (filters.search) parts.push(`search "${filters.search}"`);
4454
+ if (filters.updatedFrom) parts.push(`from ${filters.updatedFrom}`);
4455
+ if (filters.updatedTo) parts.push(`to ${filters.updatedTo}`);
4456
+ return parts.join(", ");
4457
+ }
4458
+ function selectMeetingId(meetings, selectedMeetingId) {
4459
+ if (selectedMeetingId && meetings.some((meeting) => meeting.id === selectedMeetingId)) return selectedMeetingId;
4460
+ return meetings[0]?.id ?? null;
4461
+ }
4462
+ function buildMeetingsQuery(filters, options = {}) {
4463
+ const params = new URLSearchParams();
4464
+ params.set("limit", String(options.limit ?? 100));
4465
+ params.set("sort", filters.sort || "updated-desc");
4466
+ if (filters.search) params.set("search", filters.search);
4467
+ if (filters.updatedFrom) params.set("updatedFrom", filters.updatedFrom);
4468
+ if (filters.updatedTo) params.set("updatedTo", filters.updatedTo);
4469
+ if (filters.selectedFolderId) params.set("folderId", filters.selectedFolderId);
4470
+ if (options.refresh) params.set("refresh", "true");
4471
+ return `?${params.toString()}`;
4472
+ }
4473
+ function buildNotesExportRequest(selectedFolderId) {
4474
+ return {
4475
+ folderId: selectedFolderId || void 0,
4476
+ format: "markdown"
4477
+ };
4478
+ }
4479
+ function buildTranscriptsExportRequest(selectedFolderId) {
4480
+ return {
4481
+ folderId: selectedFolderId || void 0,
4482
+ format: "text"
4483
+ };
4484
+ }
4485
+ function nextWorkspaceTab(currentTab, key) {
4486
+ const current = parseWorkspaceTab(currentTab);
4487
+ switch (key) {
4488
+ case "1": return "notes";
4489
+ case "2": return "transcript";
4490
+ case "3": return "metadata";
4491
+ case "4": return "raw";
4492
+ case "]":
4493
+ switch (current) {
4494
+ case "notes": return "transcript";
4495
+ case "transcript": return "metadata";
4496
+ case "metadata": return "raw";
4497
+ case "raw": return "notes";
4498
+ }
4499
+ break;
4500
+ case "[":
4501
+ switch (current) {
4502
+ case "notes": return "raw";
4503
+ case "transcript": return "notes";
4504
+ case "metadata": return "transcript";
4505
+ case "raw": return "metadata";
4506
+ }
4507
+ break;
4508
+ default: return;
4509
+ }
4510
+ }
4511
+ //#endregion
4417
4512
  //#region src/web/client-script.ts
4418
4513
  const granolaWebClientScript = String.raw`
4419
4514
  const serverConfig = window.__GRANOLA_SERVER__ || { passwordRequired: false };
4420
4515
  const workspaceTabs = ["notes", "transcript", "metadata", "raw"];
4516
+ ${parseWorkspaceTab.toString()}
4517
+ ${startupSelectionFromSearch.toString()}
4518
+ ${buildBrowserUrlPath.toString()}
4519
+ ${exportScopeLabel.toString()}
4520
+ ${currentFilterSummary.toString()}
4521
+ ${selectMeetingId.toString()}
4522
+ ${buildMeetingsQuery.toString()}
4523
+ ${buildNotesExportRequest.toString()}
4524
+ ${buildTranscriptsExportRequest.toString()}
4525
+ ${nextWorkspaceTab.toString()}
4421
4526
 
4422
4527
  const state = {
4423
4528
  appState: null,
@@ -4466,41 +4571,12 @@ const els = {
4466
4571
  workspaceTabs: document.querySelectorAll("[data-workspace-tab]"),
4467
4572
  };
4468
4573
 
4469
- function parseWorkspaceTab(value) {
4470
- return workspaceTabs.includes(value) ? value : "notes";
4471
- }
4472
-
4473
- function startupSelection() {
4474
- const params = new URLSearchParams(window.location.search);
4475
- return {
4476
- folderId: params.get("folder")?.trim() || "",
4477
- meetingId: params.get("meeting")?.trim() || "",
4478
- workspaceTab: parseWorkspaceTab(params.get("tab")),
4479
- };
4480
- }
4481
-
4482
4574
  function syncBrowserUrl() {
4483
- const url = new URL(window.location.href);
4484
-
4485
- if (state.selectedFolderId) {
4486
- url.searchParams.set("folder", state.selectedFolderId);
4487
- } else {
4488
- url.searchParams.delete("folder");
4489
- }
4490
-
4491
- if (state.selectedMeetingId) {
4492
- url.searchParams.set("meeting", state.selectedMeetingId);
4493
- } else {
4494
- url.searchParams.delete("meeting");
4495
- }
4496
-
4497
- if (state.workspaceTab !== "notes") {
4498
- url.searchParams.set("tab", state.workspaceTab);
4499
- } else {
4500
- url.searchParams.delete("tab");
4501
- }
4502
-
4503
- const nextPath = url.pathname + url.search + url.hash;
4575
+ const nextPath = buildBrowserUrlPath(window.location.href, {
4576
+ selectedFolderId: state.selectedFolderId,
4577
+ selectedMeetingId: state.selectedMeetingId,
4578
+ workspaceTab: state.workspaceTab,
4579
+ });
4504
4580
  const currentPath = window.location.pathname + window.location.search + window.location.hash;
4505
4581
  if (nextPath !== currentPath) {
4506
4582
  history.replaceState(null, "", nextPath);
@@ -4515,12 +4591,6 @@ function escapeHtml(value) {
4515
4591
  .replaceAll('"', """);
4516
4592
  }
4517
4593
 
4518
- function exportScopeLabel(scope) {
4519
- return scope && scope.mode === "folder"
4520
- ? "Folder: " + (scope.folderName || scope.folderId)
4521
- : "Scope: All meetings";
4522
- }
4523
-
4524
4594
  function setStatus(label, tone = "idle") {
4525
4595
  els.stateBadge.textContent = label;
4526
4596
  els.stateBadge.dataset.tone = tone;
@@ -4534,29 +4604,6 @@ function syncFilterInputs() {
4534
4604
  els.updatedTo.value = state.updatedTo;
4535
4605
  }
4536
4606
 
4537
- function currentFilterSummary() {
4538
- const parts = [];
4539
-
4540
- if (state.selectedFolderId) {
4541
- const folder = state.folders.find((candidate) => candidate.id === state.selectedFolderId);
4542
- parts.push("folder " + (folder ? '"' + folder.name + '"' : '"' + state.selectedFolderId + '"'));
4543
- }
4544
-
4545
- if (state.search) {
4546
- parts.push('search "' + state.search + '"');
4547
- }
4548
-
4549
- if (state.updatedFrom) {
4550
- parts.push("from " + state.updatedFrom);
4551
- }
4552
-
4553
- if (state.updatedTo) {
4554
- parts.push("to " + state.updatedTo);
4555
- }
4556
-
4557
- return parts.join(", ");
4558
- }
4559
-
4560
4607
  function renderWorkspaceTabs() {
4561
4608
  for (const button of els.workspaceTabs) {
4562
4609
  button.dataset.selected = button.dataset.workspaceTab === state.workspaceTab ? "true" : "false";
@@ -4730,7 +4777,13 @@ function renderMeetingList() {
4730
4777
  state.selectedMeeting = null;
4731
4778
  state.selectedMeetingBundle = null;
4732
4779
  syncBrowserUrl();
4733
- const filterSummary = currentFilterSummary();
4780
+ const filterSummary = currentFilterSummary({
4781
+ folders: state.folders,
4782
+ search: state.search,
4783
+ selectedFolderId: state.selectedFolderId,
4784
+ updatedFrom: state.updatedFrom,
4785
+ updatedTo: state.updatedTo,
4786
+ });
4734
4787
  const message = filterSummary
4735
4788
  ? "No meetings match " + filterSummary + "."
4736
4789
  : "No meetings yet. Try Refresh.";
@@ -4739,10 +4792,7 @@ function renderMeetingList() {
4739
4792
  return;
4740
4793
  }
4741
4794
 
4742
- const visibleIds = new Set(state.meetings.map((meeting) => meeting.id));
4743
- if (!state.selectedMeetingId || !visibleIds.has(state.selectedMeetingId)) {
4744
- state.selectedMeetingId = state.meetings[0]?.id || null;
4745
- }
4795
+ state.selectedMeetingId = selectMeetingId(state.meetings, state.selectedMeetingId);
4746
4796
  syncBrowserUrl();
4747
4797
 
4748
4798
  els.list.innerHTML = state.meetings
@@ -4886,34 +4936,6 @@ async function fetchJson(path, init) {
4886
4936
  return payload;
4887
4937
  }
4888
4938
 
4889
- function buildMeetingsQuery(limit = 100, refresh = false) {
4890
- const params = new URLSearchParams();
4891
- params.set("limit", String(limit));
4892
- params.set("sort", state.sort);
4893
-
4894
- if (state.search) {
4895
- params.set("search", state.search);
4896
- }
4897
-
4898
- if (state.updatedFrom) {
4899
- params.set("updatedFrom", state.updatedFrom);
4900
- }
4901
-
4902
- if (state.updatedTo) {
4903
- params.set("updatedTo", state.updatedTo);
4904
- }
4905
-
4906
- if (state.selectedFolderId) {
4907
- params.set("folderId", state.selectedFolderId);
4908
- }
4909
-
4910
- if (refresh) {
4911
- params.set("refresh", "true");
4912
- }
4913
-
4914
- return "?" + params.toString();
4915
- }
4916
-
4917
4939
  async function loadFolders(options = {}) {
4918
4940
  const refresh = options.refresh === true;
4919
4941
 
@@ -4953,7 +4975,22 @@ async function loadMeetings(options = {}) {
4953
4975
 
4954
4976
  try {
4955
4977
  state.listError = "";
4956
- const payload = await fetchJson("/meetings" + buildMeetingsQuery(100, refresh));
4978
+ const payload = await fetchJson(
4979
+ "/meetings" +
4980
+ buildMeetingsQuery(
4981
+ {
4982
+ search: state.search,
4983
+ selectedFolderId: state.selectedFolderId,
4984
+ sort: state.sort,
4985
+ updatedFrom: state.updatedFrom,
4986
+ updatedTo: state.updatedTo,
4987
+ },
4988
+ {
4989
+ limit: 100,
4990
+ refresh,
4991
+ },
4992
+ ),
4993
+ );
4957
4994
  state.meetings = payload.meetings || [];
4958
4995
  state.meetingSource = payload.source || "live";
4959
4996
 
@@ -5063,10 +5100,7 @@ async function syncAuthState() {
5063
5100
  async function exportNotes() {
5064
5101
  setStatus(state.selectedFolderId ? "Exporting folder notes…" : "Exporting notes…", "busy");
5065
5102
  await fetchJson("/exports/notes", {
5066
- body: JSON.stringify({
5067
- folderId: state.selectedFolderId || undefined,
5068
- format: "markdown",
5069
- }),
5103
+ body: JSON.stringify(buildNotesExportRequest(state.selectedFolderId)),
5070
5104
  headers: { "content-type": "application/json" },
5071
5105
  method: "POST",
5072
5106
  });
@@ -5079,10 +5113,7 @@ async function exportTranscripts() {
5079
5113
  "busy",
5080
5114
  );
5081
5115
  await fetchJson("/exports/transcripts", {
5082
- body: JSON.stringify({
5083
- folderId: state.selectedFolderId || undefined,
5084
- format: "text",
5085
- }),
5116
+ body: JSON.stringify(buildTranscriptsExportRequest(state.selectedFolderId)),
5086
5117
  headers: { "content-type": "application/json" },
5087
5118
  method: "POST",
5088
5119
  });
@@ -5386,50 +5417,15 @@ document.addEventListener("keydown", (event) => {
5386
5417
  return;
5387
5418
  }
5388
5419
 
5389
- const tabs = ["notes", "transcript", "metadata", "raw"];
5390
- if (event.key === "1") {
5391
- state.workspaceTab = "notes";
5392
- syncBrowserUrl();
5393
- renderMeetingDetail();
5394
- return;
5395
- }
5396
-
5397
- if (event.key === "2") {
5398
- state.workspaceTab = "transcript";
5399
- syncBrowserUrl();
5400
- renderMeetingDetail();
5401
- return;
5402
- }
5403
-
5404
- if (event.key === "3") {
5405
- state.workspaceTab = "metadata";
5406
- syncBrowserUrl();
5407
- renderMeetingDetail();
5408
- return;
5409
- }
5410
-
5411
- if (event.key === "4") {
5412
- state.workspaceTab = "raw";
5413
- syncBrowserUrl();
5414
- renderMeetingDetail();
5415
- return;
5416
- }
5417
-
5418
- const currentIndex = tabs.indexOf(state.workspaceTab);
5419
- if (event.key === "]") {
5420
- state.workspaceTab = tabs[(currentIndex + 1) % tabs.length];
5421
- syncBrowserUrl();
5422
- renderMeetingDetail();
5423
- }
5424
-
5425
- if (event.key === "[") {
5426
- state.workspaceTab = tabs[(currentIndex + tabs.length - 1) % tabs.length];
5420
+ const nextTab = nextWorkspaceTab(state.workspaceTab, event.key);
5421
+ if (nextTab) {
5422
+ state.workspaceTab = nextTab;
5427
5423
  syncBrowserUrl();
5428
5424
  renderMeetingDetail();
5429
5425
  }
5430
5426
  });
5431
5427
 
5432
- const initialSelection = startupSelection();
5428
+ const initialSelection = startupSelectionFromSearch(window.location.search);
5433
5429
  state.selectedFolderId = initialSelection.folderId || null;
5434
5430
  state.selectedMeetingId = initialSelection.meetingId || null;
5435
5431
  state.workspaceTab = initialSelection.workspaceTab;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "granola-toolkit",
3
- "version": "0.34.5",
3
+ "version": "0.34.6",
4
4
  "description": "Toolkit for exporting and working with Granola meetings, notes, and transcripts",
5
5
  "keywords": [
6
6
  "cli",