overtype 2.1.0 → 2.2.0
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 +26 -18
- package/dist/overtype-webcomponent.esm.js +3672 -2122
- package/dist/overtype-webcomponent.esm.js.map +4 -4
- package/dist/overtype-webcomponent.js +3667 -2117
- package/dist/overtype-webcomponent.js.map +4 -4
- package/dist/overtype-webcomponent.min.js +106 -93
- package/dist/overtype.cjs +3644 -2121
- package/dist/overtype.cjs.map +4 -4
- package/dist/overtype.d.ts +16 -0
- package/dist/overtype.esm.js +3644 -2121
- package/dist/overtype.esm.js.map +4 -4
- package/dist/overtype.js +3621 -2098
- package/dist/overtype.js.map +4 -4
- package/dist/overtype.min.js +107 -94
- package/package.json +4 -4
- package/src/icons.js +6 -0
- package/src/link-tooltip.js +40 -71
- package/src/overtype-webcomponent.js +32 -3
- package/src/overtype.d.ts +16 -0
- package/src/overtype.js +403 -38
- package/src/parser.js +9 -3
- package/src/shortcuts.js +11 -76
- package/src/styles.js +36 -28
- package/src/themes.js +14 -0
- package/src/toolbar-buttons.js +48 -12
- package/src/toolbar.js +39 -48
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "overtype",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "A lightweight markdown editor library with perfect WYSIWYG alignment using an invisible textarea overlay",
|
|
5
5
|
"main": "dist/overtype.cjs",
|
|
6
6
|
"module": "dist/overtype.esm.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"build:prod": "npm test && npm run build",
|
|
32
32
|
"dev": "http-server website -p 8080 -c-1",
|
|
33
33
|
"watch": "node scripts/build.js --watch",
|
|
34
|
-
"test": "node test/overtype.test.js && node test/preview-mode.test.js && node test/links.test.js && node test/api-methods.test.js && node test/comprehensive-alignment.test.js && node test/sanctuary-parsing.test.js && node test/mode-switching.test.js && node test/syntax-highlighting.test.js && node test/webcomponent.test.js && node test/custom-syntax.test.js && npm run test:types",
|
|
34
|
+
"test": "node test/overtype.test.js && node test/preview-mode.test.js && node test/links.test.js && node test/api-methods.test.js && node test/comprehensive-alignment.test.js && node test/sanctuary-parsing.test.js && node test/mode-switching.test.js && node test/syntax-highlighting.test.js && node test/webcomponent.test.js && node test/custom-syntax.test.js && node test/auto-theme.test.js && npm run test:types",
|
|
35
35
|
"test:main": "node test/overtype.test.js",
|
|
36
36
|
"test:preview": "node test/preview-mode.test.js",
|
|
37
37
|
"test:links": "node test/links.test.js",
|
|
@@ -45,8 +45,7 @@
|
|
|
45
45
|
"preversion": "npm test",
|
|
46
46
|
"size": "gzip-size dist/overtype.min.js",
|
|
47
47
|
"serve": "http-server -p 8080 -c-1",
|
|
48
|
-
"deploy:website": "npm run build"
|
|
49
|
-
"release": "./release.sh"
|
|
48
|
+
"deploy:website": "npm run build"
|
|
50
49
|
},
|
|
51
50
|
"keywords": [
|
|
52
51
|
"markdown",
|
|
@@ -88,6 +87,7 @@
|
|
|
88
87
|
},
|
|
89
88
|
"homepage": "https://github.com/panphora/overtype#readme",
|
|
90
89
|
"dependencies": {
|
|
90
|
+
"@floating-ui/dom": "^1.7.4",
|
|
91
91
|
"markdown-actions": "^1.1.2"
|
|
92
92
|
}
|
|
93
93
|
}
|
package/src/icons.js
CHANGED
|
@@ -72,6 +72,12 @@ export const taskListIcon = `<svg viewBox="0 0 18 18">
|
|
|
72
72
|
<polyline stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" points="2.65 9.5 3.5 10.5 5 8.5"></polyline>
|
|
73
73
|
</svg>`;
|
|
74
74
|
|
|
75
|
+
export const uploadIcon = `<svg viewBox="0 0 18 18">
|
|
76
|
+
<path stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.25 12.375v1.688A1.688 1.688 0 0 0 3.938 15.75h10.124a1.688 1.688 0 0 0 1.688-1.688V12.375"></path>
|
|
77
|
+
<path stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.063 6.188L9 2.25l3.938 3.938"></path>
|
|
78
|
+
<path stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 2.25v10.125"></path>
|
|
79
|
+
</svg>`;
|
|
80
|
+
|
|
75
81
|
export const eyeIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
76
82
|
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" fill="none"></path>
|
|
77
83
|
<circle cx="12" cy="12" r="3" fill="none"></circle>
|
package/src/link-tooltip.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Link Tooltip -
|
|
3
|
-
*
|
|
4
|
-
* Uses CSS anchor positioning for modern browsers, Floating UI for older browsers
|
|
2
|
+
* Link Tooltip - Shows a clickable tooltip when cursor is within a link
|
|
3
|
+
* Uses Floating UI for positioning across all browsers
|
|
5
4
|
*/
|
|
6
5
|
|
|
6
|
+
import { computePosition, offset, shift, flip } from '@floating-ui/dom';
|
|
7
|
+
|
|
7
8
|
export class LinkTooltip {
|
|
8
9
|
constructor(editor) {
|
|
9
10
|
this.editor = editor;
|
|
@@ -11,37 +12,13 @@ export class LinkTooltip {
|
|
|
11
12
|
this.currentLink = null;
|
|
12
13
|
this.hideTimeout = null;
|
|
13
14
|
this.visibilityChangeHandler = null;
|
|
14
|
-
this.
|
|
15
|
-
this.floatingUI = null;
|
|
15
|
+
this.isTooltipHovered = false;
|
|
16
16
|
|
|
17
17
|
this.init();
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
// Detect CSS anchor positioning support
|
|
22
|
-
const supportsAnchorPositioning = CSS.supports('position-anchor: --x') &&
|
|
23
|
-
CSS.supports('position-area: center');
|
|
24
|
-
|
|
25
|
-
// Load Floating UI if needed
|
|
26
|
-
if (!supportsAnchorPositioning) {
|
|
27
|
-
try {
|
|
28
|
-
// Use indirect eval to prevent bundler from processing the import
|
|
29
|
-
const importFn = new Function('url', 'return import(url)');
|
|
30
|
-
const { computePosition, offset, shift, flip } = await importFn(
|
|
31
|
-
'https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.7.4/+esm'
|
|
32
|
-
);
|
|
33
|
-
this.floatingUI = { computePosition, offset, shift, flip };
|
|
34
|
-
this.useFloatingUI = true;
|
|
35
|
-
} catch (error) {
|
|
36
|
-
// If dynamic import fails, tooltips simply won't show
|
|
37
|
-
console.warn('Failed to load Floating UI fallback:', error);
|
|
38
|
-
this.floatingUI = null;
|
|
39
|
-
this.useFloatingUI = false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
20
|
+
init() {
|
|
43
21
|
// Create tooltip element
|
|
44
|
-
// Note: Styles are now in the main stylesheet (styles.js) with @supports wrapper
|
|
45
22
|
this.createTooltip();
|
|
46
23
|
|
|
47
24
|
// Listen for cursor position changes
|
|
@@ -55,19 +32,19 @@ export class LinkTooltip {
|
|
|
55
32
|
// Hide tooltip when typing
|
|
56
33
|
this.editor.textarea.addEventListener('input', () => this.hide());
|
|
57
34
|
|
|
58
|
-
// Reposition
|
|
35
|
+
// Reposition tooltip when scrolling
|
|
59
36
|
this.editor.textarea.addEventListener('scroll', () => {
|
|
60
|
-
if (this.
|
|
61
|
-
|
|
62
|
-
this.showWithFloatingUI(this.currentLink);
|
|
63
|
-
} else {
|
|
64
|
-
// Hide for CSS anchor positioning (native browser behavior handles this)
|
|
65
|
-
this.hide();
|
|
37
|
+
if (this.currentLink) {
|
|
38
|
+
this.positionTooltip(this.currentLink);
|
|
66
39
|
}
|
|
67
40
|
});
|
|
68
41
|
|
|
69
|
-
// Hide tooltip when textarea loses focus
|
|
70
|
-
this.editor.textarea.addEventListener('blur', () =>
|
|
42
|
+
// Hide tooltip when textarea loses focus (unless hovering tooltip)
|
|
43
|
+
this.editor.textarea.addEventListener('blur', () => {
|
|
44
|
+
if (!this.isTooltipHovered) {
|
|
45
|
+
this.hide();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
71
48
|
|
|
72
49
|
// Hide tooltip when page loses visibility (tab switch, minimize, etc.)
|
|
73
50
|
this.visibilityChangeHandler = () => {
|
|
@@ -77,13 +54,18 @@ export class LinkTooltip {
|
|
|
77
54
|
};
|
|
78
55
|
document.addEventListener('visibilitychange', this.visibilityChangeHandler);
|
|
79
56
|
|
|
80
|
-
//
|
|
81
|
-
this.tooltip.addEventListener('mouseenter', () =>
|
|
57
|
+
// Track hover state to prevent hiding when clicking tooltip
|
|
58
|
+
this.tooltip.addEventListener('mouseenter', () => {
|
|
59
|
+
this.isTooltipHovered = true;
|
|
60
|
+
this.cancelHide();
|
|
61
|
+
});
|
|
62
|
+
this.tooltip.addEventListener('mouseleave', () => {
|
|
63
|
+
this.isTooltipHovered = false;
|
|
64
|
+
this.scheduleHide();
|
|
65
|
+
});
|
|
82
66
|
}
|
|
83
67
|
|
|
84
68
|
createTooltip() {
|
|
85
|
-
// Create tooltip element
|
|
86
|
-
// Styles are now included in the main stylesheet (styles.js)
|
|
87
69
|
this.tooltip = document.createElement('div');
|
|
88
70
|
this.tooltip.className = 'overtype-link-tooltip';
|
|
89
71
|
|
|
@@ -153,7 +135,7 @@ export class LinkTooltip {
|
|
|
153
135
|
return null;
|
|
154
136
|
}
|
|
155
137
|
|
|
156
|
-
show(linkInfo) {
|
|
138
|
+
async show(linkInfo) {
|
|
157
139
|
this.currentLink = linkInfo;
|
|
158
140
|
this.cancelHide();
|
|
159
141
|
|
|
@@ -161,72 +143,61 @@ export class LinkTooltip {
|
|
|
161
143
|
const urlSpan = this.tooltip.querySelector('.overtype-link-tooltip-url');
|
|
162
144
|
urlSpan.textContent = linkInfo.url;
|
|
163
145
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
} else {
|
|
167
|
-
this.showWithAnchorPositioning(linkInfo);
|
|
168
|
-
}
|
|
146
|
+
// Position first (tooltip is always rendered but invisible, so Floating UI can measure it)
|
|
147
|
+
await this.positionTooltip(linkInfo);
|
|
169
148
|
|
|
170
|
-
this
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// Set the CSS variable to point to the correct anchor
|
|
175
|
-
this.tooltip.style.setProperty('--target-anchor', `--link-${linkInfo.index}`);
|
|
149
|
+
// Only reveal if we're still showing this link
|
|
150
|
+
if (this.currentLink === linkInfo) {
|
|
151
|
+
this.tooltip.classList.add('visible');
|
|
152
|
+
}
|
|
176
153
|
}
|
|
177
154
|
|
|
178
|
-
async
|
|
179
|
-
// Find the <a> element in preview that corresponds to this link
|
|
155
|
+
async positionTooltip(linkInfo) {
|
|
180
156
|
const anchorElement = this.findAnchorElement(linkInfo.index);
|
|
181
157
|
|
|
182
158
|
if (!anchorElement) {
|
|
183
159
|
return;
|
|
184
160
|
}
|
|
185
161
|
|
|
186
|
-
// Check if anchor element is visible and in viewport
|
|
187
162
|
const rect = anchorElement.getBoundingClientRect();
|
|
188
163
|
if (rect.width === 0 || rect.height === 0) {
|
|
189
164
|
return;
|
|
190
165
|
}
|
|
191
166
|
|
|
192
167
|
try {
|
|
193
|
-
|
|
194
|
-
const { x, y } = await this.floatingUI.computePosition(
|
|
168
|
+
const { x, y } = await computePosition(
|
|
195
169
|
anchorElement,
|
|
196
170
|
this.tooltip,
|
|
197
171
|
{
|
|
172
|
+
strategy: 'fixed',
|
|
198
173
|
placement: 'bottom',
|
|
199
174
|
middleware: [
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
175
|
+
offset(8),
|
|
176
|
+
shift({ padding: 8 }),
|
|
177
|
+
flip()
|
|
203
178
|
]
|
|
204
179
|
}
|
|
205
180
|
);
|
|
206
181
|
|
|
207
|
-
// Apply position
|
|
208
182
|
Object.assign(this.tooltip.style, {
|
|
209
183
|
left: `${x}px`,
|
|
210
184
|
top: `${y}px`,
|
|
211
|
-
position: '
|
|
185
|
+
position: 'fixed'
|
|
212
186
|
});
|
|
213
187
|
} catch (error) {
|
|
214
|
-
// If Floating UI computation fails, don't show tooltip
|
|
215
188
|
console.warn('Floating UI positioning failed:', error);
|
|
216
|
-
return;
|
|
217
189
|
}
|
|
218
190
|
}
|
|
219
191
|
|
|
220
192
|
findAnchorElement(linkIndex) {
|
|
221
|
-
// Find the <a> element with the matching anchor-name style
|
|
222
193
|
const preview = this.editor.preview;
|
|
223
|
-
// Direct query for the specific link - more efficient than iterating
|
|
224
194
|
return preview.querySelector(`a[style*="--link-${linkIndex}"]`);
|
|
225
195
|
}
|
|
226
196
|
|
|
227
197
|
hide() {
|
|
228
198
|
this.tooltip.classList.remove('visible');
|
|
229
199
|
this.currentLink = null;
|
|
200
|
+
this.isTooltipHovered = false;
|
|
230
201
|
}
|
|
231
202
|
|
|
232
203
|
scheduleHide() {
|
|
@@ -244,7 +215,6 @@ export class LinkTooltip {
|
|
|
244
215
|
destroy() {
|
|
245
216
|
this.cancelHide();
|
|
246
217
|
|
|
247
|
-
// Remove visibility change listener
|
|
248
218
|
if (this.visibilityChangeHandler) {
|
|
249
219
|
document.removeEventListener('visibilitychange', this.visibilityChangeHandler);
|
|
250
220
|
this.visibilityChangeHandler = null;
|
|
@@ -255,7 +225,6 @@ export class LinkTooltip {
|
|
|
255
225
|
}
|
|
256
226
|
this.tooltip = null;
|
|
257
227
|
this.currentLink = null;
|
|
258
|
-
this.
|
|
259
|
-
this.useFloatingUI = false;
|
|
228
|
+
this.isTooltipHovered = false;
|
|
260
229
|
}
|
|
261
230
|
}
|
|
@@ -15,7 +15,7 @@ const DEFAULT_PLACEHOLDER = 'Start typing...';
|
|
|
15
15
|
const OBSERVED_ATTRIBUTES = [
|
|
16
16
|
'value', 'theme', 'toolbar', 'height', 'min-height', 'max-height',
|
|
17
17
|
'placeholder', 'font-size', 'line-height', 'padding', 'auto-resize',
|
|
18
|
-
'autofocus', 'show-stats', 'smart-lists', 'readonly'
|
|
18
|
+
'autofocus', 'show-stats', 'smart-lists', 'readonly', 'spellcheck'
|
|
19
19
|
];
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -261,6 +261,7 @@ class OverTypeEditor extends HTMLElement {
|
|
|
261
261
|
autoResize: this.hasAttribute('auto-resize'),
|
|
262
262
|
showStats: this.hasAttribute('show-stats'),
|
|
263
263
|
smartLists: !this.hasAttribute('smart-lists') || this.getAttribute('smart-lists') !== 'false',
|
|
264
|
+
spellcheck: this.hasAttribute('spellcheck') && this.getAttribute('spellcheck') !== 'false',
|
|
264
265
|
onChange: this._handleChange,
|
|
265
266
|
onKeydown: this._handleKeydown
|
|
266
267
|
};
|
|
@@ -324,8 +325,14 @@ class OverTypeEditor extends HTMLElement {
|
|
|
324
325
|
break;
|
|
325
326
|
|
|
326
327
|
case 'placeholder':
|
|
327
|
-
if (this._editor
|
|
328
|
-
this._editor.
|
|
328
|
+
if (this._editor) {
|
|
329
|
+
this._editor.options.placeholder = value || '';
|
|
330
|
+
if (this._editor.textarea) {
|
|
331
|
+
this._editor.textarea.placeholder = value || '';
|
|
332
|
+
}
|
|
333
|
+
if (this._editor.placeholderEl) {
|
|
334
|
+
this._editor.placeholderEl.textContent = value || '';
|
|
335
|
+
}
|
|
329
336
|
}
|
|
330
337
|
break;
|
|
331
338
|
|
|
@@ -382,6 +389,16 @@ class OverTypeEditor extends HTMLElement {
|
|
|
382
389
|
this._reinitializeEditor();
|
|
383
390
|
break;
|
|
384
391
|
}
|
|
392
|
+
|
|
393
|
+
case 'spellcheck':
|
|
394
|
+
if (this._editor) {
|
|
395
|
+
const enabled = this.hasAttribute('spellcheck') && this.getAttribute('spellcheck') !== 'false';
|
|
396
|
+
this._editor.options.spellcheck = enabled;
|
|
397
|
+
if (this._editor.textarea) {
|
|
398
|
+
this._editor.textarea.setAttribute('spellcheck', String(enabled));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
break;
|
|
385
402
|
}
|
|
386
403
|
}
|
|
387
404
|
|
|
@@ -670,6 +687,18 @@ class OverTypeEditor extends HTMLElement {
|
|
|
670
687
|
getEditor() {
|
|
671
688
|
return this._editor;
|
|
672
689
|
}
|
|
690
|
+
|
|
691
|
+
showToolbar() {
|
|
692
|
+
if (this._editor) {
|
|
693
|
+
this._editor.showToolbar();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
hideToolbar() {
|
|
698
|
+
if (this._editor) {
|
|
699
|
+
this._editor.hideToolbar();
|
|
700
|
+
}
|
|
701
|
+
}
|
|
673
702
|
}
|
|
674
703
|
|
|
675
704
|
// Register the custom element
|
package/src/overtype.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface Theme {
|
|
|
22
22
|
hr?: string;
|
|
23
23
|
link?: string;
|
|
24
24
|
listMarker?: string;
|
|
25
|
+
placeholder?: string;
|
|
25
26
|
primary?: string;
|
|
26
27
|
rawLine?: string;
|
|
27
28
|
selection?: string;
|
|
@@ -102,6 +103,7 @@ export interface Options {
|
|
|
102
103
|
toolbar?: boolean;
|
|
103
104
|
toolbarButtons?: ToolbarButton[]; // Custom toolbar button configuration
|
|
104
105
|
smartLists?: boolean; // v1.2.3+ Smart list continuation
|
|
106
|
+
spellcheck?: boolean; // Browser spellcheck (default: false)
|
|
105
107
|
statsFormatter?: (stats: Stats) => string;
|
|
106
108
|
codeHighlighter?: ((code: string, language: string) => string) | null; // Per-instance code highlighter
|
|
107
109
|
|
|
@@ -109,6 +111,15 @@ export interface Options {
|
|
|
109
111
|
theme?: string | Theme;
|
|
110
112
|
colors?: Partial<Theme['colors']>;
|
|
111
113
|
|
|
114
|
+
// File upload
|
|
115
|
+
fileUpload?: {
|
|
116
|
+
enabled: boolean;
|
|
117
|
+
maxSize?: number;
|
|
118
|
+
mimeTypes?: string[];
|
|
119
|
+
batch?: boolean;
|
|
120
|
+
onInsertFile: (file: File | File[]) => Promise<string | string[]>;
|
|
121
|
+
};
|
|
122
|
+
|
|
112
123
|
// Callbacks
|
|
113
124
|
onChange?: (value: string, instance: OverTypeInstance) => void;
|
|
114
125
|
onKeydown?: (event: KeyboardEvent, instance: OverTypeInstance) => void;
|
|
@@ -172,6 +183,10 @@ export interface OverTypeInstance {
|
|
|
172
183
|
setTheme(theme: string | Theme): this;
|
|
173
184
|
setCodeHighlighter(highlighter: ((code: string, language: string) => string) | null): void;
|
|
174
185
|
updatePreview(): void;
|
|
186
|
+
performAction(actionId: string, event?: Event | null): Promise<boolean>;
|
|
187
|
+
showToolbar(): void;
|
|
188
|
+
hideToolbar(): void;
|
|
189
|
+
insertAtCursor(text: string): void;
|
|
175
190
|
|
|
176
191
|
// HTML output methods
|
|
177
192
|
getRenderedHTML(options?: RenderOptions): string;
|
|
@@ -209,6 +224,7 @@ export const toolbarButtons: {
|
|
|
209
224
|
orderedList: ToolbarButton;
|
|
210
225
|
taskList: ToolbarButton;
|
|
211
226
|
quote: ToolbarButton;
|
|
227
|
+
upload: ToolbarButton;
|
|
212
228
|
viewMode: ToolbarButton;
|
|
213
229
|
};
|
|
214
230
|
|