overtype 2.1.1 → 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.1",
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,38 +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;
16
15
  this.isTooltipHovered = false;
17
16
 
18
17
  this.init();
19
18
  }
20
19
 
21
- async init() {
22
- // Detect CSS anchor positioning support
23
- const supportsAnchorPositioning = CSS.supports('position-anchor: --x') &&
24
- CSS.supports('position-area: center');
25
-
26
- // Load Floating UI if needed
27
- if (!supportsAnchorPositioning) {
28
- try {
29
- // Use indirect eval to prevent bundler from processing the import
30
- const importFn = new Function('url', 'return import(url)');
31
- const { computePosition, offset, shift, flip } = await importFn(
32
- 'https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.7.4/+esm'
33
- );
34
- this.floatingUI = { computePosition, offset, shift, flip };
35
- this.useFloatingUI = true;
36
- } catch (error) {
37
- // If dynamic import fails, tooltips simply won't show
38
- console.warn('Failed to load Floating UI fallback:', error);
39
- this.floatingUI = null;
40
- this.useFloatingUI = false;
41
- }
42
- }
43
-
20
+ init() {
44
21
  // Create tooltip element
45
- // Note: Styles are now in the main stylesheet (styles.js) with @supports wrapper
46
22
  this.createTooltip();
47
23
 
48
24
  // Listen for cursor position changes
@@ -56,14 +32,10 @@ export class LinkTooltip {
56
32
  // Hide tooltip when typing
57
33
  this.editor.textarea.addEventListener('input', () => this.hide());
58
34
 
59
- // Reposition or hide tooltip when scrolling
35
+ // Reposition tooltip when scrolling
60
36
  this.editor.textarea.addEventListener('scroll', () => {
61
- if (this.useFloatingUI && this.currentLink) {
62
- // Reposition the tooltip for Floating UI
63
- this.showWithFloatingUI(this.currentLink);
64
- } else {
65
- // Hide for CSS anchor positioning (native browser behavior handles this)
66
- this.hide();
37
+ if (this.currentLink) {
38
+ this.positionTooltip(this.currentLink);
67
39
  }
68
40
  });
69
41
 
@@ -94,8 +66,6 @@ export class LinkTooltip {
94
66
  }
95
67
 
96
68
  createTooltip() {
97
- // Create tooltip element
98
- // Styles are now included in the main stylesheet (styles.js)
99
69
  this.tooltip = document.createElement('div');
100
70
  this.tooltip.className = 'overtype-link-tooltip';
101
71
 
@@ -165,7 +135,7 @@ export class LinkTooltip {
165
135
  return null;
166
136
  }
167
137
 
168
- show(linkInfo) {
138
+ async show(linkInfo) {
169
139
  this.currentLink = linkInfo;
170
140
  this.cancelHide();
171
141
 
@@ -173,66 +143,54 @@ export class LinkTooltip {
173
143
  const urlSpan = this.tooltip.querySelector('.overtype-link-tooltip-url');
174
144
  urlSpan.textContent = linkInfo.url;
175
145
 
176
- if (this.useFloatingUI) {
177
- this.showWithFloatingUI(linkInfo);
178
- } else {
179
- this.showWithAnchorPositioning(linkInfo);
180
- }
181
-
182
- this.tooltip.classList.add('visible');
183
- }
146
+ // Position first (tooltip is always rendered but invisible, so Floating UI can measure it)
147
+ await this.positionTooltip(linkInfo);
184
148
 
185
- showWithAnchorPositioning(linkInfo) {
186
- // Set the CSS variable to point to the correct anchor
187
- 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
+ }
188
153
  }
189
154
 
190
- async showWithFloatingUI(linkInfo) {
191
- // Find the <a> element in preview that corresponds to this link
155
+ async positionTooltip(linkInfo) {
192
156
  const anchorElement = this.findAnchorElement(linkInfo.index);
193
157
 
194
158
  if (!anchorElement) {
195
159
  return;
196
160
  }
197
161
 
198
- // Check if anchor element is visible and in viewport
199
162
  const rect = anchorElement.getBoundingClientRect();
200
163
  if (rect.width === 0 || rect.height === 0) {
201
164
  return;
202
165
  }
203
166
 
204
167
  try {
205
- // Compute position using Floating UI
206
- const { x, y } = await this.floatingUI.computePosition(
168
+ const { x, y } = await computePosition(
207
169
  anchorElement,
208
170
  this.tooltip,
209
171
  {
172
+ strategy: 'fixed',
210
173
  placement: 'bottom',
211
174
  middleware: [
212
- this.floatingUI.offset(8),
213
- this.floatingUI.shift({ padding: 8 }),
214
- this.floatingUI.flip()
175
+ offset(8),
176
+ shift({ padding: 8 }),
177
+ flip()
215
178
  ]
216
179
  }
217
180
  );
218
181
 
219
- // Apply position
220
182
  Object.assign(this.tooltip.style, {
221
183
  left: `${x}px`,
222
184
  top: `${y}px`,
223
- position: 'absolute'
185
+ position: 'fixed'
224
186
  });
225
187
  } catch (error) {
226
- // If Floating UI computation fails, don't show tooltip
227
188
  console.warn('Floating UI positioning failed:', error);
228
- return;
229
189
  }
230
190
  }
231
191
 
232
192
  findAnchorElement(linkIndex) {
233
- // Find the <a> element with the matching anchor-name style
234
193
  const preview = this.editor.preview;
235
- // Direct query for the specific link - more efficient than iterating
236
194
  return preview.querySelector(`a[style*="--link-${linkIndex}"]`);
237
195
  }
238
196
 
@@ -257,7 +215,6 @@ export class LinkTooltip {
257
215
  destroy() {
258
216
  this.cancelHide();
259
217
 
260
- // Remove visibility change listener
261
218
  if (this.visibilityChangeHandler) {
262
219
  document.removeEventListener('visibilitychange', this.visibilityChangeHandler);
263
220
  this.visibilityChangeHandler = null;
@@ -268,8 +225,6 @@ export class LinkTooltip {
268
225
  }
269
226
  this.tooltip = null;
270
227
  this.currentLink = null;
271
- this.floatingUI = null;
272
- this.useFloatingUI = false;
273
228
  this.isTooltipHovered = false;
274
229
  }
275
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