Skip to content

Commit d1dca2d

Browse files
committed
add tabs
1 parent b2e1b4b commit d1dca2d

3 files changed

Lines changed: 244 additions & 2 deletions

File tree

src/css/style.css

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,98 @@ body {
2727
background-color: var(--background-color);
2828
}
2929

30+
.tabs {
31+
display: flex;
32+
align-items: center;
33+
/* gap: 6px; */
34+
padding: 0px 0px;
35+
background-color: var(--background-color);
36+
border-top: 1px solid rgba(0, 0, 0, 0.05);
37+
}
38+
39+
.tab-list {
40+
display: flex;
41+
flex-wrap: nowrap;
42+
overflow-x: auto;
43+
scrollbar-width: thin;
44+
-ms-overflow-style: none;
45+
}
46+
47+
.tab-list::-webkit-scrollbar {
48+
height: 6px;
49+
}
50+
51+
.tab {
52+
width: 200px;
53+
min-width: 200px;
54+
display: inline-flex;
55+
align-items: center;
56+
gap: 8px;
57+
padding: 8px 24px;
58+
border-radius: 0px;
59+
background: #cec98d;
60+
color: #222;
61+
border: 1px solid rgba(0, 0, 0, 0.06);
62+
cursor: pointer;
63+
white-space: nowrap;
64+
justify-content: space-between;
65+
}
66+
67+
.tab .title {
68+
max-width: 200px;
69+
overflow: hidden;
70+
text-overflow: ellipsis;
71+
}
72+
73+
.tab .close {
74+
font-weight: 900;
75+
opacity: 1;
76+
font-size: large;
77+
}
78+
79+
.tab.active {
80+
background: #eee8aa;
81+
border-color: rgba(0, 0, 0, 0.15);
82+
border: 0px solid rgba(0, 0, 0, 0.06);
83+
justify-content: space-between;
84+
}
85+
86+
.add-tab {
87+
background: #cec98d;
88+
border: 0px solid rgba(0, 0, 0, 0.06);
89+
border-radius: 0px;
90+
width: 150px;
91+
cursor: pointer;
92+
padding: 10px 24px;
93+
}
94+
95+
.add-tab:hover {
96+
background: #eee8aa;
97+
border: 1px solid rgba(0, 0, 0, 0.06);
98+
}
99+
100+
.dark-mode-topbar.tabs,
101+
.tabs.dark-mode-topbar {
102+
background-color: #1e1e1e !important;
103+
}
104+
105+
.tabs .tab,
106+
.tabs .add-tab {
107+
color: #444;
108+
}
109+
110+
.dark-mode-topbar .tab,
111+
.dark-mode-topbar .add-tab {
112+
color: #ece5e5cc;
113+
background: #222;
114+
border-color: #333;
115+
}
116+
117+
.dark-mode-topbar .tab.active {
118+
background: #121212;
119+
border-color: #444;
120+
}
121+
30122
@media only screen and (max-width: 991px) {
31123

32124
/* This rule applies only to screens smaller than 992px */
@@ -150,4 +242,4 @@ input[type="file"] {
150242
::-webkit-scrollbar-thumb {
151243
background-color: var(--impact-light-gray);
152244
border-radius: 10px;
153-
}
245+
}

src/index.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373

7474
<body text="#000000" topmargin="0" leftmargin="0" rightmargin="0" bottommargin="0" marginheight="0" marginwidth="0">
7575
<div id="main">
76+
7677
<div id="topbar" class="topbar">
7778
<div id="toolbar" class="toolbar">
7879
<span class="ql-formats">
@@ -158,6 +159,12 @@
158159
</label>
159160
</div>
160161
</div>
162+
<div id="tabs" class="tabs">
163+
<div id="tab-list" class="tab-list"></div>
164+
<button id="add-tab" class="add-tab" title="New tab">
165+
<span>+ New Tab</span>
166+
</button>
167+
</div>
161168
<grammarly-editor-plugin>
162169
<div id="editor" class="editor">
163170
</div>
@@ -178,6 +185,7 @@
178185
theme: 'snow',
179186
placeholder: default_text
180187
});
188+
if (window.initTabs) { window.initTabs(); }
181189
</script>
182190

183191
<script type="text/javascript">
@@ -228,4 +236,4 @@
228236
});
229237
</script>
230238

231-
</html>
239+
</html>

src/js/main.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,151 @@ function applyDarkMode() {
5757
document.getElementById("topbar").classList.toggle("dark-mode-topbar");
5858
document.getElementById("toolbar").classList.toggle("dark-mode-toolbar");
5959
document.getElementById("navbar").classList.toggle("dark-mode-navbar");
60+
var tabsEl = document.getElementById("tabs");
61+
if (tabsEl) tabsEl.classList.toggle("dark-mode-topbar");
6062
}
6163

6264
function printConsoleArt() {
6365
const consoleStr = `Hello There!`;
6466
console.log(consoleStr);
6567
}
68+
69+
(function () {
70+
function debounce(fn, delay) {
71+
var t;
72+
return function () {
73+
var ctx = this, args = arguments;
74+
clearTimeout(t);
75+
t = setTimeout(function () { fn.apply(ctx, args) }, delay);
76+
}
77+
}
78+
79+
var TAB_STORAGE_KEY = "tabs.v1";
80+
var ACTIVE_TAB_KEY = "activeTabId.v1";
81+
var state = {
82+
tabs: [],
83+
activeId: null,
84+
els: { list: null, add: null }
85+
};
86+
87+
function loadState() {
88+
try { state.tabs = JSON.parse(localStorage.getItem(TAB_STORAGE_KEY)) || []; } catch (e) { state.tabs = []; }
89+
state.activeId = localStorage.getItem(ACTIVE_TAB_KEY) || (state.tabs[0] && state.tabs[0].id) || null;
90+
}
91+
92+
function saveTabs() {
93+
localStorage.setItem(TAB_STORAGE_KEY, JSON.stringify(state.tabs));
94+
}
95+
96+
function saveActive() {
97+
if (!state.activeId) return;
98+
var t = state.tabs.find(function (x) { return x.id === state.activeId });
99+
if (!t) return;
100+
if (typeof quill !== 'undefined') {
101+
t.html = quill.root.innerHTML;
102+
var text = quill.getText().trim();
103+
var title = text.split("\n")[0] || "Untitled";
104+
if (title.length > 30) title = title.slice(0, 30) + "…";
105+
t.title = title || t.title || "Untitled";
106+
}
107+
saveTabs();
108+
}
109+
110+
var debouncedSave = debounce(saveActive, 500);
111+
112+
function createTab(title, html) {
113+
var id = (typeof uuid !== 'undefined' && uuid.v4) ? uuid.v4() : (Date.now() + "" + Math.random());
114+
var t = { id: id, title: title || "Untitled", html: html || "" };
115+
state.tabs.push(t);
116+
state.activeId = id;
117+
localStorage.setItem(ACTIVE_TAB_KEY, state.activeId);
118+
saveTabs();
119+
render();
120+
loadIntoEditor(id);
121+
}
122+
123+
function closeTab(id) {
124+
var idx = state.tabs.findIndex(function (x) { return x.id === id });
125+
if (idx < 0) return;
126+
var wasActive = state.activeId === id;
127+
state.tabs.splice(idx, 1);
128+
if (state.tabs.length === 0) {
129+
createTab();
130+
} else if (wasActive) {
131+
var next = state.tabs[idx] || state.tabs[idx - 1] || state.tabs[0];
132+
state.activeId = next.id;
133+
localStorage.setItem(ACTIVE_TAB_KEY, state.activeId);
134+
loadIntoEditor(state.activeId);
135+
}
136+
saveTabs();
137+
render();
138+
}
139+
140+
function switchTab(id) {
141+
if (state.activeId === id) return;
142+
saveActive();
143+
state.activeId = id;
144+
localStorage.setItem(ACTIVE_TAB_KEY, state.activeId);
145+
loadIntoEditor(id);
146+
render();
147+
}
148+
149+
function loadIntoEditor(id) {
150+
var t = state.tabs.find(function (x) { return x.id === id });
151+
if (!t || typeof quill === 'undefined') return;
152+
quill.root.innerHTML = '';
153+
quill.clipboard.dangerouslyPasteHTML(0, t.html || '');
154+
quill.setSelection(0);
155+
}
156+
157+
function render() {
158+
if (!state.els.list) return;
159+
state.els.list.innerHTML = '';
160+
state.tabs.forEach(function (t) {
161+
var b = document.createElement('button');
162+
b.className = 'tab' + (t.id === state.activeId ? ' active' : '');
163+
b.setAttribute('data-id', t.id);
164+
var titleSpan = document.createElement('span');
165+
titleSpan.className = 'title';
166+
titleSpan.textContent = t.title || 'Untitled';
167+
var close = document.createElement('span');
168+
close.className = 'close';
169+
close.textContent = '×';
170+
close.addEventListener('click', function (e) {
171+
e.stopPropagation();
172+
var title = t.title || 'this tab';
173+
var ok = window.confirm('Close "' + title + '"? This will permanently remove its content from this browser.');
174+
if (ok) closeTab(t.id);
175+
});
176+
b.addEventListener('click', function () { switchTab(t.id); });
177+
b.appendChild(titleSpan);
178+
b.appendChild(close);
179+
state.els.list.appendChild(b);
180+
});
181+
}
182+
183+
function bindQuill() {
184+
if (typeof quill === 'undefined') return;
185+
quill.on('text-change', function () { debouncedSave(); });
186+
}
187+
188+
function ensureInitial() {
189+
if (!state.tabs || state.tabs.length === 0) {
190+
createTab();
191+
} else {
192+
render();
193+
loadIntoEditor(state.activeId);
194+
}
195+
}
196+
197+
function initTabs() {
198+
state.els.list = document.getElementById('tab-list');
199+
state.els.add = document.getElementById('add-tab');
200+
if (state.els.add) { state.els.add.addEventListener('click', function () { saveActive(); createTab(); }); }
201+
loadState();
202+
bindQuill();
203+
ensureInitial();
204+
}
205+
206+
window.initTabs = initTabs;
207+
})();

0 commit comments

Comments
 (0)