summary history files

commit:01e1310b0102fb1023e84526344a7d2c56c3a65e
date:Tue Mar 10 08:20:34 2026 +1100
parents:04fc9410e504986c01a79036c6c8e9613eea64ff
timezones: added url encoding support
diff --git a/timezones/index.html b/timezones/index.html
line changes: +119/-1
index 50c403e..844fe73
--- a/timezones/index.html
+++ b/timezones/index.html
@@ -307,6 +307,17 @@ tr.selected td {
   line-height: 1.4;
   color: var(--text-color);
 }
+.url-display {
+  margin-top: 10px;
+  padding: 10px;
+  background: var(--accent-bg);
+  border: 1px solid var(--border-color);
+  border-radius: 4px;
+  font-family: 'Courier New', Consolas, monospace;
+  font-size: 0.8rem;
+  word-break: break-all;
+  color: var(--text-color);
+}
 @media (max-width: 768px) {
   .timezone-row {
     flex-direction: column;
@@ -357,6 +368,10 @@ tr.selected td {
       <input type="checkbox" id="includeUtc"> Include UTC
     </label>
   </div>
+  <div class="actions" style="margin-top: 10px;">
+    <button onclick="generateShareableUrl()">Generate Shareable URL</button>
+    <div id="urlDisplay" class="url-display hidden"></div>
+  </div>
 </div>
 
 <div id="output" class="hidden">
@@ -403,6 +418,26 @@ let debounceTimer = null;
 
 const STORAGE_KEY = 'tzplanner_state';
 
+function encodeBase64(str) {
+  try {
+    return btoa(encodeURIComponent(str));
+  } catch (e) {
+    return btoa(str);
+  }
+}
+
+function decodeBase64(str) {
+  try {
+    return decodeURIComponent(atob(str));
+  } catch (e) {
+    try {
+      return atob(str);
+    } catch (e2) {
+      return null;
+    }
+  }
+}
+
 function debounce(func, wait) {
   return function executedFunction(...args) {
     const later = () => {
@@ -460,6 +495,41 @@ function loadState() {
   }
 }
 
+function loadFromUrl() {
+  const params = new URLSearchParams(window.location.search);
+  const dataParam = params.get('d');
+
+  if (!dataParam) return false;
+
+  try {
+    const decoded = decodeBase64(dataParam);
+    if (!decoded) return false;
+
+    const data = JSON.parse(decoded);
+    if (!data.zones || !Array.isArray(data.zones) || data.zones.length < minZones) return false;
+
+    document.getElementById('inputs').innerHTML = '';
+    timezoneCount = 0;
+
+    data.zones.forEach(zone => {
+      addTimezone(zone.name || '', zone.timezone || '');
+    });
+
+    document.getElementById('includeUtc').checked = data.utc || false;
+
+    if (data.selected) {
+      selectedHourIndices = new Set(data.selected);
+    }
+
+    setTimeout(() => generateTable(true), 100);
+
+    return true;
+  } catch (e) {
+    console.error('Failed to load from URL:', e);
+    return false;
+  }
+}
+
 function showError(msg) {
   const errorEl = document.getElementById('errorContainer');
   errorEl.textContent = msg;
@@ -675,6 +745,7 @@ function clearAll() {
   document.getElementById('inputs').innerHTML = '';
   timezoneCount = 0;
   document.getElementById('includeUtc').checked = false;
+  document.getElementById('urlDisplay').classList.add('hidden');
   localStorage.removeItem(STORAGE_KEY);
   clearTable();
   const browserTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -932,6 +1003,53 @@ function copyToClipboard() {
   });
 }
 
+function generateShareableUrl() {
+  const rows = document.querySelectorAll('.timezone-row');
+  const zones = [];
+
+  for (const row of rows) {
+    const nameInput = row.querySelector('.name-input');
+    const tzInput = row.querySelector('.tz-input');
+    const tz = tzInput.dataset.value || tzInput.value;
+    if (tz) {
+      zones.push({
+        name: nameInput.value,
+        timezone: tz
+      });
+    }
+  }
+
+  if (zones.length < minZones) {
+    showError(`Please add at least ${minZones} timezones first`);
+    return;
+  }
+
+  const data = {
+    zones: zones,
+    utc: document.getElementById('includeUtc').checked
+  };
+
+  if (selectedHourIndices.size > 0) {
+    data.selected = Array.from(selectedHourIndices);
+  }
+
+  const jsonStr = JSON.stringify(data);
+  const encoded = encodeBase64(jsonStr);
+
+  const url = `${window.location.origin}${window.location.pathname}?d=${encoded}`;
+
+  const urlDisplay = document.getElementById('urlDisplay');
+  urlDisplay.textContent = url;
+  urlDisplay.classList.remove('hidden');
+
+  navigator.clipboard.writeText(url).then(() => {
+    showError('URL copied to clipboard!');
+    setTimeout(clearError, 3000);
+  }).catch(() => {
+    showError('URL generated (copy manually)');
+  });
+}
+
 document.getElementById('includeUtc').addEventListener('change', debouncedAutoGenerate);
 
 document.addEventListener('keydown', (e) => {
@@ -944,7 +1062,7 @@ document.addEventListener('keydown', (e) => {
 document.getElementById('loadingIndicator').classList.remove('hidden');
 setTimeout(() => {
   document.getElementById('loadingIndicator').classList.add('hidden');
-  if (!loadState()) {
+  if (!loadFromUrl() && !loadState()) {
     const browserTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
     for (let i = 0; i < minZones; i++) {
       if (i === 0 && browserTz && timezones.includes(browserTz)) {

diff --git a/timezones/plan.md b/timezones/plan.md
line changes: +11/-0
index 3041974..2060131
--- a/timezones/plan.md
+++ b/timezones/plan.md
@@ -3,6 +3,11 @@
 ## Goal
 Develop a single-file, client-side web application that generates an interactive timezone comparison table to help distributed teams find optimal meeting times across 2-5 global locations, with exportable markdown formatting for easy sharing.
 
+Users can generate a URL which includes the timezones they have added
+and the names they have assigned to each timezone. This link can be
+shared and viewed by anyone on the internet.
+
+
 ## Requirements
 
 ### MUST:
@@ -15,6 +20,11 @@ Develop a single-file, client-side web application that generates an interactive
 - The button text in Generate Table should not use grey on blue which is very hard to read
 - Support selecting multiple hours in the table, not just a single hour:
   - This should be done through holding the select key and selecting the rows
+- Support a URL generate optional button which creates a URL with the
+  timezones and the names assigned to each timezone:
+  - The path of the URL should be encoded so it's not long like
+    "/?zones=%5B%7B%22name%22%3A%22foo%22%2C%22timezone%22%3A%22Australia%2FMelbourne%22%7D%2C%7B%22name%22%3A%22abc%22%2C%22timezone%22%3A%22America%2FNew_York%22%7D%2C%7B%22name%22%3A%22mma%22%2C%22timezone%22%3A%22Asia%2FAmman%22%7D%5D&utc=false&selected=13%2C14%2C15%2C16%2C17".
+    Instead it should be encoded with an encoding scheme such as base64.
 
 ### SHOULD:
 - Support custom names for timezones
@@ -60,6 +70,7 @@ Develop a single-file, client-side web application that generates an interactive
 - [x] Dark mode support (media query based with CSS variables)
 - [x] Handle browser timezone detection as default first entry
 - [x] Fix dropdown positioning on mobile screens (<400px width)
+- [ ] Encode generated URL
 
 ### Phase 3: Advanced Features (Future)
 **Objective**: Extend functionality for power users.