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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overtype",
3
- "version": "2.1.0",
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>
@@ -1,9 +1,10 @@
1
1
  /**
2
- * Link Tooltip - CSS Anchor Positioning with Floating UI fallback
3
- * Shows a clickable tooltip when cursor is within a link
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.useFloatingUI = false;
15
- this.floatingUI = null;
15
+ this.isTooltipHovered = false;
16
16
 
17
17
  this.init();
18
18
  }
19
19
 
20
- async init() {
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 or hide tooltip when scrolling
35
+ // Reposition tooltip when scrolling
59
36
  this.editor.textarea.addEventListener('scroll', () => {
60
- if (this.useFloatingUI && this.currentLink) {
61
- // Reposition the tooltip for Floating UI
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', () => this.hide());
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
- // Keep tooltip visible on hover (only prevent hide, don't schedule hide on leave)
81
- this.tooltip.addEventListener('mouseenter', () => this.cancelHide());
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
- if (this.useFloatingUI) {
165
- this.showWithFloatingUI(linkInfo);
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.tooltip.classList.add('visible');
171
- }
172
-
173
- showWithAnchorPositioning(linkInfo) {
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 showWithFloatingUI(linkInfo) {
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
- // Compute position using Floating UI
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
- this.floatingUI.offset(8),
201
- this.floatingUI.shift({ padding: 8 }),
202
- this.floatingUI.flip()
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: 'absolute'
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.floatingUI = null;
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.textarea) {
328
- this._editor.textarea.placeholder = value || '';
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