fix: Menu dropdown should be in right x position

This commit is contained in:
2026-05-31 00:43:24 +05:30
parent 0f9959c1f9
commit f37291196b
2 changed files with 102 additions and 118 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+101 -117
View File
@@ -91,7 +91,6 @@
display: none; display: none;
position: absolute; position: absolute;
top: 100%; top: 100%;
left: 0;
background: var(--menu-bg); background: var(--menu-bg);
color: var(--menu-fg); color: var(--menu-fg);
border: 1px solid #000; border: 1px solid #000;
@@ -261,7 +260,7 @@
#dialog-overlay.open { display: flex; } #dialog-overlay.open { display: flex; }
.dialog { .dialog {
background: var(--dialog-bg); background: var(--bg);
color: var(--menu-fg); color: var(--menu-fg);
border: 2px solid #ffffff; border: 2px solid #ffffff;
min-width: 340px; min-width: 340px;
@@ -325,7 +324,7 @@
.btn:hover { background: #cccccc; } .btn:hover { background: #cccccc; }
.btn:active { box-shadow: none; transform: translate(1px, 1px); } .btn:active { box-shadow: none; transform: translate(1px, 1px); }
.btn.primary { .btn.primary {
background: #000080; background: (--menu-bg);
color: #fff; color: #fff;
border-color: #fff; border-color: #fff;
} }
@@ -602,7 +601,7 @@
(function () { (function () {
'use strict'; 'use strict';
// DOM References // -- DOM References
const editor = document.getElementById('editor'); const editor = document.getElementById('editor');
const lineNums = document.getElementById('line-numbers'); const lineNums = document.getElementById('line-numbers');
const titleBar = document.getElementById('title-bar'); const titleBar = document.getElementById('title-bar');
@@ -625,8 +624,9 @@
const findStatus = document.getElementById('find-status'); const findStatus = document.getElementById('find-status');
const gotoBar = document.getElementById('goto-bar'); const gotoBar = document.getElementById('goto-bar');
const gotoInput = document.getElementById('goto-input'); const gotoInput = document.getElementById('goto-input');
const menuBar = document.getElementById('menu-bar');
// State // -- State
let fileName = 'Untitled'; let fileName = 'Untitled';
let isModified = false; let isModified = false;
let insertMode = true; let insertMode = true;
@@ -638,28 +638,28 @@
let notifTimer = null; let notifTimer = null;
let replaceMode = false; let replaceMode = false;
// Initial Content // -- Initial Content
editor.value = [ editor.value = [
'Pascar', 'Pascar',
'===================================', '======',
'', '',
'A faithful offline recreation of the classic MS-DOS Editor', 'A faithful offline recreation of the classic MS-DOS Editor',
'Runs entirely in your browser - no server, no internet.', 'Runs entirely in your browser - no server, no internet.',
'', '',
'QUICK START', 'QUICK START',
'-----------', '-----------',
' Ctrl+O Open a text file from your computer', ' Ctrl+O Open a text file from your computer',
' Ctrl+S Save / download the current file', ' Ctrl+S Save / download the current file',
' Ctrl+F Find text', ' Ctrl+F Find text',
' Ctrl+H Find and Replace', ' Ctrl+H Find and Replace',
' Ctrl+G Go to a specific line number', ' Ctrl+G Go to a specific line number',
' F3 / Shift+F3 Find next / previous match', ' F3 / Shift+F3 Find next / previous match',
' F5 Insert current date and time', ' F5 Insert current date and time',
' Alt+F/E/S/V/H Open menu (then ↑↓ to navigate, Enter to select)', ' Alt+F/E/S/V/H Open menu (then ↑↓ to navigate, Enter to select)',
' Alt+L Toggle line numbers', ' Alt+L Toggle line numbers',
' Ctrl++ / Ctrl+- Zoom in / out', ' Ctrl++ / Ctrl-- Zoom in / out',
' Insert Toggle Insert / Overwrite mode', ' Insert Toggle Insert / Overwrite mode',
' F1 Show full keyboard shortcuts', ' F1 Show full keyboard shortcuts',
'', '',
'Start typing to edit this file.', 'Start typing to edit this file.',
].join('\n'); ].join('\n');
@@ -667,7 +667,7 @@
updateAll(); updateAll();
editor.focus(); editor.focus();
// Core Update Functions // -- Core Update Functions
function updateAll() { function updateAll() {
updateLineNumbers(); updateLineNumbers();
updateStatus(); updateStatus();
@@ -706,7 +706,7 @@
titleBar.textContent = (isModified ? '* ' : '') + fileName + ' - Pascar'; titleBar.textContent = (isModified ? '* ' : '') + fileName + ' - Pascar';
} }
// Notifications // -- Notifications
function showNotif(msg, dur = 2500) { function showNotif(msg, dur = 2500) {
notif.textContent = msg; notif.textContent = msg;
notif.classList.add('show'); notif.classList.add('show');
@@ -714,25 +714,22 @@
notifTimer = setTimeout(() => notif.classList.remove('show'), dur); notifTimer = setTimeout(() => notif.classList.remove('show'), dur);
} }
// Editor Events // -- Editor Events
editor.addEventListener('input', () => { isModified = true; updateAll(); }); editor.addEventListener('input', () => { isModified = true; updateAll(); });
editor.addEventListener('keyup', updateStatus); editor.addEventListener('keyup', updateStatus);
editor.addEventListener('click', updateStatus); editor.addEventListener('click', updateStatus);
editor.addEventListener('scroll', () => { lineNums.scrollTop = editor.scrollTop; }); editor.addEventListener('scroll', () => { lineNums.scrollTop = editor.scrollTop; });
editor.addEventListener('keydown', (e) => { editor.addEventListener('keydown', (e) => {
// Insert / Overwrite toggle
if (e.key === 'Insert' && !e.ctrlKey && !e.altKey) { if (e.key === 'Insert' && !e.ctrlKey && !e.altKey) {
insertMode = !insertMode; insertMode = !insertMode;
updateStatus(); updateStatus();
return; return;
} }
// Tab - 4 spaces
if (e.key === 'Tab' && !e.ctrlKey && !e.altKey) { if (e.key === 'Tab' && !e.ctrlKey && !e.altKey) {
e.preventDefault(); e.preventDefault();
const s = editor.selectionStart, en = editor.selectionEnd; const s = editor.selectionStart, en = editor.selectionEnd;
if (e.shiftKey) { if (e.shiftKey) {
// unindent
const lineStart = editor.value.lastIndexOf('\n', s - 1) + 1; const lineStart = editor.value.lastIndexOf('\n', s - 1) + 1;
const lineText = editor.value.substring(lineStart, s); const lineText = editor.value.substring(lineStart, s);
const spaces = lineText.match(/^ {1,4}/); const spaces = lineText.match(/^ {1,4}/);
@@ -750,29 +747,35 @@
} }
}); });
// Menu keyboard navigation state // -- Menu System
// Maps Alt+letter - menu name
const ALT_MENU_MAP = { f: 'file', e: 'edit', s: 'search', v: 'view', h: 'help' }; const ALT_MENU_MAP = { f: 'file', e: 'edit', s: 'search', v: 'view', h: 'help' };
// Maps letter - action for items within an open dropdown
// Built dynamically from .ul spans inside each dropdown
function getOpenDropdown() { function getOpenDropdown() {
return document.querySelector('.dropdown.open') || null; return document.querySelector('.dropdown.open') || null;
} }
// Position the dropdown directly below its menu-item trigger
function positionDropdown(menuName) {
const item = document.querySelector(`.menu-item[data-menu="${menuName}"]`);
const dd = document.getElementById('menu-' + menuName);
if (!item || !dd) return;
const itemRect = item.getBoundingClientRect();
const barRect = menuBar.getBoundingClientRect();
dd.style.left = (itemRect.left - barRect.left) + 'px';
}
function openMenu(name) { function openMenu(name) {
const item = document.querySelector(`.menu-item[data-menu="${name}"]`); const item = document.querySelector(`.menu-item[data-menu="${name}"]`);
const dd = document.getElementById('menu-' + name); const dd = document.getElementById('menu-' + name);
if (!item || !dd) return; if (!item || !dd) return;
document.querySelectorAll('.dropdown').forEach(d => d.classList.remove('open')); document.querySelectorAll('.dropdown').forEach(d => d.classList.remove('open'));
document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active')); document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active'));
positionDropdown(name);
dd.classList.add('open'); dd.classList.add('open');
item.classList.add('active'); item.classList.add('active');
// Highlight first non-separator item
highlightDDItem(dd, 0); highlightDDItem(dd, 0);
} }
// Highlight a dd-item by index among visible (non-separator, non-disabled) items
function getDDItems(dd) { function getDDItems(dd) {
return Array.from(dd.querySelectorAll('.dd-item:not(.disabled)')); return Array.from(dd.querySelectorAll('.dd-item:not(.disabled)'));
} }
@@ -790,18 +793,55 @@
return items.findIndex(i => i.classList.contains('dd-focused')); return items.findIndex(i => i.classList.contains('dd-focused'));
} }
// Global Keyboard Shortcuts document.querySelectorAll('.menu-item').forEach(item => {
item.addEventListener('click', (e) => {
e.stopPropagation();
const name = item.dataset.menu;
const dd = document.getElementById('menu-' + name);
const wasOpen = dd.classList.contains('open');
document.querySelectorAll('.dropdown').forEach(d => d.classList.remove('open'));
document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active'));
if (!wasOpen) {
positionDropdown(name);
dd.classList.add('open');
item.classList.add('active');
}
});
item.addEventListener('mouseenter', () => {
const anyOpen = document.querySelector('.dropdown.open');
if (anyOpen) {
const name = item.dataset.menu;
document.querySelectorAll('.dropdown').forEach(d => d.classList.remove('open'));
document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active'));
positionDropdown(name);
document.getElementById('menu-' + name).classList.add('open');
item.classList.add('active');
}
});
});
document.querySelectorAll('.dd-item:not(.disabled)').forEach(item => {
item.addEventListener('click', (e) => {
e.stopPropagation();
closeAll();
handleAction(item.dataset.action);
});
});
document.addEventListener('click', (e) => {
if (!e.target.closest('#menu-bar')) closeAll();
});
// -- Global Keyboard Shortcuts
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
const ctrl = e.ctrlKey || e.metaKey; const ctrl = e.ctrlKey || e.metaKey;
const shift = e.shiftKey; const shift = e.shiftKey;
const alt = e.altKey; const alt = e.altKey;
const key = e.key.toLowerCase(); const key = e.key.toLowerCase();
// Alt+letter: open a menu
if (alt && !ctrl && ALT_MENU_MAP[key]) { if (alt && !ctrl && ALT_MENU_MAP[key]) {
e.preventDefault(); e.preventDefault();
const dd = getOpenDropdown(); const dd = getOpenDropdown();
// If same menu already open, close it; otherwise open the requested one
if (dd && dd.id === 'menu-' + ALT_MENU_MAP[key]) { if (dd && dd.id === 'menu-' + ALT_MENU_MAP[key]) {
closeAll(); closeAll();
} else { } else {
@@ -810,13 +850,11 @@
return; return;
} }
// Arrow navigation inside open dropdown
const openDD = getOpenDropdown(); const openDD = getOpenDropdown();
if (openDD) { if (openDD) {
if (e.key === 'ArrowDown') { if (e.key === 'ArrowDown') {
e.preventDefault(); e.preventDefault();
const idx = getFocusedIndex(openDD); highlightDDItem(openDD, getFocusedIndex(openDD) + 1);
highlightDDItem(openDD, idx + 1);
return; return;
} }
if (e.key === 'ArrowUp') { if (e.key === 'ArrowUp') {
@@ -827,19 +865,16 @@
} }
if (e.key === 'ArrowLeft') { if (e.key === 'ArrowLeft') {
e.preventDefault(); e.preventDefault();
// Move to previous menu
const menus = ['file','edit','search','view','help']; const menus = ['file','edit','search','view','help'];
const cur = openDD.id.replace('menu-', ''); const cur = openDD.id.replace('menu-', '');
const i = menus.indexOf(cur); openMenu(menus[(menus.indexOf(cur) - 1 + menus.length) % menus.length]);
openMenu(menus[(i - 1 + menus.length) % menus.length]);
return; return;
} }
if (e.key === 'ArrowRight') { if (e.key === 'ArrowRight') {
e.preventDefault(); e.preventDefault();
const menus = ['file','edit','search','view','help']; const menus = ['file','edit','search','view','help'];
const cur = openDD.id.replace('menu-', ''); const cur = openDD.id.replace('menu-', '');
const i = menus.indexOf(cur); openMenu(menus[(menus.indexOf(cur) + 1) % menus.length]);
openMenu(menus[(i + 1) % menus.length]);
return; return;
} }
if (e.key === 'Enter') { if (e.key === 'Enter') {
@@ -852,11 +887,9 @@
} }
return; return;
} }
// Letter shortcut inside menu: match underlined letter
if (!ctrl && !alt && e.key.length === 1) { if (!ctrl && !alt && e.key.length === 1) {
const letter = e.key.toLowerCase(); const letter = e.key.toLowerCase();
const items = getDDItems(openDD); const match = getDDItems(openDD).find(item => {
const match = items.find(item => {
const ul = item.querySelector('.ul'); const ul = item.querySelector('.ul');
return ul && ul.textContent.toLowerCase() === letter; return ul && ul.textContent.toLowerCase() === letter;
}); });
@@ -883,7 +916,6 @@
if (ctrl && key === 'f') { e.preventDefault(); openFindBar(false); } if (ctrl && key === 'f') { e.preventDefault(); openFindBar(false); }
if (ctrl && key === 'h') { e.preventDefault(); openFindBar(true); } if (ctrl && key === 'h') { e.preventDefault(); openFindBar(true); }
if (ctrl && key === 'g') { e.preventDefault(); openGoto(); } if (ctrl && key === 'g') { e.preventDefault(); openGoto(); }
if (ctrl && key === 'a') { /* browser default */ }
if (ctrl && (key === '=' || key === '+')) { e.preventDefault(); zoomChange(1); } if (ctrl && (key === '=' || key === '+')) { e.preventDefault(); zoomChange(1); }
if (ctrl && key === '-') { e.preventDefault(); zoomChange(-1); } if (ctrl && key === '-') { e.preventDefault(); zoomChange(-1); }
if (ctrl && key === '0') { e.preventDefault(); zoomReset(); } if (ctrl && key === '0') { e.preventDefault(); zoomReset(); }
@@ -891,7 +923,7 @@
if (alt && key === 'l') { e.preventDefault(); toggleLineNumbers(); } if (alt && key === 'l') { e.preventDefault(); toggleLineNumbers(); }
}); });
// Close All Overlays // -- Close All Overlays
function closeAll() { function closeAll() {
document.querySelectorAll('.dropdown').forEach(d => d.classList.remove('open')); document.querySelectorAll('.dropdown').forEach(d => d.classList.remove('open'));
document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active')); document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active'));
@@ -907,42 +939,7 @@
else { closeAll(); helpOverlay.classList.add('open'); } else { closeAll(); helpOverlay.classList.add('open'); }
} }
// Menu System // -- Action Dispatcher
document.querySelectorAll('.menu-item').forEach(item => {
item.addEventListener('click', (e) => {
e.stopPropagation();
const dd = document.getElementById('menu-' + item.dataset.menu);
const wasOpen = dd.classList.contains('open');
document.querySelectorAll('.dropdown').forEach(d => d.classList.remove('open'));
document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active'));
if (!wasOpen) { dd.classList.add('open'); item.classList.add('active'); }
});
// Keep menu open when hovering to other menus while one is open
item.addEventListener('mouseenter', () => {
const anyOpen = document.querySelector('.dropdown.open');
if (anyOpen) {
document.querySelectorAll('.dropdown').forEach(d => d.classList.remove('open'));
document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active'));
const dd = document.getElementById('menu-' + item.dataset.menu);
dd.classList.add('open');
item.classList.add('active');
}
});
});
document.querySelectorAll('.dd-item:not(.disabled)').forEach(item => {
item.addEventListener('click', (e) => {
e.stopPropagation();
closeAll();
handleAction(item.dataset.action);
});
});
document.addEventListener('click', (e) => {
if (!e.target.closest('#menu-bar')) closeAll();
});
// Action Dispatcher
function handleAction(a) { function handleAction(a) {
const map = { const map = {
new: doNew, new: doNew,
@@ -975,7 +972,10 @@
if (map[a]) map[a](); if (map[a]) map[a]();
} }
// File Operations // -- File Operations
let fileHandle = null;
const hasFS = typeof window.showOpenFilePicker === 'function';
function doNew() { function doNew() {
if (isModified && !confirm('Discard unsaved changes?')) return; if (isModified && !confirm('Discard unsaved changes?')) return;
editor.value = ''; editor.value = '';
@@ -986,10 +986,6 @@
editor.focus(); editor.focus();
} }
// File System Access API handle (Chrome/Edge only) - lets us save in-place
let fileHandle = null;
const hasFS = typeof window.showOpenFilePicker === 'function';
async function doOpen() { async function doOpen() {
if (isModified && !confirm('Discard unsaved changes?')) return; if (isModified && !confirm('Discard unsaved changes?')) return;
if (hasFS) { if (hasFS) {
@@ -1009,7 +1005,6 @@
if (err.name !== 'AbortError') showNotif('Could not open file.'); if (err.name !== 'AbortError') showNotif('Could not open file.');
} }
} else { } else {
// Fallback: classic <input type=file>
const inp = document.createElement('input'); const inp = document.createElement('input');
inp.type = 'file'; inp.type = 'file';
inp.accept = '.txt,.md,.js,.ts,.py,.html,.css,.json,.xml,.csv,.log,.c,.cpp,.h,.java,.rs,.go,.rb,.sh,.bat,.ini,.cfg,.yaml,.yml,text/*'; inp.accept = '.txt,.md,.js,.ts,.py,.html,.css,.json,.xml,.csv,.log,.c,.cpp,.h,.java,.rs,.go,.rb,.sh,.bat,.ini,.cfg,.yaml,.yml,text/*';
@@ -1033,7 +1028,6 @@
async function doSave() { async function doSave() {
if (hasFS && fileHandle) { if (hasFS && fileHandle) {
// Write directly back to the original file - no download dialog
try { try {
const writable = await fileHandle.createWritable(); const writable = await fileHandle.createWritable();
await writable.write(editor.value); await writable.write(editor.value);
@@ -1045,10 +1039,8 @@
if (err.name !== 'AbortError') showNotif('Save failed: ' + err.message); if (err.name !== 'AbortError') showNotif('Save failed: ' + err.message);
} }
} else if (hasFS && !fileHandle) { } else if (hasFS && !fileHandle) {
// No handle yet (new file) - show Save As picker
await doSaveAs(); await doSaveAs();
} else { } else {
// Fallback: trigger download
_downloadFile(fileName === 'Untitled' ? 'untitled.txt' : fileName); _downloadFile(fileName === 'Untitled' ? 'untitled.txt' : fileName);
} }
} }
@@ -1072,11 +1064,10 @@
if (err.name !== 'AbortError') showNotif('Save failed: ' + err.message); if (err.name !== 'AbortError') showNotif('Save failed: ' + err.message);
} }
} else { } else {
// Fallback: ask name then download
showDialog('Save As', ` showDialog('Save As', `
<label>File name:</label> <label>File name:</label>
<input type="text" id="d-savename" value="${escHtml(fileName === 'Untitled' ? 'untitled.txt' : fileName)}"> <input type="text" id="d-savename" value="${escHtml(fileName === 'Untitled' ? 'untitled.txt' : fileName)}">
<div style="font-size:12px;color:#555;margin-bottom:8px;">File System Access API not supported in this browser.<br>File will be downloaded instead.</div> <div style="font-size:12px;color:#aaa;margin-bottom:8px;">File System Access API not supported in this browser.<br>File will be downloaded instead.</div>
<div class="dialog-buttons"> <div class="dialog-buttons">
<button class="btn primary" id="d-saveas-ok">Download</button> <button class="btn primary" id="d-saveas-ok">Download</button>
<button class="btn" id="d-saveas-cancel">Cancel</button> <button class="btn" id="d-saveas-cancel">Cancel</button>
@@ -1125,7 +1116,7 @@
isModified = true; updateAll(); editor.focus(); isModified = true; updateAll(); editor.focus();
} }
// View // -- View
function toggleWordWrap() { function toggleWordWrap() {
wordWrap = !wordWrap; wordWrap = !wordWrap;
editor.style.whiteSpace = wordWrap ? 'pre-wrap' : 'pre'; editor.style.whiteSpace = wordWrap ? 'pre-wrap' : 'pre';
@@ -1144,26 +1135,22 @@
} }
function zoomReset() { fontSize = 15; applyZoom(); } function zoomReset() { fontSize = 15; applyZoom(); }
function applyZoom() { function applyZoom() {
editor.style.fontSize = fontSize + 'px'; editor.style.fontSize = fontSize + 'px';
lineNums.style.fontSize = fontSize + 'px'; lineNums.style.fontSize = fontSize + 'px';
editor.style.lineHeight = lineNums.style.lineHeight = '1.25'; editor.style.lineHeight = lineNums.style.lineHeight = '1.25';
updateLineNumbers(); updateLineNumbers();
showNotif(`Zoom: ${fontSize}px`); showNotif(`Zoom: ${fontSize}px`);
} }
// Find / Replace Bar // -- Find / Replace Bar
function openFindBar(withReplace) { function openFindBar(withReplace) {
replaceMode = withReplace; replaceMode = withReplace;
closeAll(); closeAll();
findBar.classList.add('open'); findBar.classList.add('open');
// Toggle replace row
document.getElementById('replace-label').style.display = withReplace ? '' : 'none'; document.getElementById('replace-label').style.display = withReplace ? '' : 'none';
replInput.style.display = withReplace ? '' : 'none'; replInput.style.display = withReplace ? '' : 'none';
document.getElementById('btn-replace-one').style.display = withReplace ? '' : 'none'; document.getElementById('btn-replace-one').style.display = withReplace ? '' : 'none';
document.getElementById('btn-replace-all').style.display = withReplace ? '' : 'none'; document.getElementById('btn-replace-all').style.display = withReplace ? '' : 'none';
document.getElementById('find-label').textContent = withReplace ? 'Find:' : 'Find:';
findInput.value = lastSearch; findInput.value = lastSearch;
replInput.value = lastReplace; replInput.value = lastReplace;
findStatus.textContent = ''; findStatus.textContent = '';
@@ -1252,12 +1239,12 @@
} }
function scrollToSelection() { function scrollToSelection() {
const lines = editor.value.substring(0, editor.selectionStart).split('\n'); const lines = editor.value.substring(0, editor.selectionStart).split('\n');
const lineH = editor.scrollHeight / Math.max(1, editor.value.split('\n').length); const lineH = editor.scrollHeight / Math.max(1, editor.value.split('\n').length);
editor.scrollTop = Math.max(0, (lines.length - 5) * lineH); editor.scrollTop = Math.max(0, (lines.length - 5) * lineH);
} }
// Go to Line // -- Go to Line
function openGoto() { function openGoto() {
closeAll(); closeAll();
gotoBar.classList.add('open'); gotoBar.classList.add('open');
@@ -1275,8 +1262,8 @@
function execGoto() { function execGoto() {
const n = parseInt(gotoInput.value, 10); const n = parseInt(gotoInput.value, 10);
if (isNaN(n) || n < 1) { showNotif('Enter a valid line number.'); return; } if (isNaN(n) || n < 1) { showNotif('Enter a valid line number.'); return; }
const lines = editor.value.split('\n'); const lines = editor.value.split('\n');
const lineN = Math.min(n, lines.length); const lineN = Math.min(n, lines.length);
let pos = 0; let pos = 0;
for (let i = 0; i < lineN - 1; i++) pos += lines[i].length + 1; for (let i = 0; i < lineN - 1; i++) pos += lines[i].length + 1;
editor.setSelectionRange(pos, pos); editor.setSelectionRange(pos, pos);
@@ -1288,12 +1275,11 @@
showNotif(`Jumped to line ${lineN}`); showNotif(`Jumped to line ${lineN}`);
} }
// Dialog Helper // -- Dialog Helper
function showDialog(title, bodyHTML, onReady) { function showDialog(title, bodyHTML, onReady) {
dialogTitle.textContent = title; dialogTitle.textContent = title;
dialogBody.innerHTML = bodyHTML; dialogBody.innerHTML = bodyHTML;
dialogOv.classList.add('open'); dialogOv.classList.add('open');
// Run callback after DOM is updated so getElementById finds the elements
if (onReady) onReady(); if (onReady) onReady();
} }
@@ -1301,14 +1287,14 @@
if (e.target === dialogOv) closeAll(); if (e.target === dialogOv) closeAll();
}); });
// About Dialog // -- About Dialog
function showAbout() { function showAbout() {
showDialog('About Pascar', ` showDialog('About Pascar', `
<div style="text-align:center;padding:8px 0 4px;"> <div style="text-align:center;padding:8px 0 4px;">
<div style="font-size:22px;font-weight:bold;margin-bottom:6px;letter-spacing:1px;">Pascar</div> <div style="font-size:22px;font-weight:bold;margin-bottom:6px;letter-spacing:1px;">Pascar</div>
<div style="margin-bottom:4px;font-size:14px;">MSDOS Editor Browser Edition - Offline Recreation</div> <div style="margin-bottom:4px;font-size:14px;">Browser based offline text editor</div>
<div style="color:#555;font-size:12px;margin-bottom:14px;">Inspired by the classic MS-DOS TUI text editor</div> <div style="color:#7f7f7f;font-size:12px;margin-bottom:14px;">Inspired by the classic MS-DOS editor</div>
<div style="font-size:12px;color:#333;line-height:1.6;"> <div style="font-size:12px;color:#fff;line-height:1.6;">
Runs 100% offline in your browser.<br> Runs 100% offline in your browser.<br>
No server. No data sent anywhere.<br> No server. No data sent anywhere.<br>
Open &amp; save files via your local filesystem. Open &amp; save files via your local filesystem.
@@ -1321,19 +1307,17 @@
}); });
} }
// Utility // -- Utility
function escHtml(s) { function escHtml(s) {
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
} }
// Prevent browser zoom from interfering with Ctrl+0/+/-
window.addEventListener('keydown', (e) => { window.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && (e.key === '0' || e.key === '+' || e.key === '=' || e.key === '-')) { if ((e.ctrlKey || e.metaKey) && (e.key === '0' || e.key === '+' || e.key === '=' || e.key === '-')) {
e.preventDefault(); e.preventDefault();
} }
}, { capture: true }); }, { capture: true });
// Warn before closing with unsaved changes
window.addEventListener('beforeunload', (e) => { window.addEventListener('beforeunload', (e) => {
if (isModified) { if (isModified) {
e.preventDefault(); e.preventDefault();