davanstrien HF Staff Claude commited on
Commit
69abcd1
·
1 Parent(s): fae80e2

Add deep link sharing functionality

Browse files

- Read URL parameters on page load (dataset, index, view, diff, markdown)
- Auto-update URL when navigating or changing settings
- Add Copy Link button with success notification
- Support fallback clipboard API for older browsers
- Enable sharing specific views with full state preservation

Users can now share direct links to specific pages and views,
making collaboration and reference sharing much easier.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>

Files changed (2) hide show
  1. index.html +27 -0
  2. js/app.js +102 -10
index.html CHANGED
@@ -144,6 +144,33 @@
144
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
145
  </svg>
146
  </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  </div>
148
  </div>
149
  </div>
 
144
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
145
  </svg>
146
  </button>
147
+
148
+ <!-- Copy Link Button -->
149
+ <div class="relative">
150
+ <button
151
+ @click="copyShareLink()"
152
+ class="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
153
+ title="Copy shareable link"
154
+ >
155
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
156
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
157
+ </svg>
158
+ </button>
159
+
160
+ <!-- Success Toast -->
161
+ <div
162
+ x-show="showShareSuccess"
163
+ x-transition
164
+ class="absolute right-0 top-12 bg-green-600 text-white px-3 py-2 rounded-md shadow-lg whitespace-nowrap z-50"
165
+ >
166
+ <div class="flex items-center gap-2">
167
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
168
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
169
+ </svg>
170
+ <span class="text-sm">Link copied!</span>
171
+ </div>
172
+ </div>
173
+ </div>
174
  </div>
175
  </div>
176
  </div>
js/app.js CHANGED
@@ -33,6 +33,7 @@ document.addEventListener('alpine:init', () => {
33
  showDock: false,
34
  renderMarkdown: false,
35
  hasMarkdown: false,
 
36
 
37
  // Reasoning trace state
38
  hasReasoningTrace: false,
@@ -76,6 +77,31 @@ document.addEventListener('alpine:init', () => {
76
  // Initialize API
77
  this.api = new DatasetAPI();
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  // Apply dark mode from localStorage
80
  this.darkMode = localStorage.getItem('darkMode') === 'true';
81
  this.$watch('darkMode', value => {
@@ -87,8 +113,19 @@ document.addEventListener('alpine:init', () => {
87
  // Setup keyboard navigation
88
  this.setupKeyboardNavigation();
89
 
 
 
 
90
  // Load initial dataset
91
  await this.loadDataset();
 
 
 
 
 
 
 
 
92
  },
93
 
94
  setupKeyboardNavigation() {
@@ -208,10 +245,7 @@ document.addEventListener('alpine:init', () => {
208
  this.updateDiff();
209
 
210
  // Update URL without triggering navigation
211
- const url = new URL(window.location);
212
- url.searchParams.set('dataset', this.datasetId);
213
- url.searchParams.set('index', index);
214
- window.history.replaceState({}, '', url);
215
 
216
  } catch (error) {
217
  this.error = `Failed to load sample: ${error.message}`;
@@ -811,15 +845,73 @@ document.addEventListener('alpine:init', () => {
811
  await this.loadSample(index);
812
  },
813
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
814
  // Watch for diff mode changes
815
  initWatchers() {
816
- this.$watch('diffMode', () => this.updateDiff());
 
 
 
817
  this.$watch('currentSample', () => this.updateDiff());
 
 
818
  }
819
  }));
820
- });
821
-
822
- // Initialize watchers after Alpine loads
823
- document.addEventListener('alpine:initialized', () => {
824
- Alpine.store('ocrExplorer')?.initWatchers?.();
825
  });
 
33
  showDock: false,
34
  renderMarkdown: false,
35
  hasMarkdown: false,
36
+ showShareSuccess: false,
37
 
38
  // Reasoning trace state
39
  hasReasoningTrace: false,
 
77
  // Initialize API
78
  this.api = new DatasetAPI();
79
 
80
+ // Read URL parameters for deep linking
81
+ const urlParams = new URLSearchParams(window.location.search);
82
+ const urlDataset = urlParams.get('dataset');
83
+ const urlIndex = urlParams.get('index');
84
+ const urlView = urlParams.get('view');
85
+ const urlDiff = urlParams.get('diff');
86
+ const urlMarkdown = urlParams.get('markdown');
87
+
88
+ // Apply URL parameters if present
89
+ if (urlDataset) {
90
+ this.datasetId = urlDataset;
91
+ }
92
+
93
+ if (urlView && ['comparison', 'diff', 'improved'].includes(urlView)) {
94
+ this.activeTab = urlView;
95
+ }
96
+
97
+ if (urlDiff && ['char', 'word', 'line', 'markdown'].includes(urlDiff)) {
98
+ this.diffMode = urlDiff;
99
+ }
100
+
101
+ if (urlMarkdown !== null) {
102
+ this.renderMarkdown = urlMarkdown === 'true';
103
+ }
104
+
105
  // Apply dark mode from localStorage
106
  this.darkMode = localStorage.getItem('darkMode') === 'true';
107
  this.$watch('darkMode', value => {
 
113
  // Setup keyboard navigation
114
  this.setupKeyboardNavigation();
115
 
116
+ // Setup watchers for URL updates
117
+ this.initWatchers();
118
+
119
  // Load initial dataset
120
  await this.loadDataset();
121
+
122
+ // Jump to specific index if provided in URL
123
+ if (urlIndex !== null) {
124
+ const index = parseInt(urlIndex);
125
+ if (!isNaN(index) && index >= 0 && index < this.totalSamples) {
126
+ await this.loadSample(index);
127
+ }
128
+ }
129
  },
130
 
131
  setupKeyboardNavigation() {
 
245
  this.updateDiff();
246
 
247
  // Update URL without triggering navigation
248
+ this.updateURL();
 
 
 
249
 
250
  } catch (error) {
251
  this.error = `Failed to load sample: ${error.message}`;
 
845
  await this.loadSample(index);
846
  },
847
 
848
+ // Update URL with current state
849
+ updateURL() {
850
+ const url = new URL(window.location);
851
+ url.searchParams.set('dataset', this.datasetId);
852
+ url.searchParams.set('index', this.currentIndex);
853
+ url.searchParams.set('view', this.activeTab);
854
+ url.searchParams.set('diff', this.diffMode);
855
+ url.searchParams.set('markdown', this.renderMarkdown);
856
+ window.history.replaceState({}, '', url);
857
+ },
858
+
859
+ // Copy shareable link to clipboard
860
+ async copyShareLink() {
861
+ const url = new URL(window.location);
862
+ url.searchParams.set('dataset', this.datasetId);
863
+ url.searchParams.set('index', this.currentIndex);
864
+ url.searchParams.set('view', this.activeTab);
865
+ url.searchParams.set('diff', this.diffMode);
866
+ url.searchParams.set('markdown', this.renderMarkdown);
867
+
868
+ const shareUrl = url.toString();
869
+
870
+ try {
871
+ await navigator.clipboard.writeText(shareUrl);
872
+
873
+ // Show success feedback
874
+ this.showShareSuccess = true;
875
+ setTimeout(() => {
876
+ this.showShareSuccess = false;
877
+ }, 2000);
878
+
879
+ return true;
880
+ } catch (err) {
881
+ // Fallback for older browsers
882
+ const textArea = document.createElement('textarea');
883
+ textArea.value = shareUrl;
884
+ textArea.style.position = 'fixed';
885
+ textArea.style.opacity = '0';
886
+ document.body.appendChild(textArea);
887
+ textArea.select();
888
+
889
+ try {
890
+ document.execCommand('copy');
891
+ // Show success feedback
892
+ this.showShareSuccess = true;
893
+ setTimeout(() => {
894
+ this.showShareSuccess = false;
895
+ }, 2000);
896
+ return true;
897
+ } catch (err) {
898
+ console.error('Failed to copy link:', err);
899
+ return false;
900
+ } finally {
901
+ document.body.removeChild(textArea);
902
+ }
903
+ }
904
+ },
905
+
906
  // Watch for diff mode changes
907
  initWatchers() {
908
+ this.$watch('diffMode', () => {
909
+ this.updateDiff();
910
+ this.updateURL();
911
+ });
912
  this.$watch('currentSample', () => this.updateDiff());
913
+ this.$watch('activeTab', () => this.updateURL());
914
+ this.$watch('renderMarkdown', () => this.updateURL());
915
  }
916
  }));
 
 
 
 
 
917
  });