release: v0.1.7 — editing experience: gutter, auto-format, bracket close, drag-drop, outline

feat(core): line number gutter with current line highlight
- .me-gutter rendered alongside textarea, sync-scrolls with content
- .me-gutter-active highlights the line containing the cursor
- config.lineNumbers (default true) to toggle

feat(core): smart Enter auto-formatting
- List continuation: '- ' / '1. ' auto-insert on Enter at end of list item
- Ordered list auto-increment: '1.' → '2.'
- Quote continuation: '> ' auto-insert on Enter at end of quote line
- Empty list/quote item: Enter removes the marker (end list)

feat(core): bracket/quote auto-close
- (), [], {}, <>, "", '', ``, **, __ auto-close pairs
- Selection wrapping: select text then press ( → wraps as (text)
- config.autoBrackets (default true) to toggle

feat(core): drag & drop file support
- Drop image files → auto base64 inline insert ![](data:...)
- Drop text/code files → insert file contents
- Drop external text from browser → insert at cursor

feat(core): outline/TOC panel
- Extracts headings from rendered HTML preview
- Nested tree with indentation by heading level
- Click to scroll-jump in both preview and textarea
- config.outline (default false) to toggle

style: gutter, outline panel, current line highlight CSS

test: 11 new tests for gutter, auto-format, bracket close, outline (532 total)

chore: bump version 0.1.6 → 0.1.7 across all files, site, types
This commit is contained in:
2026-07-24 10:33:12 +08:00
parent 45ed8d5351
commit 289b8e55a6
18 changed files with 539 additions and 24 deletions
+130
View File
@@ -1352,3 +1352,133 @@ describe('MarkdownEditor - v0.1.3 修复验证', () => {
expect(ed.isDisabled()).toBe(false);
});
});
// ============ v0.1.7 新增测试 ============
describe('MarkdownEditor - v0.1.7 行号装订线', () => {
test('lineNumbers 开启时创建 gutter', () => {
document.body.innerHTML = '';
const c = document.createElement('div');
document.body.appendChild(c);
const ed = new MarkdownEditor(c, { value: 'line1\nline2\nline3', lineNumbers: true });
expect(ed.gutter).not.toBeNull();
expect(ed.gutter.children.length).toBeGreaterThan(0);
ed.destroy();
});
test('lineNumbers 关闭时不显示', () => {
document.body.innerHTML = '';
const c = document.createElement('div');
document.body.appendChild(c);
const ed = new MarkdownEditor(c, { value: 'test', lineNumbers: false });
expect(ed.gutter).not.toBeNull();
expect(ed.gutter.style.display).toBe('none');
ed.destroy();
});
test('输入后行号更新', () => {
document.body.innerHTML = '';
const c = document.createElement('div');
document.body.appendChild(c);
const ed = new MarkdownEditor(c, { value: 'a\nb', lineNumbers: true });
ed.textarea.value = 'a\nb\nc';
ed.textarea.dispatchEvent(new Event('input', { bubbles: true }));
expect(ed.gutter.children.length).toBe(3);
ed.destroy();
});
});
describe('MarkdownEditor - v0.1.7 自动格式化', () => {
test('列表项行尾 Enter 自动延续 -', () => {
document.body.innerHTML = '';
const c = document.createElement('div');
document.body.appendChild(c);
const ed = new MarkdownEditor(c, { value: '- item' });
ed.textarea.setSelectionRange(6, 6); // 光标在行尾
ed.textarea.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
expect(ed.getValue()).toContain('- ');
ed.destroy();
});
test('空列表项 Enter 结束列表', () => {
document.body.innerHTML = '';
const c = document.createElement('div');
document.body.appendChild(c);
const ed = new MarkdownEditor(c, { value: '- ' });
ed.textarea.setSelectionRange(2, 2);
ed.textarea.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
expect(ed.getValue()).toBe('\n');
ed.destroy();
});
test('有序列表自动递增', () => {
document.body.innerHTML = '';
const c = document.createElement('div');
document.body.appendChild(c);
const ed = new MarkdownEditor(c, { value: '1. first' });
ed.textarea.setSelectionRange(8, 8);
ed.textarea.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
expect(ed.getValue()).toContain('2. ');
ed.destroy();
});
test('引用块延续 >', () => {
document.body.innerHTML = '';
const c = document.createElement('div');
document.body.appendChild(c);
const ed = new MarkdownEditor(c, { value: '> quote' });
ed.textarea.setSelectionRange(7, 7);
ed.textarea.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
expect(ed.getValue()).toContain('> ');
ed.destroy();
});
});
describe('MarkdownEditor - v0.1.7 括号自动闭合', () => {
test('无选区输入 ( 自动闭合', () => {
document.body.innerHTML = '';
const c = document.createElement('div');
document.body.appendChild(c);
const ed = new MarkdownEditor(c, { value: 'hello', autoBrackets: true });
ed.textarea.setSelectionRange(5, 5);
ed.textarea.dispatchEvent(new KeyboardEvent('keypress', { key: '(', bubbles: true }));
expect(ed.getValue()).toBe('hello()');
expect(ed.textarea.selectionStart).toBe(6); // 光标在括号内
ed.destroy();
});
test('有选区输入 ( 包裹选中文本', () => {
document.body.innerHTML = '';
const c = document.createElement('div');
document.body.appendChild(c);
const ed = new MarkdownEditor(c, { value: 'hello world', autoBrackets: true });
ed.textarea.setSelectionRange(0, 5);
ed.textarea.dispatchEvent(new KeyboardEvent('keypress', { key: '(', bubbles: true }));
expect(ed.getValue()).toBe('(hello) world');
ed.destroy();
});
test('autoBrackets 关闭时不闭合', () => {
document.body.innerHTML = '';
const c = document.createElement('div');
document.body.appendChild(c);
const ed = new MarkdownEditor(c, { value: 'hello', autoBrackets: false });
ed.textarea.setSelectionRange(5, 5);
// jsdom 不模拟原生键盘插入,验证不抛错即可
expect(() => {
ed.textarea.dispatchEvent(new KeyboardEvent('keypress', { key: '(', bubbles: true }));
}).not.toThrow();
ed.destroy();
});
});
describe('MarkdownEditor - v0.1.7 outline', () => {
test('outline 关闭时不创建面板', () => {
document.body.innerHTML = '';
const c = document.createElement('div');
document.body.appendChild(c);
const ed = new MarkdownEditor(c, { value: '# hi\n## there', outline: false, mode: 'split' });
expect(ed.el.querySelector('.me-outline')).toBeNull();
ed.destroy();
});
});