Skip to content

feat: Multi-tab workspace with localStorage session persistence#40

Merged
ThisIs-Developer merged 2 commits intomainfrom
copilot/add-document-tabs-session-management
Mar 7, 2026
Merged

feat: Multi-tab workspace with localStorage session persistence#40
ThisIs-Developer merged 2 commits intomainfrom
copilot/add-document-tabs-session-management

Conversation

Copy link
Contributor

Copilot AI commented Mar 7, 2026

Adds a VS Code–style tabbed interface to the Markdown Viewer so users can work across multiple documents simultaneously, with full session restore on reload via localStorage.

Tab bar UI

Data model

Each tab persists { id, title, content, scrollPos, viewMode, createdAt } under markdownViewerTabs in localStorage; active tab ID is stored separately under markdownViewerActiveTab.

index.html

  • Tab bar (#tab-bar) inserted between <header> and the dropzone — a scrollable #tab-list plus a + new-tab button.

styles.css

  • Full tab bar style block: inactive/active/hover states, close-button visibility, slide-in animation, horizontal overflow scroll, drag-over highlight, dark mode via existing CSS variables, hidden on ≤480px.

script.jsTabManager module

  • initTabs() — replaces the bare renderMarkdown() init call; restores tabs, active tab content, view mode, and scroll position from localStorage. Falls back to sampleMarkdown on first run.
  • switchTab(tabId) — flushes current tab state before loading the target tab's content, view mode, and scroll position.
  • newTab(content?, title?) — capped at 20 tabs; called by the + button, Ctrl/Cmd+T, and on file import (opens in a new tab instead of overwriting the current one).
  • closeTab(tabId)Ctrl/Cmd+W or the × button; last tab is protected.
  • renderTabBar() — full re-render with HTML5 drag-and-drop reordering; active tab auto-scrolled into view.
  • saveCurrentTabState() — debounced 500 ms on editor input; immediate on view mode change.
  • updateActiveTabTitle() — debounced 800 ms; derives title from first H1 → first non-empty line (≤30 chars) → 'Untitled'; skips write if unchanged.
  • restoreViewMode(mode) — extracted helper that resets currentViewMode to null before calling setViewMode() to force re-application regardless of previous state.

Import integration

// Before: overwrote the current tab
markdownEditor.value = e.target.result;
renderMarkdown();

// After: opens the file in a new tab
newTab(e.target.result, file.name.replace(/\.md$/i, ''));

Share-URL integration

loadFromShareHash() now calls saveCurrentTabState() + updateActiveTabTitle() after decoding, so the shared content is persisted into the active tab's slot.

Original prompt

Feature: Document Tabs & Session Management

Implement a full multi-tab workspace system in the Markdown Viewer application. Users currently can only work with a single document at a time. This feature adds a VS Code–style tabbed interface with complete localStorage-based session persistence so nothing is lost on refresh.


Files to Modify

  • index.html — Add the tab bar HTML structure (between <header> and the dropzone <div>)
  • styles.css — Add all tab bar styles, dark mode support, responsive behaviour, and animations
  • script.js — Add the entire TabManager module and wire it into the existing app logic

Detailed Requirements

1. Data Model (script.js)

Maintain a tabs array in memory and sync it to localStorage under the key markdownViewerTabs. Each tab object has:

{
  id: String,           // unique ID e.g. 'tab_<timestamp>_<random>'
  title: String,        // derived from first H1 heading, or filename, or 'Untitled'
  content: String,      // full markdown text
  scrollPos: Number,    // editor scroll position (integer pixels)
  viewMode: String,     // 'split' | 'editor' | 'preview'
  createdAt: Number     // Date.now()
}

A activeTabId string is stored separately in localStorage as markdownViewerActiveTab.


2. Tab Bar HTML (insert into index.html)

Insert this block directly above the <div id="dropzone" ...> element and below the closing </header>:

<!-- Tab Bar -->
<div class="tab-bar" id="tab-bar" role="tablist" aria-label="Document tabs">
  <div class="tab-list" id="tab-list"></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>
  </button>
</div>

3. Tab Bar CSS (append to styles.css)

Add a clearly delimited section at the bottom of styles.css:

/* ========================================
   DOCUMENT TABS & SESSION MANAGEMENT
   ======================================== */

.tab-bar {
  display: flex;
  align-items: center;
  background-color: var(--header-bg);
  border-bottom: 1px solid var(--border-color);
  height: 36px;
  overflow: hidden;
  flex-shrink: 0;
  padding: 0 4px;
  gap: 0;
  user-select: none;
}

.tab-list {
  display: flex;
  align-items: flex-end;
  overflow-x: auto;
  overflow-y: hidden;
  flex: 1;
  height: 100%;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none;
}

.tab-list::-webkit-scrollbar {
  display: none;
}

.tab-item {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 10px 0 12px;
  min-width: 100px;
  max-width: 180px;
  background-color: var(--button-bg);
  border: 1px solid var(--border-color);
  border-bottom: none;
  border-radius: 6px 6px 0 0;
  cursor: pointer;
  font-size: 13px;
  color: var(--text-color);
  white-space: nowrap;
  overflow: hidden;
  position: relative;
  transition: background-color 0.15s ease, color 0.15s ease;
  flex-shrink: 0;
  margin-right: 2px;
  opacity: 0.7;
}

.tab-item:hover {
  background-color: var(--button-hover);
  opacity: 0.9;
}

.tab-item.active {
  background-color: var(--bg-color);
  border-color: var(--border-color);
  color: var(--accent-color);
  border-bottom: 1px solid var(--bg-color);
  opacity: 1;
  z-index: 2;
}

.tab-item.unsaved::after {
  content: '';
  display: inline-block;
  width: 6px;
  height: 6px;
  background-color: var(--accent-color);
  border-radius: 50%;
  flex-shrink: 0;
  margin-left: 2px;
}

.tab-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  font-size: 12px;
  line-height: 1;
}

.tab-close-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 3px;
  background: none;
  border: none;
  color: var(--text-color);
  cursor: pointer;
  padding: 0;
  font-size: 11px;
  opacity: 0;
  flex-shrink: 0;
  transition: background-color 0.15s ease, opacity 0.15s ease;
}

.tab-item:hover .tab-close-btn,
.tab-item.active .tab-close-btn {
  opacity: 0.6;
}

.tab-close-btn:hover {
  background-color: var(--button-active);
  opacity: 1 !important;
  color: var(--color-danger-fg, #d73a49);
}

.tab-new-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 5px;
  background: none;
  border: 1px solid transparent;
  color: var(--text-color);
  cursor: pointer;
  font-size: 16px;
  flex-shrink: 0;
  margin-left: 4px;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

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

/* Drag-and-drop visual feedback */
.tab-item.dragging {
  opacity: 0.4;
}

.tab-item.drag-over {
  border-left: 2px solid var(--accent-color);
}

/* Tab enter animation */
@keyframes tabSlideIn {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 0.7; transform: translateY(0); }
}

.tab-item {
  a...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.

@vercel
Copy link

vercel bot commented Mar 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
markdown-viwer Ready Ready Preview, Comment Mar 7, 2026 6:38pm

- Add tab bar HTML to index.html (between header and dropzone)
- Add complete tab bar CSS to styles.css (tabs, dark mode, animations, responsive)
- Add TabManager module to script.js:
  - Storage helpers (localStorage keys, load/save)
  - deriveTitleFromContent, createTab, renderTabBar with drag-and-drop
  - initTabs, switchTab, newTab, closeTab, updateActiveTabTitle
  - saveCurrentTabState, restoreViewMode helper
- Wire into existing app: initTabs() replaces renderMarkdown() on init
- importMarkdownFile opens files in new tab
- Debounced save (500ms) and title update (800ms) on editor input
- saveCurrentTabState on view mode button click
- Ctrl/Cmd+T (new tab), Ctrl/Cmd+W (close tab) keyboard shortcuts
- tab-new-btn click handler
- loadFromShareHash persists shared content to current tab
- Max 20 tabs with informative alert

Co-authored-by: ThisIs-Developer <109382325+ThisIs-Developer@users.noreply.github.com>
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Mar 7, 2026

Deploying markdown-viewer with  Cloudflare Pages  Cloudflare Pages

Latest commit: d8510fc
Status: ✅  Deploy successful!
Preview URL: https://c78cd5f8.markdown-viewer.pages.dev
Branch Preview URL: https://copilot-add-document-tabs-se.markdown-viewer.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link

Deploying markdownviewer with  Cloudflare Pages  Cloudflare Pages

Latest commit: d8510fc
Status: ✅  Deploy successful!
Preview URL: https://2a010d86.markdownviewer.pages.dev
Branch Preview URL: https://copilot-add-document-tabs-se.markdownviewer.pages.dev

View logs

Copilot AI changed the title [WIP] Add multi-tab workspace system to Markdown Viewer application feat: Multi-tab workspace with localStorage session persistence Mar 7, 2026
@ThisIs-Developer ThisIs-Developer marked this pull request as ready for review March 7, 2026 18:40
@ThisIs-Developer ThisIs-Developer merged commit 2fed8d5 into main Mar 7, 2026
6 checks passed
@ThisIs-Developer ThisIs-Developer deleted the copilot/add-document-tabs-session-management branch March 7, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants