@crvouga/mockingbird-service-google-calendar 1.6.0 → 1.6.1

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.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # Changelog — @crvouga/mockingbird-service-google-calendar
2
2
 
3
- ## 1.6.0 (2026-09-30)
3
+ ## 1.6.1 (2026-09-30)
4
+
5
+ ### Fixes and improvements
6
+
7
+ - keep admin buttons pressed until the action finishes ([0aabfcb](https://github.com/crvouga/mockingbird/commit/0aabfcb5f392aa51e193f9c3f817d5877d92bc6a))
8
+ - size admin select fields like the text inputs beside them ([0fb3ef2](https://github.com/crvouga/mockingbird/commit/0fb3ef2ddbf43376c93a2ae6d87c6c9bf890dafc))
9
+ - keep admin section tabs wired when boot is renamed ([4eda25e](https://github.com/crvouga/mockingbird/commit/4eda25eacd59da048d196f7cc8369d7a02b47bfc))
10
+
11
+ ## 1.6.0 (2026-09-29)
4
12
 
5
13
  ### Features
6
14
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createRuntime
3
- } from "./chunk-ABVGQK6B.js";
3
+ } from "./chunk-UFM3RECQ.js";
4
4
 
5
5
  // ../../adapters/node/dist/cli.js
6
6
  import { readFile } from "node:fs/promises";
@@ -361,4 +361,4 @@ export {
361
361
  createServer2 as createServer,
362
362
  serveTarget
363
363
  };
364
- //# sourceMappingURL=chunk-UFHN47JF.js.map
364
+ //# sourceMappingURL=chunk-JTIBRMC6.js.map
@@ -2323,6 +2323,50 @@ function bootAdmin(config) {
2323
2323
  if (render)
2324
2324
  await render();
2325
2325
  }
2326
+ const delay = (ms) => new Promise((resolve) => {
2327
+ window.setTimeout(resolve, ms);
2328
+ });
2329
+ const confirmAction = async (button, pending, done, work) => {
2330
+ if (button.dataset.pending === "1")
2331
+ return;
2332
+ const label = button.textContent ?? "";
2333
+ const width = button.offsetWidth;
2334
+ button.dataset.pending = "1";
2335
+ button.classList.add("pressed");
2336
+ button.setAttribute("aria-busy", "true");
2337
+ button.style.minWidth = `${width}px`;
2338
+ button.textContent = pending;
2339
+ try {
2340
+ await work();
2341
+ button.textContent = done;
2342
+ await delay(1e3);
2343
+ } catch (error) {
2344
+ showError(error);
2345
+ } finally {
2346
+ button.textContent = label;
2347
+ button.style.minWidth = "";
2348
+ button.classList.remove("pressed");
2349
+ button.removeAttribute("aria-busy");
2350
+ delete button.dataset.pending;
2351
+ }
2352
+ };
2353
+ document.addEventListener("click", (event) => {
2354
+ const target = event.target;
2355
+ if (!(target instanceof Element))
2356
+ return;
2357
+ const button = target.closest("button");
2358
+ if (!(button instanceof HTMLButtonElement) || button.dataset.pending === "1")
2359
+ return;
2360
+ button.classList.add("pressed");
2361
+ const token = String(Date.now());
2362
+ button.dataset.press = token;
2363
+ window.setTimeout(() => {
2364
+ if (button.dataset.press !== token || button.dataset.pending === "1")
2365
+ return;
2366
+ button.classList.remove("pressed");
2367
+ delete button.dataset.press;
2368
+ }, 450);
2369
+ });
2326
2370
  el("namespace", HTMLSelectElement).addEventListener("change", () => {
2327
2371
  state.namespace = el("namespace", HTMLSelectElement).value;
2328
2372
  refresh().catch(showError);
@@ -2333,7 +2377,7 @@ function bootAdmin(config) {
2333
2377
  refresh().catch(showError);
2334
2378
  });
2335
2379
  el("refresh", HTMLButtonElement).addEventListener("click", () => {
2336
- refresh().catch(showError);
2380
+ confirmAction(el("refresh", HTMLButtonElement), "Refreshing...", "Updated", () => refresh()).catch(showError);
2337
2381
  });
2338
2382
  el("collections", HTMLElement).addEventListener("click", (event) => {
2339
2383
  const target = event.target;
@@ -2456,10 +2500,10 @@ function bootAdmin(config) {
2456
2500
  }).then(() => renderFaults()).catch(showError);
2457
2501
  });
2458
2502
  el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2459
- api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2503
+ confirmAction(el("fault-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/faults").then(() => renderFaults())).catch(showError);
2460
2504
  });
2461
2505
  el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2462
- api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2506
+ confirmAction(el("journal-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/requests").then(() => renderJournal())).catch(showError);
2463
2507
  });
2464
2508
  el("route-list", HTMLElement).addEventListener("click", (event) => {
2465
2509
  const target = event.target;
@@ -2574,9 +2618,13 @@ function bootAdmin(config) {
2574
2618
  var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2575
2619
  var adminClientSource = (config) => {
2576
2620
  const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2621
+ const entry = bootAdmin.name;
2622
+ if (!/^[A-Za-z_$][\w$]*$/.test(entry)) {
2623
+ throw new Error("admin client cannot call its boot function");
2624
+ }
2577
2625
  return `<script>
2578
2626
  ${clientPrelude}
2579
- bootAdmin(${payload});
2627
+ ${entry}(${payload});
2580
2628
  </script>`;
2581
2629
  };
2582
2630
 
@@ -2790,9 +2838,24 @@ var renderAdminDocument = (service) => {
2790
2838
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
2791
2839
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
2792
2840
  label.field input, label.field select, .controls input[type="password"] {
2793
- background: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
2794
- padding: 7px 8px; min-height: 36px;
2795
- }
2841
+ background-color: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
2842
+ padding: 7px 8px; min-height: 36px; line-height: 1.2;
2843
+ }
2844
+ /* A native menu ignores min-height, so Preset rendered about half as tall as
2845
+ the text fields in the same form. Paint it like those fields. */
2846
+ label.field select {
2847
+ -webkit-appearance: none; appearance: none;
2848
+ padding-right: 28px;
2849
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%2371717a' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M2.5 4.5 6 8l3.5-3.5'/%3E%3C/svg%3E");
2850
+ background-repeat: no-repeat; background-position: right 10px center; background-size: 12px 12px;
2851
+ }
2852
+ .stack > .field > input,
2853
+ .stack > .field > select,
2854
+ .stack > .field > textarea,
2855
+ dialog .field > input,
2856
+ dialog .field > select,
2857
+ dialog .field > textarea { width: 100%; }
2858
+ .icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger, nav.side button { cursor: pointer; }
2796
2859
  .icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger {
2797
2860
  border-radius: 9px; min-height: 36px; padding: 0 12px; border: 1px solid var(--line);
2798
2861
  background: var(--bg-elev);
@@ -2800,6 +2863,14 @@ var renderAdminDocument = (service) => {
2800
2863
  .btn-primary { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
2801
2864
  .btn-quiet { background: transparent; }
2802
2865
  .btn-danger { color: var(--err); }
2866
+ /* Held after mouseup. Clearing an empty journal changes nothing on screen,
2867
+ so the press itself has to stay visible. */
2868
+ .icon-btn:active, .btn:active, .btn-quiet:active, .btn-danger:active, nav.side button:active,
2869
+ .icon-btn.pressed, .btn.pressed, .btn-quiet.pressed, .btn-danger.pressed, nav.side button.pressed {
2870
+ background: var(--bg-inset); border-color: var(--line-strong); color: var(--ink);
2871
+ }
2872
+ .btn-danger:active, .btn-danger.pressed { background: var(--err-soft); color: var(--err); }
2873
+ .btn-primary:active, .btn-primary.pressed { filter: brightness(0.88); color: var(--accent-ink); }
2803
2874
  /* minmax(0, 1fr): a 1fr column will not shrink below its content, so a long
2804
2875
  record or the full nav row would widen the page past the viewport. */
2805
2876
  .layout {
@@ -2888,8 +2959,16 @@ var renderAdminDocument = (service) => {
2888
2959
  input, select, textarea { font-size: 16px; }
2889
2960
  .brand, .vendor, .controls { width: 100%; }
2890
2961
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
2891
- .layout { grid-template-columns: minmax(0, 1fr); }
2962
+ /* The layout fills the screen. A single-column grid stretches every auto
2963
+ row, so the tab bar grew with the leftover height and the buttons floated
2964
+ in the middle of it. The bar stays one row; the page below takes the rest. */
2965
+ .layout {
2966
+ grid-template-columns: minmax(0, 1fr);
2967
+ grid-template-rows: auto minmax(0, 1fr);
2968
+ align-content: start;
2969
+ }
2892
2970
  nav.side {
2971
+ align-self: start; height: auto;
2893
2972
  flex-direction: row; align-items: center; overflow-x: auto; min-width: 0;
2894
2973
  border-right: 0; border-bottom: 1px solid var(--line);
2895
2974
  padding: 8px;
@@ -3757,7 +3836,7 @@ var stateAdminRoutes = (routes) => {
3757
3836
  };
3758
3837
 
3759
3838
  // ../core/dist/version.js
3760
- var PACKAGE_VERSION = true ? "1.6.0" : UNRELEASED_VERSION;
3839
+ var PACKAGE_VERSION = true ? "1.6.1" : UNRELEASED_VERSION;
3761
3840
 
3762
3841
  // ../core/dist/signing.js
3763
3842
  var encoder = new TextEncoder();
@@ -5842,4 +5921,4 @@ export {
5842
5921
  emailFromCode,
5843
5922
  GoogleCalendarAPI
5844
5923
  };
5845
- //# sourceMappingURL=chunk-ABVGQK6B.js.map
5924
+ //# sourceMappingURL=chunk-UFM3RECQ.js.map