From 510fd8cbe53abb39cba2c7cbaaefcf2783dc0066 Mon Sep 17 00:00:00 2001
From: "netop://ウィビ" <paul@webb.page>
Date: Fri, 24 Apr 2026 16:37:33 -0700
Subject: Implement v0.6-1.0: shortcuts, format, export/import, splitter,
 timing, APQ

- v0.6: matchShortcut + format(); Cmd+Shift+Enter/W/F + Cmd+Alt+arrows
- v0.7: SessionStore.exportAll/importTabs with version-1 validator
- v0.8: Splitter component + four resize handles persisted under layout.*
- v0.10: createApqFetcher (HTTP-only) wrapping shared http-body helpers
- Drop .svelte re-exports from index.ts for multi-entry JSR/npm publishing
---
 source/library/components/HistoryPanel.svelte | 106 ++++++++++++++++++++++++--
 1 file changed, 100 insertions(+), 6 deletions(-)

(limited to 'source/library/components/HistoryPanel.svelte')

diff --git a/source/library/components/HistoryPanel.svelte b/source/library/components/HistoryPanel.svelte
index b7f5c4c..01f397a 100644
--- a/source/library/components/HistoryPanel.svelte
+++ b/source/library/components/HistoryPanel.svelte
@@ -3,13 +3,29 @@
 
   type Props = {
     entries: HistoryEntry[];
+    notice?: string | null;
     onClear: () => void;
+    onDismissNotice?: () => void;
+    onExport?: () => void;
     onFavorite: (id: string) => void;
+    onImport?: (file: File) => void;
     onLoad: (id: string, inNewTab: boolean) => void;
     onRemove: (id: string) => void;
   };
 
-  let { entries, onClear, onFavorite, onLoad, onRemove }: Props = $props();
+  let {
+    entries,
+    notice = null,
+    onClear,
+    onDismissNotice,
+    onExport,
+    onFavorite,
+    onImport,
+    onLoad,
+    onRemove
+  }: Props = $props();
+
+  let fileInput = $state<HTMLInputElement | null>(null);
 
   const sorted = $derived([...entries].sort((a, b) => {
     if (a.favorite !== b.favorite)
@@ -33,6 +49,20 @@
       onLoad(id, event.shiftKey);
     }
   }
+
+  function onImportClick() {
+    fileInput?.click();
+  }
+
+  function onFileChange(event: Event) {
+    const input = event.currentTarget as HTMLInputElement;
+    const file = input.files?.[0];
+
+    if (file)
+      onImport?.(file);
+
+    input.value = "";
+  }
 </script>
 
 <style lang="scss">
@@ -50,6 +80,7 @@
     align-items: center;
     border-bottom: 1px solid var(--graphiql-border, #333);
     display: flex;
+    gap: 0.5rem;
     justify-content: space-between;
     padding: 0.5rem 0.75rem;
   }
@@ -59,7 +90,12 @@
     font-weight: 600;
   }
 
-  .clear {
+  .actions {
+    display: flex;
+    gap: 0.5rem;
+  }
+
+  .action {
     background: none;
     border: none;
     color: var(--graphiql-muted, #858585);
@@ -73,6 +109,36 @@
     }
   }
 
+  .hidden-input {
+    display: none;
+  }
+
+  .notice {
+    align-items: center;
+    background: var(--graphiql-bg, #1e1e1e);
+    border-bottom: 1px solid var(--graphiql-border, #333);
+    color: var(--graphiql-fg, #d4d4d4);
+    display: flex;
+    font-size: 0.75rem;
+    gap: 0.5rem;
+    justify-content: space-between;
+    padding: 0.375rem 0.75rem;
+  }
+
+  .notice-dismiss {
+    background: none;
+    border: none;
+    color: var(--graphiql-muted, #858585);
+    cursor: pointer;
+    font-size: 0.875rem;
+    line-height: 1;
+    padding: 0 0.25rem;
+
+    &:hover {
+      color: var(--graphiql-fg, #d4d4d4);
+    }
+  }
+
   .list {
     display: grid;
     gap: 0.125rem;
@@ -147,10 +213,38 @@
 </style>
 
 <div class="panel">
-  <div class="header">
-    <span class="title">History</span>
-    {#if entries.length > 0}
-      <button class="clear" onclick={onClear} type="button">Clear</button>
+  <div>
+    <div class="header">
+      <span class="title">History</span>
+      <div class="actions">
+        {#if onExport}
+          <button class="action" onclick={onExport} type="button">Export</button>
+        {/if}
+        {#if onImport}
+          <button class="action" onclick={onImportClick} type="button">Import</button>
+          <input
+            accept="application/json"
+            bind:this={fileInput}
+            class="hidden-input"
+            onchange={onFileChange}
+            type="file"/>
+        {/if}
+        {#if entries.length > 0}
+          <button class="action" onclick={onClear} type="button">Clear</button>
+        {/if}
+      </div>
+    </div>
+    {#if notice}
+      <div class="notice">
+        <span>{notice}</span>
+        {#if onDismissNotice}
+          <button
+            aria-label="Dismiss notice"
+            class="notice-dismiss"
+            onclick={onDismissNotice}
+            type="button">×</button>
+        {/if}
+      </div>
     {/if}
   </div>
   <div class="list">
-- 
cgit v1.2.3

