Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions desktop-app/resources/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ <h2 class="h5 m-0">Menu</h2>
<!-- Tab Bar -->
<div class="tab-bar" id="tab-bar">
<div class="tab-list" id="tab-list" role="tablist" aria-label="Document tabs"></div>
<button class="tab-new-btn" id="tab-new-btn" title="New Tab (Ctrl+T)" aria-label="Open new tab">
<i class="bi bi-plus-lg"></i> New Tab
</button>
<button class="tab-reset-btn" id="tab-reset-btn" title="Reset all files" aria-label="Reset all files">
<i class="bi bi-arrow-counterclockwise"></i> Reset
</button>
Expand Down
110 changes: 93 additions & 17 deletions desktop-app/resources/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -1151,23 +1151,6 @@ document.addEventListener("DOMContentLoaded", function () {
if (tabId) switchTab(tabId);
};

// "+ Create" button at end of tab list (placed outside tabList to prevent ARIA child violation)
let newBtn = tabList.parentElement.querySelector('.tab-new-btn');
if (!newBtn) {
newBtn = document.createElement('button');
newBtn.className = 'tab-new-btn';
newBtn.title = 'New Tab (Ctrl+T)';
newBtn.setAttribute('aria-label', 'Open new tab');
newBtn.innerHTML = '<i class="bi bi-plus-lg"></i>';
newBtn.addEventListener('click', function() { newTab(); });

const resetBtn = document.getElementById('tab-reset-btn');
if (resetBtn) {
tabList.parentElement.insertBefore(newBtn, resetBtn);
} else {
tabList.parentElement.appendChild(newBtn);
}
}

// Auto-scroll active tab into view (paint-aligned to prevent forced reflows)
const activeItem = tabList.querySelector('.tab-item.active');
Expand Down Expand Up @@ -1224,6 +1207,91 @@ document.addEventListener("DOMContentLoaded", function () {
};

renderMobileTabList(tabsArr, currentActiveTabId);
if (typeof tabList.dispatchEvent === 'function') {
tabList.dispatchEvent(new Event('scroll'));
}
}

// ========================================
// TAB OVERFLOW — Scroll Buttons, Wheel, Indicators
// ========================================

var _tabOverflowInitialized = false;

function setupTabOverflow() {
if (_tabOverflowInitialized) return;
_tabOverflowInitialized = true;

var tabBar = document.getElementById('tab-bar');
var tabList = document.getElementById('tab-list');
if (!tabBar || !tabList) return;

// --- Create scroll arrow buttons ---
var scrollLeftBtn = document.createElement('button');
scrollLeftBtn.className = 'tab-scroll-btn tab-scroll-left';
scrollLeftBtn.setAttribute('aria-label', 'Scroll tabs left');
scrollLeftBtn.title = 'Scroll left';
scrollLeftBtn.innerHTML = '<i class="bi bi-chevron-left"></i>';
scrollLeftBtn.addEventListener('click', function() {
tabList.scrollBy({ left: -200, behavior: 'smooth' });
});

var scrollRightBtn = document.createElement('button');
scrollRightBtn.className = 'tab-scroll-btn tab-scroll-right';
scrollRightBtn.setAttribute('aria-label', 'Scroll tabs right');
scrollRightBtn.title = 'Scroll right';
scrollRightBtn.innerHTML = '<i class="bi bi-chevron-right"></i>';
scrollRightBtn.addEventListener('click', function() {
tabList.scrollBy({ left: 200, behavior: 'smooth' });
});

// Insert scroll buttons flanking the tab-list
tabBar.insertBefore(scrollLeftBtn, tabList);
var newBtn = document.getElementById('tab-new-btn');
if (newBtn) {
tabBar.insertBefore(scrollRightBtn, newBtn);
} else {
var resetBtn = document.getElementById('tab-reset-btn');
if (resetBtn) {
tabBar.insertBefore(scrollRightBtn, resetBtn);
} else {
tabBar.appendChild(scrollRightBtn);
}
}

// --- Overflow detection ---
var _overflowRafId = null;
function updateOverflowState() {
if (_overflowRafId) return;
_overflowRafId = requestAnimationFrame(function() {
_overflowRafId = null;
var hasLeft = tabList.scrollLeft > 1;
var hasRight = tabList.scrollLeft < (tabList.scrollWidth - tabList.clientWidth - 1);
tabBar.classList.toggle('has-overflow-left', hasLeft);
tabBar.classList.toggle('has-overflow-right', hasRight);
});
}

tabList.addEventListener('scroll', updateOverflowState);

// Use ResizeObserver to detect when overflow state changes due to window resize
if (typeof ResizeObserver !== 'undefined') {
var resizeObs = new ResizeObserver(updateOverflowState);
resizeObs.observe(tabList);
}

// Initial check
updateOverflowState();

// --- Mouse wheel scroll: vertical wheel → horizontal scroll ---
tabList.addEventListener('wheel', function(e) {
// Only intercept vertical wheel (don't fight native horizontal wheel/trackpad)
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
e.preventDefault();
tabList.scrollLeft += e.deltaY;
updateOverflowState();
}
}, { passive: false });
}

function renderMobileTabList(tabsArr, currentActiveTabId) {
Expand Down Expand Up @@ -1510,6 +1578,14 @@ document.addEventListener("DOMContentLoaded", function () {
markdownEditor.scrollTop = activeTab.scrollPos || 0;
});
renderTabBar(tabs, activeTabId);
setupTabOverflow();

const staticNewBtn = document.getElementById('tab-new-btn');
if (staticNewBtn) {
staticNewBtn.onclick = function() {
newTab();
};
}
}

// Late-load callback hook for Neutralino command-line files
Expand Down
107 changes: 98 additions & 9 deletions desktop-app/resources/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1831,23 +1831,29 @@ a:focus {
.tab-new-btn {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
gap: 4px;
height: 24px;
padding: 0 8px;
border-radius: 5px;
background: none;
border: 1px solid transparent;
border: 1px solid var(--border-color);
color: var(--text-color);
cursor: pointer;
font-size: 16px;
font-size: 12px;
flex-shrink: 0;
margin-left: 4px;
transition: background-color 0.15s ease, border-color 0.15s ease;
margin-left: 6px;
align-self: center;
transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.tab-new-btn:hover {
background-color: var(--button-hover);
border-color: var(--border-color);
background-color: rgba(46, 160, 67, 0.1);
border-color: var(--accent-color, #2ea043);
color: var(--accent-color, #2ea043);
}

.tab-new-btn:active {
background-color: rgba(46, 160, 67, 0.2);
}

/* Drag-and-drop visual feedback */
Expand Down Expand Up @@ -1880,6 +1886,83 @@ a:focus {
}
}

/* ========================================
TAB OVERFLOW — Scroll Buttons & Fade Indicators
======================================== */

.tab-scroll-btn {
display: none;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 4px;
background: none;
border: 1px solid transparent;
color: var(--text-color);
cursor: pointer;
font-size: 14px;
flex-shrink: 0;
padding: 0;
transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
z-index: 2;
opacity: 0.6;
}

.tab-scroll-btn:hover {
background-color: var(--button-hover);
border-color: var(--border-color);
opacity: 1;
}

.tab-scroll-btn:active {
background-color: var(--button-active);
}

/* Show scroll buttons only when overflow exists */
.tab-bar.has-overflow-left .tab-scroll-left,
.tab-bar.has-overflow-right .tab-scroll-right {
display: flex;
}

/* Overflow fade indicators — subtle gradient at clipped edges */
.tab-list::before,
.tab-list::after {
content: '';
position: sticky;
top: 0;
bottom: 0;
width: 0;
flex-shrink: 0;
pointer-events: none;
z-index: 3;
transition: box-shadow 0.2s ease;
}

.tab-list::before {
left: 0;
}

.tab-list::after {
right: 0;
}

.tab-bar.has-overflow-left .tab-list::before {
box-shadow: 8px 0 12px -4px rgba(0, 0, 0, 0.12);
}

.tab-bar.has-overflow-right .tab-list::after {
box-shadow: -8px 0 12px -4px rgba(0, 0, 0, 0.12);
}

[data-theme="dark"] .tab-bar.has-overflow-left .tab-list::before {
box-shadow: 8px 0 12px -4px rgba(0, 0, 0, 0.35);
}

[data-theme="dark"] .tab-bar.has-overflow-right .tab-list::after {
box-shadow: -8px 0 12px -4px rgba(0, 0, 0, 0.35);
}

/* ========================================
THREE-DOT TAB MENU
======================================== */
Expand Down Expand Up @@ -1927,7 +2010,13 @@ a:focus {
padding: 0 10px 0 12px !important;
gap: 8px !important;
}
.tab-new-btn {
.tab-new-btn,
.tab-reset-btn {
height: 32px !important;
font-size: 14px !important;
padding: 0 12px !important;
}
.tab-scroll-btn {
width: 32px !important;
height: 32px !important;
font-size: 18px !important;
Expand Down
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,9 @@ <h2 class="h5 m-0">Menu</h2>
<!-- Tab Bar -->
<div class="tab-bar" id="tab-bar">
<div class="tab-list" id="tab-list" role="tablist" aria-label="Document tabs"></div>
<button class="tab-new-btn" id="tab-new-btn" title="New Tab (Ctrl+T)" aria-label="Open new tab">
<i class="bi bi-plus-lg"></i> New Tab
</button>
Comment on lines 298 to +302
<button class="tab-reset-btn" id="tab-reset-btn" title="Reset all files" aria-label="Reset all files">
<i class="bi bi-arrow-counterclockwise"></i> Reset
</button>
Expand Down
Loading
Loading