overtype 1.1.0 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/overtype.esm.js +203 -1327
- package/dist/overtype.esm.js.map +4 -4
- package/dist/overtype.js +203 -1327
- package/dist/overtype.js.map +4 -4
- package/dist/overtype.min.js +112 -90
- package/package.json +1 -2
- package/src/link-tooltip.js +75 -149
- package/src/overtype.js +147 -13
- package/src/parser.js +20 -2
- package/src/styles.js +29 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "overtype",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "A lightweight markdown editor library with perfect WYSIWYG alignment using an invisible textarea overlay",
|
|
5
5
|
"main": "dist/overtype.js",
|
|
6
6
|
"module": "dist/overtype.esm.js",
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
},
|
|
47
47
|
"homepage": "https://github.com/panphora/overtype#readme",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@floating-ui/dom": "^1.7.3",
|
|
50
49
|
"markdown-actions": "^1.1.2"
|
|
51
50
|
}
|
|
52
51
|
}
|
package/src/link-tooltip.js
CHANGED
|
@@ -1,73 +1,90 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Link Tooltip -
|
|
2
|
+
* Link Tooltip - CSS Anchor Positioning with index-based anchors
|
|
3
3
|
* Shows a clickable tooltip when cursor is within a link
|
|
4
|
+
* Uses CSS anchor positioning with dynamically selected anchor
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import { computePosition, flip, shift, offset } from '@floating-ui/dom';
|
|
7
|
-
|
|
8
7
|
export class LinkTooltip {
|
|
9
8
|
constructor(editor) {
|
|
10
9
|
this.editor = editor;
|
|
11
10
|
this.tooltip = null;
|
|
12
11
|
this.currentLink = null;
|
|
13
12
|
this.hideTimeout = null;
|
|
14
|
-
this.isMouseInTooltip = false;
|
|
15
|
-
this.isMouseInLink = false;
|
|
16
13
|
|
|
17
14
|
this.init();
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
init() {
|
|
18
|
+
// Check for CSS anchor positioning support
|
|
19
|
+
const supportsAnchor =
|
|
20
|
+
CSS.supports('position-anchor: --x') &&
|
|
21
|
+
CSS.supports('position-area: center');
|
|
22
|
+
|
|
23
|
+
if (!supportsAnchor) {
|
|
24
|
+
// Don't show anything if not supported
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
// Create tooltip element
|
|
22
29
|
this.createTooltip();
|
|
23
30
|
|
|
24
31
|
// Listen for cursor position changes
|
|
25
32
|
this.editor.textarea.addEventListener('selectionchange', () => this.checkCursorPosition());
|
|
26
|
-
this.editor.textarea.addEventListener('input', () => this.checkCursorPosition());
|
|
27
33
|
this.editor.textarea.addEventListener('keyup', (e) => {
|
|
28
|
-
|
|
29
|
-
if (e.key.includes('Arrow')) {
|
|
34
|
+
if (e.key.includes('Arrow') || e.key === 'Home' || e.key === 'End') {
|
|
30
35
|
this.checkCursorPosition();
|
|
31
36
|
}
|
|
32
37
|
});
|
|
33
38
|
|
|
34
|
-
// Hide tooltip when scrolling
|
|
39
|
+
// Hide tooltip when typing or scrolling
|
|
40
|
+
this.editor.textarea.addEventListener('input', () => this.hide());
|
|
35
41
|
this.editor.textarea.addEventListener('scroll', () => this.hide());
|
|
36
42
|
|
|
37
|
-
//
|
|
38
|
-
this.tooltip.addEventListener('mouseenter', () =>
|
|
39
|
-
|
|
40
|
-
this.cancelHide();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
this.tooltip.addEventListener('mouseleave', () => {
|
|
44
|
-
this.isMouseInTooltip = false;
|
|
45
|
-
this.scheduleHide();
|
|
46
|
-
});
|
|
43
|
+
// Keep tooltip visible on hover
|
|
44
|
+
this.tooltip.addEventListener('mouseenter', () => this.cancelHide());
|
|
45
|
+
this.tooltip.addEventListener('mouseleave', () => this.scheduleHide());
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
createTooltip() {
|
|
49
|
+
// Create tooltip element
|
|
50
50
|
this.tooltip = document.createElement('div');
|
|
51
51
|
this.tooltip.className = 'overtype-link-tooltip';
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
52
|
+
|
|
53
|
+
// Add CSS anchor positioning styles
|
|
54
|
+
const tooltipStyles = document.createElement('style');
|
|
55
|
+
tooltipStyles.textContent = `
|
|
56
|
+
@supports (position-anchor: --x) and (position-area: center) {
|
|
57
|
+
.overtype-link-tooltip {
|
|
58
|
+
position: absolute;
|
|
59
|
+
position-anchor: var(--target-anchor, --link-0);
|
|
60
|
+
position-area: block-end center;
|
|
61
|
+
margin-top: 8px;
|
|
62
|
+
|
|
63
|
+
background: #333;
|
|
64
|
+
color: white;
|
|
65
|
+
padding: 6px 10px;
|
|
66
|
+
border-radius: 16px;
|
|
67
|
+
font-size: 12px;
|
|
68
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
69
|
+
display: none;
|
|
70
|
+
z-index: 10000;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
73
|
+
max-width: 300px;
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
text-overflow: ellipsis;
|
|
77
|
+
|
|
78
|
+
position-try: most-width block-end inline-end, flip-inline, block-start center;
|
|
79
|
+
position-visibility: anchors-visible;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.overtype-link-tooltip.visible {
|
|
83
|
+
display: flex;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
70
86
|
`;
|
|
87
|
+
document.head.appendChild(tooltipStyles);
|
|
71
88
|
|
|
72
89
|
// Add link icon and text container
|
|
73
90
|
this.tooltip.innerHTML = `
|
|
@@ -90,8 +107,8 @@ export class LinkTooltip {
|
|
|
90
107
|
}
|
|
91
108
|
});
|
|
92
109
|
|
|
93
|
-
// Append to
|
|
94
|
-
|
|
110
|
+
// Append tooltip to editor container
|
|
111
|
+
this.editor.container.appendChild(this.tooltip);
|
|
95
112
|
}
|
|
96
113
|
|
|
97
114
|
checkCursorPosition() {
|
|
@@ -99,19 +116,13 @@ export class LinkTooltip {
|
|
|
99
116
|
const text = this.editor.textarea.value;
|
|
100
117
|
|
|
101
118
|
// Find if cursor is within a markdown link
|
|
102
|
-
const
|
|
119
|
+
const linkInfo = this.findLinkAtPosition(text, cursorPos);
|
|
103
120
|
|
|
104
|
-
if (
|
|
105
|
-
this.
|
|
106
|
-
|
|
107
|
-
this.currentLink.start !== link.start ||
|
|
108
|
-
this.currentLink.url !== link.url) {
|
|
109
|
-
// New link or different link
|
|
110
|
-
this.show(link);
|
|
121
|
+
if (linkInfo) {
|
|
122
|
+
if (!this.currentLink || this.currentLink.url !== linkInfo.url || this.currentLink.index !== linkInfo.index) {
|
|
123
|
+
this.show(linkInfo);
|
|
111
124
|
}
|
|
112
125
|
} else {
|
|
113
|
-
// Not in a link
|
|
114
|
-
this.isMouseInLink = false;
|
|
115
126
|
this.scheduleHide();
|
|
116
127
|
}
|
|
117
128
|
}
|
|
@@ -120,6 +131,7 @@ export class LinkTooltip {
|
|
|
120
131
|
// Regex to find markdown links: [text](url)
|
|
121
132
|
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
122
133
|
let match;
|
|
134
|
+
let linkIndex = 0;
|
|
123
135
|
|
|
124
136
|
while ((match = linkRegex.exec(text)) !== null) {
|
|
125
137
|
const start = match.index;
|
|
@@ -127,128 +139,42 @@ export class LinkTooltip {
|
|
|
127
139
|
|
|
128
140
|
if (position >= start && position <= end) {
|
|
129
141
|
return {
|
|
130
|
-
start: start,
|
|
131
|
-
end: end,
|
|
132
142
|
text: match[1],
|
|
133
143
|
url: match[2],
|
|
134
|
-
|
|
144
|
+
index: linkIndex,
|
|
145
|
+
start: start,
|
|
146
|
+
end: end
|
|
135
147
|
};
|
|
136
148
|
}
|
|
149
|
+
linkIndex++;
|
|
137
150
|
}
|
|
138
151
|
|
|
139
152
|
return null;
|
|
140
153
|
}
|
|
141
154
|
|
|
142
|
-
|
|
143
|
-
this.currentLink =
|
|
155
|
+
show(linkInfo) {
|
|
156
|
+
this.currentLink = linkInfo;
|
|
144
157
|
this.cancelHide();
|
|
145
158
|
|
|
146
159
|
// Update tooltip content
|
|
147
160
|
const urlSpan = this.tooltip.querySelector('.overtype-link-tooltip-url');
|
|
148
|
-
urlSpan.textContent =
|
|
161
|
+
urlSpan.textContent = linkInfo.url;
|
|
149
162
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (linkElement) {
|
|
154
|
-
// Use the link element as reference
|
|
155
|
-
await this.positionTooltip(linkElement);
|
|
156
|
-
} else {
|
|
157
|
-
// Fallback: position based on cursor
|
|
158
|
-
await this.positionTooltipAtCursor(link);
|
|
159
|
-
}
|
|
163
|
+
// Set the CSS variable to point to the correct anchor
|
|
164
|
+
this.tooltip.style.setProperty('--target-anchor', `--link-${linkInfo.index}`);
|
|
160
165
|
|
|
161
|
-
// Show tooltip
|
|
162
|
-
this.tooltip.
|
|
163
|
-
// Force reflow
|
|
164
|
-
this.tooltip.offsetHeight;
|
|
165
|
-
this.tooltip.style.opacity = '1';
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
findLinkElementInPreview(link) {
|
|
169
|
-
// Find the corresponding link element in the preview
|
|
170
|
-
const links = this.editor.preview.querySelectorAll('a');
|
|
171
|
-
|
|
172
|
-
for (const linkEl of links) {
|
|
173
|
-
// Check if this link contains our URL
|
|
174
|
-
const urlSpans = linkEl.querySelectorAll('.syntax-marker');
|
|
175
|
-
for (const span of urlSpans) {
|
|
176
|
-
if (span.textContent === link.url) {
|
|
177
|
-
return linkEl;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
async positionTooltip(referenceEl) {
|
|
186
|
-
const { x, y } = await computePosition(referenceEl, this.tooltip, {
|
|
187
|
-
placement: 'bottom',
|
|
188
|
-
middleware: [
|
|
189
|
-
offset(6),
|
|
190
|
-
flip(),
|
|
191
|
-
shift({ padding: 10 })
|
|
192
|
-
]
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
Object.assign(this.tooltip.style, {
|
|
196
|
-
left: `${x}px`,
|
|
197
|
-
top: `${y}px`
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
async positionTooltipAtCursor(link) {
|
|
202
|
-
// Get cursor position in the textarea
|
|
203
|
-
const textarea = this.editor.textarea;
|
|
204
|
-
|
|
205
|
-
// Create a temporary element to measure text position
|
|
206
|
-
const measurer = document.createElement('div');
|
|
207
|
-
measurer.style.cssText = window.getComputedStyle(textarea).cssText;
|
|
208
|
-
measurer.style.position = 'absolute';
|
|
209
|
-
measurer.style.visibility = 'hidden';
|
|
210
|
-
measurer.style.whiteSpace = 'pre-wrap';
|
|
211
|
-
measurer.style.wordWrap = 'break-word';
|
|
212
|
-
|
|
213
|
-
// Get text up to cursor
|
|
214
|
-
const textBeforeCursor = textarea.value.substring(0, link.start + link.fullMatch.length / 2);
|
|
215
|
-
measurer.textContent = textBeforeCursor;
|
|
216
|
-
|
|
217
|
-
document.body.appendChild(measurer);
|
|
218
|
-
const textHeight = measurer.offsetHeight;
|
|
219
|
-
document.body.removeChild(measurer);
|
|
220
|
-
|
|
221
|
-
// Get textarea position
|
|
222
|
-
const rect = textarea.getBoundingClientRect();
|
|
223
|
-
|
|
224
|
-
// Estimate position (this is approximate)
|
|
225
|
-
const x = rect.left + rect.width / 2;
|
|
226
|
-
const y = rect.top + Math.min(textHeight, rect.height - 50);
|
|
227
|
-
|
|
228
|
-
Object.assign(this.tooltip.style, {
|
|
229
|
-
left: `${x}px`,
|
|
230
|
-
top: `${y}px`,
|
|
231
|
-
transform: 'translateX(-50%)'
|
|
232
|
-
});
|
|
166
|
+
// Show tooltip (CSS anchor positioning handles the rest)
|
|
167
|
+
this.tooltip.classList.add('visible');
|
|
233
168
|
}
|
|
234
169
|
|
|
235
170
|
hide() {
|
|
236
|
-
this.tooltip.
|
|
237
|
-
|
|
238
|
-
if (this.tooltip.style.opacity === '0') {
|
|
239
|
-
this.tooltip.style.display = 'none';
|
|
240
|
-
this.currentLink = null;
|
|
241
|
-
}
|
|
242
|
-
}, 200);
|
|
171
|
+
this.tooltip.classList.remove('visible');
|
|
172
|
+
this.currentLink = null;
|
|
243
173
|
}
|
|
244
174
|
|
|
245
175
|
scheduleHide() {
|
|
246
176
|
this.cancelHide();
|
|
247
|
-
this.hideTimeout = setTimeout(() =>
|
|
248
|
-
if (!this.isMouseInTooltip && !this.isMouseInLink) {
|
|
249
|
-
this.hide();
|
|
250
|
-
}
|
|
251
|
-
}, 300);
|
|
177
|
+
this.hideTimeout = setTimeout(() => this.hide(), 300);
|
|
252
178
|
}
|
|
253
179
|
|
|
254
180
|
cancelHide() {
|
package/src/overtype.js
CHANGED
|
@@ -144,8 +144,14 @@ class OverType {
|
|
|
144
144
|
lineHeight: 1.5
|
|
145
145
|
},
|
|
146
146
|
|
|
147
|
+
// Native textarea properties
|
|
148
|
+
textareaProps: {},
|
|
149
|
+
|
|
147
150
|
// Behavior
|
|
148
151
|
autofocus: false,
|
|
152
|
+
autoResize: false, // Auto-expand height with content
|
|
153
|
+
minHeight: '100px', // Minimum height for autoResize mode
|
|
154
|
+
maxHeight: null, // Maximum height for autoResize mode (null = unlimited)
|
|
149
155
|
placeholder: 'Start typing...',
|
|
150
156
|
value: '',
|
|
151
157
|
|
|
@@ -307,10 +313,6 @@ class OverType {
|
|
|
307
313
|
this.wrapper = document.createElement('div');
|
|
308
314
|
this.wrapper.className = 'overtype-wrapper';
|
|
309
315
|
|
|
310
|
-
// Add stats wrapper class if stats are enabled
|
|
311
|
-
if (this.options.showStats) {
|
|
312
|
-
this.wrapper.classList.add('with-stats');
|
|
313
|
-
}
|
|
314
316
|
|
|
315
317
|
// Apply instance-specific styles via CSS custom properties
|
|
316
318
|
if (this.options.fontSize) {
|
|
@@ -330,6 +332,19 @@ class OverType {
|
|
|
330
332
|
this.textarea.className = 'overtype-input';
|
|
331
333
|
this.textarea.placeholder = this.options.placeholder;
|
|
332
334
|
this._configureTextarea();
|
|
335
|
+
|
|
336
|
+
// Apply any native textarea properties
|
|
337
|
+
if (this.options.textareaProps) {
|
|
338
|
+
Object.entries(this.options.textareaProps).forEach(([key, value]) => {
|
|
339
|
+
if (key === 'className' || key === 'class') {
|
|
340
|
+
this.textarea.className += ' ' + value;
|
|
341
|
+
} else if (key === 'style' && typeof value === 'object') {
|
|
342
|
+
Object.assign(this.textarea.style, value);
|
|
343
|
+
} else {
|
|
344
|
+
this.textarea.setAttribute(key, value);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
333
348
|
|
|
334
349
|
// Create preview div
|
|
335
350
|
this.preview = document.createElement('div');
|
|
@@ -340,19 +355,42 @@ class OverType {
|
|
|
340
355
|
this.wrapper.appendChild(this.textarea);
|
|
341
356
|
this.wrapper.appendChild(this.preview);
|
|
342
357
|
|
|
343
|
-
// Add
|
|
358
|
+
// Add wrapper to container first
|
|
359
|
+
this.container.appendChild(this.wrapper);
|
|
360
|
+
|
|
361
|
+
// Add stats bar at the end (bottom) if enabled
|
|
344
362
|
if (this.options.showStats) {
|
|
345
363
|
this.statsBar = document.createElement('div');
|
|
346
364
|
this.statsBar.className = 'overtype-stats';
|
|
347
|
-
this.
|
|
365
|
+
this.container.appendChild(this.statsBar);
|
|
348
366
|
this._updateStats();
|
|
349
367
|
}
|
|
350
368
|
|
|
351
|
-
// Add wrapper to container
|
|
352
|
-
this.container.appendChild(this.wrapper);
|
|
353
|
-
|
|
354
369
|
// Add container to element
|
|
355
370
|
this.element.appendChild(this.container);
|
|
371
|
+
|
|
372
|
+
// Debug logging
|
|
373
|
+
if (window.location.pathname.includes('demo.html')) {
|
|
374
|
+
console.log('_createDOM completed:', {
|
|
375
|
+
elementId: this.element.id,
|
|
376
|
+
autoResize: this.options.autoResize,
|
|
377
|
+
containerClasses: this.container.className,
|
|
378
|
+
hasStats: !!this.statsBar,
|
|
379
|
+
hasToolbar: this.options.toolbar
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Setup auto-resize if enabled
|
|
384
|
+
if (this.options.autoResize) {
|
|
385
|
+
this._setupAutoResize();
|
|
386
|
+
} else {
|
|
387
|
+
// Ensure auto-resize class is removed if not using auto-resize
|
|
388
|
+
this.container.classList.remove('overtype-auto-resize');
|
|
389
|
+
|
|
390
|
+
if (window.location.pathname.includes('demo.html')) {
|
|
391
|
+
console.log('Removed auto-resize class from:', this.element.id);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
356
394
|
}
|
|
357
395
|
|
|
358
396
|
/**
|
|
@@ -378,6 +416,16 @@ class OverType {
|
|
|
378
416
|
if (this.options.autofocus) {
|
|
379
417
|
this.textarea.focus();
|
|
380
418
|
}
|
|
419
|
+
|
|
420
|
+
// Setup or remove auto-resize
|
|
421
|
+
if (this.options.autoResize) {
|
|
422
|
+
if (!this.container.classList.contains('overtype-auto-resize')) {
|
|
423
|
+
this._setupAutoResize();
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
// Ensure auto-resize class is removed
|
|
427
|
+
this.container.classList.remove('overtype-auto-resize');
|
|
428
|
+
}
|
|
381
429
|
|
|
382
430
|
// Update preview with initial content
|
|
383
431
|
this.updatePreview();
|
|
@@ -554,6 +602,11 @@ class OverType {
|
|
|
554
602
|
setValue(value) {
|
|
555
603
|
this.textarea.value = value;
|
|
556
604
|
this.updatePreview();
|
|
605
|
+
|
|
606
|
+
// Update height if auto-resize is enabled
|
|
607
|
+
if (this.options.autoResize) {
|
|
608
|
+
this._updateAutoHeight();
|
|
609
|
+
}
|
|
557
610
|
}
|
|
558
611
|
|
|
559
612
|
|
|
@@ -629,6 +682,89 @@ class OverType {
|
|
|
629
682
|
}
|
|
630
683
|
}
|
|
631
684
|
|
|
685
|
+
/**
|
|
686
|
+
* Setup auto-resize functionality
|
|
687
|
+
* @private
|
|
688
|
+
*/
|
|
689
|
+
_setupAutoResize() {
|
|
690
|
+
// Add auto-resize class for styling
|
|
691
|
+
this.container.classList.add('overtype-auto-resize');
|
|
692
|
+
|
|
693
|
+
// Store previous height for comparison
|
|
694
|
+
this.previousHeight = null;
|
|
695
|
+
|
|
696
|
+
// Initial height update
|
|
697
|
+
this._updateAutoHeight();
|
|
698
|
+
|
|
699
|
+
// Listen for input events
|
|
700
|
+
this.textarea.addEventListener('input', () => this._updateAutoHeight());
|
|
701
|
+
|
|
702
|
+
// Listen for window resize
|
|
703
|
+
window.addEventListener('resize', () => this._updateAutoHeight());
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Update height based on scrollHeight
|
|
708
|
+
* @private
|
|
709
|
+
*/
|
|
710
|
+
_updateAutoHeight() {
|
|
711
|
+
if (!this.options.autoResize) return;
|
|
712
|
+
|
|
713
|
+
const textarea = this.textarea;
|
|
714
|
+
const preview = this.preview;
|
|
715
|
+
const wrapper = this.wrapper;
|
|
716
|
+
|
|
717
|
+
// Get computed styles
|
|
718
|
+
const computed = window.getComputedStyle(textarea);
|
|
719
|
+
const paddingTop = parseFloat(computed.paddingTop);
|
|
720
|
+
const paddingBottom = parseFloat(computed.paddingBottom);
|
|
721
|
+
|
|
722
|
+
// Store scroll positions
|
|
723
|
+
const scrollTop = textarea.scrollTop;
|
|
724
|
+
|
|
725
|
+
// Reset height to get accurate scrollHeight
|
|
726
|
+
textarea.style.setProperty('height', 'auto', 'important');
|
|
727
|
+
|
|
728
|
+
// Calculate new height based on scrollHeight
|
|
729
|
+
let newHeight = textarea.scrollHeight;
|
|
730
|
+
|
|
731
|
+
// Apply min height constraint
|
|
732
|
+
if (this.options.minHeight) {
|
|
733
|
+
const minHeight = parseInt(this.options.minHeight);
|
|
734
|
+
newHeight = Math.max(newHeight, minHeight);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Apply max height constraint
|
|
738
|
+
let overflow = 'hidden';
|
|
739
|
+
if (this.options.maxHeight) {
|
|
740
|
+
const maxHeight = parseInt(this.options.maxHeight);
|
|
741
|
+
if (newHeight > maxHeight) {
|
|
742
|
+
newHeight = maxHeight;
|
|
743
|
+
overflow = 'auto';
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Apply the new height to all elements with !important to override base styles
|
|
748
|
+
const heightPx = newHeight + 'px';
|
|
749
|
+
textarea.style.setProperty('height', heightPx, 'important');
|
|
750
|
+
textarea.style.setProperty('overflow-y', overflow, 'important');
|
|
751
|
+
|
|
752
|
+
preview.style.setProperty('height', heightPx, 'important');
|
|
753
|
+
preview.style.setProperty('overflow-y', overflow, 'important');
|
|
754
|
+
|
|
755
|
+
wrapper.style.setProperty('height', heightPx, 'important');
|
|
756
|
+
|
|
757
|
+
// Restore scroll position
|
|
758
|
+
textarea.scrollTop = scrollTop;
|
|
759
|
+
preview.scrollTop = scrollTop;
|
|
760
|
+
|
|
761
|
+
// Track if height changed
|
|
762
|
+
if (this.previousHeight !== newHeight) {
|
|
763
|
+
this.previousHeight = newHeight;
|
|
764
|
+
// Could dispatch a custom event here if needed
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
632
768
|
/**
|
|
633
769
|
* Show or hide stats bar
|
|
634
770
|
* @param {boolean} show - Whether to show stats
|
|
@@ -637,17 +773,15 @@ class OverType {
|
|
|
637
773
|
this.options.showStats = show;
|
|
638
774
|
|
|
639
775
|
if (show && !this.statsBar) {
|
|
640
|
-
// Create stats bar
|
|
776
|
+
// Create stats bar (add to container, not wrapper)
|
|
641
777
|
this.statsBar = document.createElement('div');
|
|
642
778
|
this.statsBar.className = 'overtype-stats';
|
|
643
|
-
this.
|
|
644
|
-
this.wrapper.classList.add('with-stats');
|
|
779
|
+
this.container.appendChild(this.statsBar);
|
|
645
780
|
this._updateStats();
|
|
646
781
|
} else if (!show && this.statsBar) {
|
|
647
782
|
// Remove stats bar
|
|
648
783
|
this.statsBar.remove();
|
|
649
784
|
this.statsBar = null;
|
|
650
|
-
this.wrapper.classList.remove('with-stats');
|
|
651
785
|
}
|
|
652
786
|
}
|
|
653
787
|
|
package/src/parser.js
CHANGED
|
@@ -7,6 +7,16 @@
|
|
|
7
7
|
* - Markdown tokens remain visible but styled
|
|
8
8
|
*/
|
|
9
9
|
export class MarkdownParser {
|
|
10
|
+
// Track link index for anchor naming
|
|
11
|
+
static linkIndex = 0;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Reset link index (call before parsing a new document)
|
|
15
|
+
*/
|
|
16
|
+
static resetLinkIndex() {
|
|
17
|
+
this.linkIndex = 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
/**
|
|
11
21
|
* Escape HTML special characters
|
|
12
22
|
* @param {string} text - Raw text to escape
|
|
@@ -99,7 +109,9 @@ export class MarkdownParser {
|
|
|
99
109
|
* @returns {string|null} Parsed code fence or null
|
|
100
110
|
*/
|
|
101
111
|
static parseCodeBlock(html) {
|
|
102
|
-
if
|
|
112
|
+
// Only treat as code block if ``` is alone or followed by a language identifier
|
|
113
|
+
// This prevents ```some code``` from being treated as a code fence
|
|
114
|
+
if (html.match(/^```(\s*|\w*)$/)) {
|
|
103
115
|
return `<div><span class="code-fence">${html}</span></div>`;
|
|
104
116
|
}
|
|
105
117
|
return null;
|
|
@@ -143,7 +155,10 @@ export class MarkdownParser {
|
|
|
143
155
|
* @returns {string} HTML with link styling
|
|
144
156
|
*/
|
|
145
157
|
static parseLinks(html) {
|
|
146
|
-
return html.replace(/\[(.+?)\]\((.+?)\)/g,
|
|
158
|
+
return html.replace(/\[(.+?)\]\((.+?)\)/g, (match, text, url) => {
|
|
159
|
+
const anchorName = `--link-${this.linkIndex++}`;
|
|
160
|
+
return `<a href="${url}" style="anchor-name: ${anchorName}"><span class="syntax-marker">[</span>${text}<span class="syntax-marker">](</span><span class="syntax-marker">${url}</span><span class="syntax-marker">)</span></a>`;
|
|
161
|
+
});
|
|
147
162
|
}
|
|
148
163
|
|
|
149
164
|
/**
|
|
@@ -223,6 +238,9 @@ export class MarkdownParser {
|
|
|
223
238
|
* @returns {string} Parsed HTML
|
|
224
239
|
*/
|
|
225
240
|
static parse(text, activeLine = -1, showActiveLineRaw = false) {
|
|
241
|
+
// Reset link counter for each parse
|
|
242
|
+
this.resetLinkIndex();
|
|
243
|
+
|
|
226
244
|
const lines = text.split('\n');
|
|
227
245
|
const parsedLines = lines.map((line, index) => {
|
|
228
246
|
// Show raw markdown on active line if requested
|