Skip to content

Commit 2c31276

Browse files
committed
.
1 parent a389f43 commit 2c31276

File tree

6 files changed

+100
-28
lines changed

6 files changed

+100
-28
lines changed

tab-extension/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ A Chrome extension for managing browser tabs and windows, inspired by Cluster Ta
99
- **Tab Management**:
1010
- Select multiple tabs with checkboxes
1111
- Close selected tabs in bulk
12+
- Move selected tabs to another window in bulk
1213
- Close individual tabs
1314
- **Rich Tab Information**:
1415
- Full URL path (not just domain)

tab-extension/icons/icon128.svg

Lines changed: 8 additions & 5 deletions
Loading

tab-extension/icons/icon16.svg

Lines changed: 8 additions & 5 deletions
Loading

tab-extension/icons/icon48.svg

Lines changed: 8 additions & 5 deletions
Loading

tab-extension/manager.js

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ function createWindowCard(window) {
9595
<option value="title">Sort by Title</option>
9696
<option value="domain">Sort by Domain</option>
9797
</select>
98+
<select class="move-to-dropdown" data-window-id="${window.id}" disabled>
99+
<option value="">Move to...</option>
100+
</select>
98101
<button class="btn btn-danger btn-close-selected" data-window-id="${window.id}" disabled>
99102
Close Selected
100103
</button>
@@ -142,15 +145,9 @@ function createTabRow(tab, windowId) {
142145
// Escape the favicon URL for use in HTML attribute
143146
const escapedFavicon = favicon.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
144147

145-
// Format metadata
148+
// Format metadata (only icons, no time)
146149
let metaHtml = '<div class="tab-meta">';
147150

148-
// Last accessed time (if available)
149-
if (tab.lastAccessed) {
150-
const timeAgo = getTimeAgo(tab.lastAccessed);
151-
metaHtml += `<span class="tab-meta-item" title="Last accessed">🕐 ${timeAgo}</span>`;
152-
}
153-
154151
// Audible indicator
155152
if (tab.audible) {
156153
metaHtml += `<span class="tab-meta-item" title="Playing audio">🔊</span>`;
@@ -163,6 +160,13 @@ function createTabRow(tab, windowId) {
163160

164161
metaHtml += '</div>';
165162

163+
// Time ago for top right
164+
let timeAgoHtml = '';
165+
if (tab.lastAccessed) {
166+
const timeAgo = getTimeAgo(tab.lastAccessed);
167+
timeAgoHtml = `<span class="tab-time" title="Last accessed">${timeAgo}</span>`;
168+
}
169+
166170
return `
167171
<div class="tab-row" data-tab-id="${tab.id}" data-window-id="${windowId}" draggable="true">
168172
<input type="checkbox" class="tab-checkbox" data-tab-id="${tab.id}">
@@ -172,6 +176,7 @@ function createTabRow(tab, windowId) {
172176
<div class="tab-url">${escapeHtml(urlPath)}</div>
173177
${metaHtml}
174178
</div>
179+
${timeAgoHtml}
175180
<div class="tab-actions">
176181
<button class="tab-action-btn" data-action="close" data-tab-id="${tab.id}">✕</button>
177182
</div>
@@ -209,19 +214,39 @@ function setupWindowCardListeners(card, window) {
209214
sortTabs(windowId, e.target.value);
210215
});
211216

217+
// Move to dropdown - populate with other windows
218+
const moveToDropdown = card.querySelector('.move-to-dropdown');
219+
allWindows.forEach(w => {
220+
if (w.id !== windowId) {
221+
const option = document.createElement('option');
222+
option.value = w.id;
223+
option.textContent = w.id === currentWindowId ? 'Current Window' : `Window ${w.id}`;
224+
moveToDropdown.appendChild(option);
225+
}
226+
});
227+
228+
moveToDropdown.addEventListener('change', (e) => {
229+
if (e.target.value) {
230+
moveSelectedTabs(windowId, parseInt(e.target.value));
231+
e.target.value = ''; // Reset dropdown
232+
}
233+
});
234+
212235
// Select all checkbox
213236
const selectAllCheckbox = card.querySelector('.select-all-checkbox');
214237
selectAllCheckbox.addEventListener('change', (e) => {
215238
const checkboxes = card.querySelectorAll('.tab-checkbox');
216239
checkboxes.forEach(cb => cb.checked = e.target.checked);
217240
updateCloseButton(windowId);
241+
updateMoveToButton(windowId);
218242
});
219243

220244
// Tab checkboxes
221245
const tabCheckboxes = card.querySelectorAll('.tab-checkbox');
222246
tabCheckboxes.forEach(checkbox => {
223247
checkbox.addEventListener('change', () => {
224248
updateCloseButton(windowId);
249+
updateMoveToButton(windowId);
225250
updateSelectAllCheckbox(windowId);
226251
});
227252
});
@@ -277,6 +302,14 @@ function updateCloseButton(windowId) {
277302
closeButton.disabled = checkboxes.length === 0;
278303
}
279304

305+
// Update move to button state
306+
function updateMoveToButton(windowId) {
307+
const card = document.querySelector(`[data-window-id="${windowId}"]`);
308+
const checkboxes = card.querySelectorAll('.tab-checkbox:checked');
309+
const moveToDropdown = card.querySelector('.move-to-dropdown');
310+
moveToDropdown.disabled = checkboxes.length === 0;
311+
}
312+
280313
// Update select all checkbox state
281314
function updateSelectAllCheckbox(windowId) {
282315
const card = document.querySelector(`[data-window-id="${windowId}"]`);
@@ -300,6 +333,20 @@ async function closeSelectedTabs(windowId) {
300333
}
301334
}
302335

336+
// Move selected tabs to another window
337+
async function moveSelectedTabs(fromWindowId, toWindowId) {
338+
const card = document.querySelector(`[data-window-id="${fromWindowId}"]`);
339+
const checkedCheckboxes = card.querySelectorAll('.tab-checkbox:checked');
340+
const tabIds = Array.from(checkedCheckboxes).map(cb => parseInt(cb.dataset.tabId));
341+
342+
if (tabIds.length > 0) {
343+
for (const tabId of tabIds) {
344+
await chrome.tabs.move(tabId, { windowId: toWindowId, index: -1 });
345+
}
346+
setTimeout(loadWindows, 100);
347+
}
348+
}
349+
303350
// Extract top-level domain from hostname
304351
function getTopLevelDomain(url) {
305352
try {

tab-extension/styles.css

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,12 @@ header {
179179

180180
.tab-row {
181181
display: flex;
182-
align-items: center;
183-
padding: 8px 14px;
182+
align-items: flex-start;
183+
padding: 6px 14px;
184184
border-bottom: 1px solid #f5f5f5;
185185
transition: background-color 0.15s;
186186
cursor: move;
187+
position: relative;
187188
}
188189

189190
.tab-row:hover {
@@ -200,6 +201,7 @@ header {
200201

201202
.tab-checkbox {
202203
margin-right: 10px;
204+
margin-top: 2px;
203205
cursor: pointer;
204206
width: 14px;
205207
height: 14px;
@@ -209,6 +211,7 @@ header {
209211
width: 16px;
210212
height: 16px;
211213
margin-right: 10px;
214+
margin-top: 1px;
212215
flex-shrink: 0;
213216
}
214217

@@ -240,23 +243,35 @@ header {
240243

241244
.tab-meta {
242245
display: flex;
243-
gap: 8px;
244-
font-size: 10px;
246+
gap: 6px;
247+
font-size: 11px;
245248
color: #aaa;
246-
margin-top: 2px;
249+
margin-top: 1px;
247250
}
248251

249252
.tab-meta-item {
250253
display: flex;
251254
align-items: center;
252-
gap: 3px;
255+
gap: 2px;
256+
}
257+
258+
.tab-time {
259+
position: absolute;
260+
top: 6px;
261+
right: 40px;
262+
font-size: 10px;
263+
color: #aaa;
264+
white-space: nowrap;
253265
}
254266

255267
.tab-actions {
256268
display: flex;
257269
gap: 4px;
258270
opacity: 0;
259271
transition: opacity 0.2s;
272+
margin-left: auto;
273+
padding-left: 8px;
274+
margin-top: 2px;
260275
}
261276

262277
.tab-row:hover .tab-actions {

0 commit comments

Comments
 (0)