Skip to content

Commit 2c2b69a

Browse files
From v2.1.1 to v2.1.2
1 parent 4a20b8d commit 2c2b69a

File tree

2 files changed

+298
-19
lines changed

2 files changed

+298
-19
lines changed

CHANGELOG.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
## v2.1.2 (26/10/2025)
2+
- Small fixes and polish for v2.1.2:
3+
- Fix dark-mode styling for the undo/redo history dropdown
4+
- Keep history dropdown visible when hovering between the toolbar button and the menu
5+
- Move changelogs to a local view and ensure the popup respects dark mode
6+
- Improved undo/redo system with reduced lag (250ms throttle)
7+
- Fixed changelog formatting and removed duplicate entries
8+
- Renamed README.md to CHANGELOG.md for better organization
9+
- Bumped application version to v2.1.2
10+
11+
## v2.1.1 (25/10/2025)
12+
No major features; maintenance and polish:
13+
- Added "Check for Updates" feature
14+
15+
## v1.3.1 (25/10/2025)
16+
Security patch applied in v1.3.1 only:
17+
- Patched a Cross-Site Scripting (XSS) vulnerability that affected older code paths
18+
- Security Mode was enabled as part of the v1.3.1 patch to mitigate risk
19+
20+
## v1.3 (25/10/2025)
21+
Improvements and bug fixes for legacy apps:
22+
- Added Find and Replace
23+
24+
## v2.1 (23/10/2025)
25+
Updated features and bug fixes:
26+
- New features: HR, Task items, Table alignment, Subscript, Superscript, and more
27+
28+
## v2.0.5 (01/10/2025)
29+
Happy Halloween! UI and layout updates:
30+
- Help/changelog layouts improved
31+
- Various dark-mode fixes
32+
33+
## v2.0.4 (25/09/2025)
34+
- Popup UI changed to be more modular and centered
35+
- Escaping and list fixes
36+
37+
## v2.0.3 (24/09/2025)
38+
- Added regex search/replace with flags and preview highlighting
39+
40+
## v2.0.2 (21/09/2025)
41+
- Find and replace support
42+
43+
## v2.0.1 (19/09/2025)
44+
- Added support for .txt files
45+
- Fixed split-mode resize bug
46+
47+
## v2.0 (18/09/2025)
48+
- Major internal restructuring
49+
50+
## v1.2 (17/09/2025)
51+
- Fixed table formatting issues
52+
53+
## v1.1 (16/09/2025)
54+
- New icon design and minor bug fixes
55+
56+
## v1.0 (14/01/2024)
57+
Original Markdown Editor, forked from Lancer Fan Club Forums:
58+
- Features: headings, basic syntax, code support, table support, and a simple UI

markdown_editor.html

Lines changed: 240 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,9 @@ <h3 id="dialog-title" style="margin-top:0;margin-bottom:15px;font-size:16px;">In
307307
<span style="font-weight:bold;">Changelogs</span>
308308
<button id="changelog-popup-close" style="background:none;border:none;color:#fff;font-size:20px;cursor:pointer;">&times;</button>
309309
</div>
310-
<iframe src="https://objectpresents.github.io/lancer-notes/" style="width:100%;height:calc(100% - 44px);border:none;background:#fff;"></iframe>
310+
<div id="changelog-content" class="preview-content" style="width:100%;height:calc(100% - 44px);border:none;overflow:auto;padding:16px;">
311+
<!-- Local changelogs will be injected here via JS (parsed from markdown) -->
312+
</div>
311313
</div>
312314

313315
<input type="file" id="file-input" class="hidden-file-input" accept=".md,.txt,.text" />
@@ -353,11 +355,73 @@ <h3 id="dialog-title" style="margin-top:0;margin-bottom:15px;font-size:16px;">In
353355
let currentFile = null;
354356
let undoStack = [];
355357
let redoStack = [];
358+
let lastUndoValue = '';
359+
let undoTimer = null;
360+
const UNDO_STACK_LIMIT = 100;
361+
let lastUndoSaveTime = 0;
362+
let undoThrottleMs = 250; // Reduced throttle time for better responsiveness
356363
let currentViewMode = 'split';
357364

358365
// Version information
359-
const CURRENT_VERSION = '2.1.1';
366+
const CURRENT_VERSION = '2.1.2';
360367
const GITHUB_RELEASES_API = 'https://api.github.com/repos/ObjectPresents/lancer-notes/releases/latest';
368+
// Local changelog markdown (sourced from README.md 'Changelogs' section)
369+
const LOCAL_CHANGELOG_MD = `
370+
371+
## v2.1.2 (26/10/2025)
372+
- Small fixes and polish for v2.1.2:
373+
- Fix dark-mode styling for the undo/redo history dropdown
374+
- Keep history dropdown visible when hovering between the toolbar button and the menu
375+
- Move changelogs to a local view and ensure the popup respects dark mode
376+
- Improved undo/redo system with reduced lag (250ms throttle)
377+
- Fixed changelog formatting and removed duplicate entries
378+
- Renamed README.md to CHANGELOG.md for better organization
379+
- Bumped application version to v2.1.2
380+
381+
## v2.1.1 (25/10/2025)
382+
No major features; maintenance and polish:
383+
- Added "Check for Updates" feature
384+
385+
### v1.3.1 (25/10/2025)
386+
- Security patch (XSS) applied in v1.3.1 only.
387+
- Patched a Cross-Site Scripting (XSS) vulnerability that affected older code paths.
388+
- Security Mode was enabled as part of that v1.3.1 patch to mitigate risk.
389+
390+
### v1.3 (25/10/2025)
391+
- Improvements and bug fixes for legacy apps.
392+
- Added Find and Replace.
393+
394+
### v2.1 (23/10/2025)
395+
- New editor features and quality-of-life improvements.
396+
- HR, Task items, Table alignment, Subscript, Superscript, and more.
397+
398+
### v2.0.5 (01/10/2025)
399+
- UI and layout updates; help/changelog layouts improved.
400+
- Various dark-mode fixes.
401+
402+
### v2.0.4 (25/09/2025)
403+
- Popup UI changed to be modular and centered; escaping and list fixes.
404+
405+
### v2.0.3 (24/09/2025)
406+
- Added regex search/replace with flags and preview highlighting.
407+
408+
### v2.0.2 (21/09/2025)
409+
- Find and Replace support.
410+
411+
### v2.0.1 (19/09/2025)
412+
- Added support for .txt files; fixed split-mode resize bug.
413+
414+
### v2.0 (18/09/2025)
415+
- Major internal restructuring.
416+
417+
### v1.2 (17/09/2025)
418+
- Fixed table formatting issues.
419+
420+
### v1.1 (16/09/2025)
421+
- New icon design and minor bug fixes.
422+
423+
### v1.0 (14/01/2024)
424+
- Original Markdown Editor (forked from Lancer Fan Club Forums).`;
361425

362426
// Toggle dark mode
363427
function toggleDarkMode() {
@@ -380,10 +444,10 @@ <h3 id="dialog-title" style="margin-top:0;margin-bottom:15px;font-size:16px;">In
380444
document.body.classList.add('dark-mode');
381445
}
382446
// Set up event listeners
383-
editor.addEventListener('input', function() {
447+
editor.addEventListener('input', function(e) {
384448
updatePreview();
385449
updateStatusBar();
386-
saveToUndoStack();
450+
saveToUndoStack(e);
387451
});
388452

389453
editor.addEventListener('keydown', function(e) {
@@ -397,8 +461,16 @@ <h3 id="dialog-title" style="margin-top:0;margin-bottom:15px;font-size:16px;">In
397461
document.getElementById('btn-new').addEventListener('click', newFile);
398462
document.getElementById('btn-open').addEventListener('click', openFile);
399463
document.getElementById('btn-save').addEventListener('click', saveFile);
400-
document.getElementById('btn-undo').addEventListener('click', undo);
401-
document.getElementById('btn-redo').addEventListener('click', redo);
464+
const undoBtn = document.getElementById('btn-undo');
465+
const redoBtn = document.getElementById('btn-redo');
466+
undoBtn.addEventListener('click', undo);
467+
redoBtn.addEventListener('click', redo);
468+
// Show history dropdown on hover
469+
undoBtn.addEventListener('mouseenter', function() { showHistoryDropdown('undo', undoBtn); });
470+
redoBtn.addEventListener('mouseenter', function() { showHistoryDropdown('redo', redoBtn); });
471+
// Remove dropdown on mouseleave (with small delay to allow click)
472+
undoBtn.addEventListener('mouseleave', function() { setTimeout(() => { const d = document.getElementById('undo-history-dropdown'); if (d) d.remove(); }, 300); });
473+
redoBtn.addEventListener('mouseleave', function() { setTimeout(() => { const d = document.getElementById('redo-history-dropdown'); if (d) d.remove(); }, 300); });
402474
document.getElementById('btn-bold').addEventListener('click', function() { insertMarkdown('**', '**'); });
403475
document.getElementById('btn-italic').addEventListener('click', function() { insertMarkdown('*', '*'); });
404476
document.getElementById('btn-strike').addEventListener('click', function() { insertMarkdown('~~', '~~'); });
@@ -1167,12 +1239,144 @@ <h3 id="dialog-title" style="margin-top:0;margin-bottom:15px;font-size:16px;">In
11671239
}
11681240

11691241
// Undo/Redo functionality
1170-
function saveToUndoStack() {
1171-
undoStack.push(editor.value);
1172-
if (undoStack.length > 50) {
1173-
undoStack.shift();
1242+
// Helper to summarize a state for dropdown
1243+
function summarizeState(text) {
1244+
let firstLine = text.split('\n')[0];
1245+
if (firstLine.length > 40) firstLine = firstLine.slice(0, 37) + '...';
1246+
return firstLine || '[Empty]';
1247+
}
1248+
1249+
// Create and show history dropdown
1250+
function showHistoryDropdown(type, anchorBtn) {
1251+
let stack = type === 'undo' ? undoStack : redoStack;
1252+
if (!stack.length) return;
1253+
const maxItems = 15;
1254+
const items = stack.slice(-maxItems).map(summarizeState);
1255+
let dropdown = document.getElementById(type + '-history-dropdown');
1256+
if (dropdown) dropdown.remove();
1257+
dropdown = document.createElement('div');
1258+
dropdown.id = type + '-history-dropdown';
1259+
dropdown.style.position = 'absolute';
1260+
dropdown.style.zIndex = 2000;
1261+
dropdown.style.background = document.body.classList.contains('dark-mode') ? '#23272a' : 'white';
1262+
dropdown.style.border = document.body.classList.contains('dark-mode') ? '1px solid #444' : '1px solid #ccc';
1263+
dropdown.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)';
1264+
dropdown.style.fontSize = '13px';
1265+
dropdown.style.minWidth = '220px';
1266+
dropdown.style.maxHeight = '320px';
1267+
dropdown.style.overflowY = 'auto';
1268+
dropdown.style.cursor = 'pointer';
1269+
dropdown.style.padding = '4px 0';
1270+
dropdown.style.color = document.body.classList.contains('dark-mode') ? '#e0e0e0' : '#222';
1271+
// Position below anchorBtn
1272+
const rect = anchorBtn.getBoundingClientRect();
1273+
dropdown.style.left = rect.left + 'px';
1274+
dropdown.style.top = (rect.bottom + window.scrollY) + 'px';
1275+
items.forEach((summary, idx) => {
1276+
const item = document.createElement('div');
1277+
item.textContent = (stack.length - maxItems + idx + 1) + '. ' + summary;
1278+
item.style.padding = '4px 12px';
1279+
item.style.borderBottom = document.body.classList.contains('dark-mode') ? '1px solid #444' : '1px solid #eee';
1280+
item.onmouseenter = () => { item.style.background = document.body.classList.contains('dark-mode') ? '#2d3136' : '#f0f0f0'; };
1281+
item.onmouseleave = () => { item.style.background = document.body.classList.contains('dark-mode') ? '#23272a' : 'white'; };
1282+
item.onclick = () => {
1283+
if (type === 'undo') {
1284+
// Jump to selected undo state
1285+
const targetIdx = stack.length - maxItems + idx;
1286+
if (targetIdx >= 0 && targetIdx < stack.length) {
1287+
// Move all newer states to redoStack
1288+
while (undoStack.length > targetIdx + 1) {
1289+
redoStack.push(undoStack.pop());
1290+
}
1291+
editor.value = undoStack[undoStack.length - 1];
1292+
updatePreview();
1293+
updateStatusBar();
1294+
lastUndoValue = editor.value;
1295+
}
1296+
} else {
1297+
// Jump to selected redo state
1298+
const targetIdx = stack.length - maxItems + idx;
1299+
if (targetIdx >= 0 && targetIdx < stack.length) {
1300+
// Move all older states to undoStack
1301+
while (redoStack.length > targetIdx + 1) {
1302+
undoStack.push(redoStack.pop());
1303+
}
1304+
editor.value = redoStack[redoStack.length - 1];
1305+
updatePreview();
1306+
updateStatusBar();
1307+
lastUndoValue = editor.value;
1308+
}
1309+
}
1310+
dropdown.remove();
1311+
};
1312+
dropdown.appendChild(item);
1313+
});
1314+
document.body.appendChild(dropdown);
1315+
// Keep dropdown visible while hovering the anchor button or the dropdown itself.
1316+
// Close when both are left, or on click outside. Small delay prevents flicker when moving between button and menu.
1317+
let closeTimeout = null;
1318+
const scheduleClose = () => {
1319+
if (closeTimeout) clearTimeout(closeTimeout);
1320+
closeTimeout = setTimeout(() => {
1321+
try {
1322+
if (!anchorBtn.matches(':hover') && !dropdown.matches(':hover')) {
1323+
if (dropdown) dropdown.remove();
1324+
document.removeEventListener('mousedown', outsideHandler);
1325+
}
1326+
} catch (err) { /* element may be gone */ }
1327+
}, 160);
1328+
};
1329+
const cancelClose = () => { if (closeTimeout) { clearTimeout(closeTimeout); closeTimeout = null; } };
1330+
1331+
const outsideHandler = (e) => {
1332+
if (!dropdown.contains(e.target) && e.target !== anchorBtn) {
1333+
if (dropdown) dropdown.remove();
1334+
document.removeEventListener('mousedown', outsideHandler);
1335+
}
1336+
};
1337+
1338+
anchorBtn.addEventListener('mouseleave', scheduleClose);
1339+
anchorBtn.addEventListener('mouseenter', cancelClose);
1340+
dropdown.addEventListener('mouseleave', scheduleClose);
1341+
dropdown.addEventListener('mouseenter', cancelClose);
1342+
// Also close when clicking outside
1343+
setTimeout(() => { document.addEventListener('mousedown', outsideHandler); }, 0);
1344+
}
1345+
// (removed duplicate header)
1346+
function saveToUndoStack(e) {
1347+
const value = editor.value;
1348+
// If shift is held, always save whole text immediately
1349+
if (e && e.shiftKey) {
1350+
undoStack.push(value);
1351+
if (undoStack.length > UNDO_STACK_LIMIT) undoStack.shift();
1352+
redoStack = [];
1353+
lastUndoValue = value;
1354+
lastUndoSaveTime = Date.now();
1355+
return;
11741356
}
1175-
redoStack = [];
1357+
// Throttle saves
1358+
if (undoTimer) clearTimeout(undoTimer);
1359+
undoTimer = setTimeout(() => {
1360+
// Detect incomplete word/typo: if last char is not space or punctuation, or if last word is not in dictionary
1361+
let incomplete = false;
1362+
const lastWordMatch = value.match(/(\w+)$/);
1363+
if (lastWordMatch) {
1364+
const lastWord = lastWordMatch[1];
1365+
// Simple typo detection: word length > 2 and not in basic dictionary
1366+
const basicDict = ['the','and','for','with','this','that','from','have','will','can','are','was','but','not','all','any','one','two','three','four','five','six','seven','eight','nine','zero','markdown','editor','bold','italic','code','list','table','image','link','quote','task','footnote','heading','feature','example','function','block','write','file','open','save','undo','redo','preview','split','pane','view','help','update','changelog','about','menu','toolbar','button','status','line','word','character','text','content','background','color','font','size','align','left','center','right','checkbox','item','definition','hr','horizontal','rule','subscript','superscript','insert','select','dialog','popup','alert','find','replace','history','search','input','output','syntax','highlight','real','time','interface','operations','welcome','happy'];
1367+
if (lastWord.length > 2 && !basicDict.includes(lastWord.toLowerCase())) {
1368+
incomplete = true;
1369+
}
1370+
}
1371+
// Save if incomplete word/typo or value changed
1372+
if (incomplete || value !== lastUndoValue) {
1373+
undoStack.push(value);
1374+
if (undoStack.length > UNDO_STACK_LIMIT) undoStack.shift();
1375+
redoStack = [];
1376+
lastUndoValue = value;
1377+
lastUndoSaveTime = Date.now();
1378+
}
1379+
}, undoThrottleMs);
11761380
}
11771381

11781382
function undo() {
@@ -1181,16 +1385,19 @@ <h3 id="dialog-title" style="margin-top:0;margin-bottom:15px;font-size:16px;">In
11811385
editor.value = undoStack[undoStack.length - 1] || '';
11821386
updatePreview();
11831387
updateStatusBar();
1388+
lastUndoValue = editor.value;
11841389
}
11851390
}
11861391

11871392
function redo() {
11881393
if (redoStack.length > 0) {
11891394
const redoValue = redoStack.pop();
11901395
undoStack.push(redoValue);
1396+
if (undoStack.length > UNDO_STACK_LIMIT) undoStack.shift();
11911397
editor.value = redoValue;
11921398
updatePreview();
11931399
updateStatusBar();
1400+
lastUndoValue = redoValue;
11941401
}
11951402
}
11961403

@@ -1485,6 +1692,13 @@ <h3 id="dialog-title" style="margin-top:0;margin-bottom:15px;font-size:16px;">In
14851692

14861693
// Menu functions (placeholder implementations)
14871694
function showAbout() {
1695+
// Render local changelogs into the popup using existing markdown parser
1696+
const el = document.getElementById('changelog-content');
1697+
try {
1698+
if (el) el.innerHTML = parseMarkdown(LOCAL_CHANGELOG_MD);
1699+
} catch (err) {
1700+
if (el) el.textContent = LOCAL_CHANGELOG_MD;
1701+
}
14881702
document.getElementById('changelog-popup').style.display = 'block';
14891703
document.getElementById('popup-overlay').style.display = 'block';
14901704
}
@@ -1505,16 +1719,23 @@ <h3 id="dialog-title" style="margin-top:0;margin-bottom:15px;font-size:16px;">In
15051719
// Hide help popup
15061720
document.addEventListener('DOMContentLoaded', function() {
15071721
var closeBtn = document.getElementById('help-popup-close');
1508-
if (closeBtn) closeBtn.onclick = function() {
1509-
document.getElementById('help-popup').style.display = 'none';
1510-
document.getElementById('popup-overlay').style.display = 'none';
1511-
};
1722+
if (closeBtn) closeBtn.onclick = function() {
1723+
document.getElementById('help-popup').style.display = 'none';
1724+
document.getElementById('popup-overlay').style.display = 'none';
1725+
};
1726+
var changelogClose = document.getElementById('changelog-popup-close');
1727+
if (changelogClose) changelogClose.onclick = function() {
1728+
document.getElementById('changelog-popup').style.display = 'none';
1729+
document.getElementById('popup-overlay').style.display = 'none';
1730+
};
15121731
// Also close when clicking overlay
15131732
var overlay = document.getElementById('popup-overlay');
1514-
if (overlay) overlay.addEventListener('click', function() {
1515-
document.getElementById('help-popup').style.display = 'none';
1516-
overlay.style.display = 'none';
1517-
});
1733+
if (overlay) overlay.addEventListener('click', function() {
1734+
// Close any open popups that use the overlay
1735+
var help = document.getElementById('help-popup'); if (help) help.style.display = 'none';
1736+
var changelog = document.getElementById('changelog-popup'); if (changelog) changelog.style.display = 'none';
1737+
overlay.style.display = 'none';
1738+
});
15181739
});
15191740

15201741
// Handle image info badge clicks

0 commit comments

Comments
 (0)