overtype 1.2.7 → 2.0.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/src/overtype.d.ts CHANGED
@@ -41,6 +41,28 @@ export interface Stats {
41
41
  column: number;
42
42
  }
43
43
 
44
+ /**
45
+ * Toolbar button definition
46
+ */
47
+ export interface ToolbarButton {
48
+ /** Unique button identifier */
49
+ name: string;
50
+
51
+ /** SVG icon markup (optional for separator) */
52
+ icon?: string;
53
+
54
+ /** Button tooltip text (optional for separator) */
55
+ title?: string;
56
+
57
+ /** Button action callback (optional for separator) */
58
+ action?: (context: {
59
+ editor: OverType;
60
+ getValue: () => string;
61
+ setValue: (value: string) => void;
62
+ event: MouseEvent;
63
+ }) => void | Promise<void>;
64
+ }
65
+
44
66
  export interface MobileOptions {
45
67
  fontSize?: string;
46
68
  padding?: string;
@@ -71,17 +93,11 @@ export interface Options {
71
93
  // Features
72
94
  showActiveLineRaw?: boolean;
73
95
  showStats?: boolean;
74
- toolbar?: boolean | {
75
- buttons?: Array<{
76
- name?: string;
77
- icon?: string;
78
- title?: string;
79
- action?: string;
80
- separator?: boolean;
81
- }>;
82
- };
96
+ toolbar?: boolean;
97
+ toolbarButtons?: ToolbarButton[]; // Custom toolbar button configuration
83
98
  smartLists?: boolean; // v1.2.3+ Smart list continuation
84
99
  statsFormatter?: (stats: Stats) => string;
100
+ codeHighlighter?: ((code: string, language: string) => string) | null; // Per-instance code highlighter
85
101
 
86
102
  // Theme (deprecated in favor of global theme)
87
103
  theme?: string | Theme;
@@ -96,7 +112,7 @@ export interface Options {
96
112
  export interface OverTypeConstructor {
97
113
  new(target: string | Element | NodeList | Element[], options?: Options): OverTypeInstance[];
98
114
  // Static members
99
- instances: WeakMap<Element, OverTypeInstance>;
115
+ instances: any;
100
116
  stylesInjected: boolean;
101
117
  globalListenersInitialized: boolean;
102
118
  instanceCount: number;
@@ -112,6 +128,7 @@ export interface OverTypeConstructor {
112
128
  destroyAll(): void;
113
129
  injectStyles(force?: boolean): void;
114
130
  setTheme(theme: string | Theme, customColors?: Partial<Theme['colors']>): void;
131
+ setCodeHighlighter(highlighter: ((code: string, language: string) => string) | null): void;
115
132
  initGlobalListeners(): void;
116
133
  getTheme(name: string): Theme;
117
134
  }
@@ -146,7 +163,8 @@ export interface OverTypeInstance {
146
163
  isInitialized(): boolean;
147
164
  reinit(options: Options): void;
148
165
  showStats(show: boolean): void;
149
- setTheme(theme: string | Theme): void;
166
+ setTheme(theme: string | Theme): this;
167
+ setCodeHighlighter(highlighter: ((code: string, language: string) => string) | null): void;
150
168
  updatePreview(): void;
151
169
 
152
170
  // HTML output methods
@@ -155,8 +173,9 @@ export interface OverTypeInstance {
155
173
  getPreviewHTML(): string;
156
174
 
157
175
  // View mode methods
158
- showPlainTextarea(show: boolean): void;
159
- showPreviewMode(show: boolean): void;
176
+ showNormalEditMode(): this;
177
+ showPlainTextarea(): this;
178
+ showPreviewMode(): this;
160
179
  }
161
180
 
162
181
  // Declare the constructor as a constant with proper typing
@@ -166,4 +185,28 @@ declare const OverType: OverTypeConstructor;
166
185
  export type OverType = OverTypeInstance;
167
186
 
168
187
  // Module exports - default export is the constructor
169
- export default OverType;
188
+ export default OverType;
189
+
190
+ /**
191
+ * Pre-defined toolbar buttons
192
+ */
193
+ export const toolbarButtons: {
194
+ bold: ToolbarButton;
195
+ italic: ToolbarButton;
196
+ code: ToolbarButton;
197
+ separator: ToolbarButton;
198
+ link: ToolbarButton;
199
+ h1: ToolbarButton;
200
+ h2: ToolbarButton;
201
+ h3: ToolbarButton;
202
+ bulletList: ToolbarButton;
203
+ orderedList: ToolbarButton;
204
+ taskList: ToolbarButton;
205
+ quote: ToolbarButton;
206
+ viewMode: ToolbarButton;
207
+ };
208
+
209
+ /**
210
+ * Default toolbar button layout with separators
211
+ */
212
+ export const defaultToolbarButtons: ToolbarButton[];
package/src/overtype.js CHANGED
@@ -10,6 +10,7 @@ import { generateStyles } from './styles.js';
10
10
  import { getTheme, mergeTheme, solar, themeToCSSVars } from './themes.js';
11
11
  import { Toolbar } from './toolbar.js';
12
12
  import { LinkTooltip } from './link-tooltip.js';
13
+ import { defaultToolbarButtons } from './toolbar-buttons.js';
13
14
 
14
15
  /**
15
16
  * OverType Editor Class
@@ -98,25 +99,10 @@ class OverType {
98
99
 
99
100
  // Setup shortcuts manager
100
101
  this.shortcuts = new ShortcutsManager(this);
101
-
102
+
102
103
  // Setup link tooltip
103
104
  this.linkTooltip = new LinkTooltip(this);
104
105
 
105
- // Setup toolbar if enabled
106
- if (this.options.toolbar) {
107
- const toolbarButtons = typeof this.options.toolbar === 'object' ? this.options.toolbar.buttons : null;
108
- this.toolbar = new Toolbar(this, toolbarButtons);
109
- this.toolbar.create();
110
-
111
- // Update toolbar states on selection change
112
- this.textarea.addEventListener('selectionchange', () => {
113
- this.toolbar.updateButtonStates();
114
- });
115
- this.textarea.addEventListener('input', () => {
116
- this.toolbar.updateButtonStates();
117
- });
118
- }
119
-
120
106
  // Mark as initialized
121
107
  this.initialized = true;
122
108
 
@@ -165,8 +151,10 @@ class OverType {
165
151
  showActiveLineRaw: false,
166
152
  showStats: false,
167
153
  toolbar: false,
154
+ toolbarButtons: null, // Defaults to defaultToolbarButtons if toolbar: true
168
155
  statsFormatter: null,
169
- smartLists: true // Enable smart list continuation
156
+ smartLists: true, // Enable smart list continuation
157
+ codeHighlighter: null // Per-instance code highlighter
170
158
  };
171
159
 
172
160
  // Remove theme and colors from options - these are now global
@@ -412,6 +400,49 @@ class OverType {
412
400
  this.textarea.setAttribute('data-enable-grammarly', 'false');
413
401
  }
414
402
 
403
+ /**
404
+ * Create and setup toolbar
405
+ * @private
406
+ */
407
+ _createToolbar() {
408
+ // Use provided toolbarButtons or default to defaultToolbarButtons
409
+ const toolbarButtons = this.options.toolbarButtons || defaultToolbarButtons;
410
+
411
+ this.toolbar = new Toolbar(this, { toolbarButtons });
412
+ this.toolbar.create();
413
+
414
+ // Store listener references for cleanup
415
+ this._toolbarSelectionListener = () => {
416
+ if (this.toolbar) {
417
+ this.toolbar.updateButtonStates();
418
+ }
419
+ };
420
+ this._toolbarInputListener = () => {
421
+ if (this.toolbar) {
422
+ this.toolbar.updateButtonStates();
423
+ }
424
+ };
425
+
426
+ // Add listeners
427
+ this.textarea.addEventListener('selectionchange', this._toolbarSelectionListener);
428
+ this.textarea.addEventListener('input', this._toolbarInputListener);
429
+ }
430
+
431
+ /**
432
+ * Cleanup toolbar event listeners
433
+ * @private
434
+ */
435
+ _cleanupToolbarListeners() {
436
+ if (this._toolbarSelectionListener) {
437
+ this.textarea.removeEventListener('selectionchange', this._toolbarSelectionListener);
438
+ this._toolbarSelectionListener = null;
439
+ }
440
+ if (this._toolbarInputListener) {
441
+ this.textarea.removeEventListener('input', this._toolbarInputListener);
442
+ this._toolbarInputListener = null;
443
+ }
444
+ }
445
+
415
446
  /**
416
447
  * Apply options to the editor
417
448
  * @private
@@ -421,7 +452,7 @@ class OverType {
421
452
  if (this.options.autofocus) {
422
453
  this.textarea.focus();
423
454
  }
424
-
455
+
425
456
  // Setup or remove auto-resize
426
457
  if (this.options.autoResize) {
427
458
  if (!this.container.classList.contains('overtype-auto-resize')) {
@@ -432,6 +463,17 @@ class OverType {
432
463
  this.container.classList.remove('overtype-auto-resize');
433
464
  }
434
465
 
466
+ // Handle toolbar option changes
467
+ if (this.options.toolbar && !this.toolbar) {
468
+ // Create toolbar if enabled and doesn't exist
469
+ this._createToolbar();
470
+ } else if (!this.options.toolbar && this.toolbar) {
471
+ // Destroy toolbar if disabled and exists
472
+ this._cleanupToolbarListeners();
473
+ this.toolbar.destroy();
474
+ this.toolbar = null;
475
+ }
476
+
435
477
  // Update preview with initial content
436
478
  this.updatePreview();
437
479
  }
@@ -443,9 +485,12 @@ class OverType {
443
485
  const text = this.textarea.value;
444
486
  const cursorPos = this.textarea.selectionStart;
445
487
  const activeLine = this._getCurrentLine(text, cursorPos);
446
-
488
+
489
+ // Detect if we're in preview mode
490
+ const isPreviewMode = this.container.dataset.mode === 'preview';
491
+
447
492
  // Parse markdown
448
- const html = MarkdownParser.parse(text, activeLine, this.options.showActiveLineRaw);
493
+ const html = MarkdownParser.parse(text, activeLine, this.options.showActiveLineRaw, this.options.codeHighlighter, isPreviewMode);
449
494
  this.preview.innerHTML = html || '<span style="color: #808080;">Start typing...</span>';
450
495
 
451
496
  // Apply code block backgrounds
@@ -778,8 +823,8 @@ class OverType {
778
823
  */
779
824
  getRenderedHTML(options = {}) {
780
825
  const markdown = this.getValue();
781
- let html = MarkdownParser.parse(markdown);
782
-
826
+ let html = MarkdownParser.parse(markdown, -1, false, this.options.codeHighlighter);
827
+
783
828
  if (options.cleanHTML) {
784
829
  // Remove all syntax marker spans for clean HTML export
785
830
  html = html.replace(/<span class="syntax-marker[^"]*">.*?<\/span>/g, '');
@@ -842,6 +887,45 @@ class OverType {
842
887
  this.updatePreview();
843
888
  }
844
889
 
890
+ /**
891
+ * Set theme for this instance
892
+ * @param {string|Object} theme - Theme name or custom theme object
893
+ * @returns {this} Returns this for chaining
894
+ */
895
+ setTheme(theme) {
896
+ // Update instance theme
897
+ this.instanceTheme = theme;
898
+
899
+ // Get theme object
900
+ const themeObj = typeof theme === 'string' ? getTheme(theme) : theme;
901
+ const themeName = typeof themeObj === 'string' ? themeObj : themeObj.name;
902
+
903
+ // Update container theme attribute
904
+ if (themeName) {
905
+ this.container.setAttribute('data-theme', themeName);
906
+ }
907
+
908
+ // Apply CSS variables to container for instance override
909
+ if (themeObj && themeObj.colors) {
910
+ const cssVars = themeToCSSVars(themeObj.colors);
911
+ this.container.style.cssText += cssVars;
912
+ }
913
+
914
+ // Update preview to reflect new theme
915
+ this.updatePreview();
916
+
917
+ return this;
918
+ }
919
+
920
+ /**
921
+ * Set instance-specific code highlighter
922
+ * @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
923
+ */
924
+ setCodeHighlighter(highlighter) {
925
+ this.options.codeHighlighter = highlighter;
926
+ this.updatePreview();
927
+ }
928
+
845
929
  /**
846
930
  * Update stats bar
847
931
  * @private
@@ -986,47 +1070,47 @@ class OverType {
986
1070
  }
987
1071
 
988
1072
  /**
989
- * Show or hide the plain textarea (toggle overlay visibility)
990
- * @param {boolean} show - true to show plain textarea (hide overlay), false to show overlay
991
- * @returns {boolean} Current plain textarea state
1073
+ * Show normal edit mode (overlay with markdown preview)
1074
+ * @returns {this} Returns this for chaining
992
1075
  */
993
- showPlainTextarea(show) {
994
- if (show) {
995
- // Show plain textarea mode (hide overlay)
996
- this.container.classList.add('plain-mode');
997
- } else {
998
- // Show overlay mode (hide plain textarea text)
999
- this.container.classList.remove('plain-mode');
1000
- }
1001
-
1076
+ showNormalEditMode() {
1077
+ this.container.dataset.mode = 'normal';
1078
+
1079
+ // Always sync scroll from preview to textarea
1080
+ requestAnimationFrame(() => {
1081
+ this.textarea.scrollTop = this.preview.scrollTop;
1082
+ this.textarea.scrollLeft = this.preview.scrollLeft;
1083
+ });
1084
+
1085
+ return this;
1086
+ }
1087
+
1088
+ /**
1089
+ * Show plain textarea mode (no overlay)
1090
+ * @returns {this} Returns this for chaining
1091
+ */
1092
+ showPlainTextarea() {
1093
+ this.container.dataset.mode = 'plain';
1094
+
1002
1095
  // Update toolbar button if exists
1003
1096
  if (this.toolbar) {
1004
1097
  const toggleBtn = this.container.querySelector('[data-action="toggle-plain"]');
1005
1098
  if (toggleBtn) {
1006
- // Button is active when showing overlay (not plain mode)
1007
- toggleBtn.classList.toggle('active', !show);
1008
- toggleBtn.title = show ? 'Show markdown preview' : 'Show plain textarea';
1099
+ toggleBtn.classList.remove('active');
1100
+ toggleBtn.title = 'Show markdown preview';
1009
1101
  }
1010
1102
  }
1011
-
1012
- return show;
1103
+
1104
+ return this;
1013
1105
  }
1014
1106
 
1015
1107
  /**
1016
- * Show/hide preview mode
1017
- * @param {boolean} show - Show preview mode if true, edit mode if false
1018
- * @returns {boolean} Current preview mode state
1108
+ * Show preview mode (read-only view)
1109
+ * @returns {this} Returns this for chaining
1019
1110
  */
1020
- showPreviewMode(show) {
1021
- if (show) {
1022
- // Show preview mode (hide textarea, make preview interactive)
1023
- this.container.classList.add('preview-mode');
1024
- } else {
1025
- // Show edit mode
1026
- this.container.classList.remove('preview-mode');
1027
- }
1028
-
1029
- return show;
1111
+ showPreviewMode() {
1112
+ this.container.dataset.mode = 'preview';
1113
+ return this;
1030
1114
  }
1031
1115
 
1032
1116
  /**
@@ -1120,18 +1204,18 @@ class OverType {
1120
1204
  static setTheme(theme, customColors = null) {
1121
1205
  // Process theme
1122
1206
  let themeObj = typeof theme === 'string' ? getTheme(theme) : theme;
1123
-
1207
+
1124
1208
  // Apply custom colors if provided
1125
1209
  if (customColors) {
1126
1210
  themeObj = mergeTheme(themeObj, customColors);
1127
1211
  }
1128
-
1212
+
1129
1213
  // Store as current theme
1130
1214
  OverType.currentTheme = themeObj;
1131
-
1215
+
1132
1216
  // Re-inject styles with new theme
1133
1217
  OverType.injectStyles(true);
1134
-
1218
+
1135
1219
  // Update all existing instances - update container theme attribute
1136
1220
  document.querySelectorAll('.overtype-container').forEach(container => {
1137
1221
  const themeName = typeof themeObj === 'string' ? themeObj : themeObj.name;
@@ -1139,7 +1223,7 @@ class OverType {
1139
1223
  container.setAttribute('data-theme', themeName);
1140
1224
  }
1141
1225
  });
1142
-
1226
+
1143
1227
  // Also handle any old-style wrappers without containers
1144
1228
  document.querySelectorAll('.overtype-wrapper').forEach(wrapper => {
1145
1229
  if (!wrapper.closest('.overtype-container')) {
@@ -1148,13 +1232,53 @@ class OverType {
1148
1232
  wrapper.setAttribute('data-theme', themeName);
1149
1233
  }
1150
1234
  }
1151
-
1235
+
1152
1236
  // Trigger preview update for the instance
1153
1237
  const instance = wrapper._instance;
1154
1238
  if (instance) {
1155
1239
  instance.updatePreview();
1156
1240
  }
1157
1241
  });
1242
+
1243
+ // Update web components (shadow DOM instances)
1244
+ const themeName = typeof themeObj === 'string' ? themeObj : themeObj.name;
1245
+ document.querySelectorAll('overtype-editor').forEach(webComponent => {
1246
+ // Set the theme attribute to update the theme name
1247
+ if (themeName && typeof webComponent.setAttribute === 'function') {
1248
+ webComponent.setAttribute('theme', themeName);
1249
+ }
1250
+ // Also call refreshTheme() to handle cases where the theme name stays the same
1251
+ // but the theme object's properties have changed
1252
+ if (typeof webComponent.refreshTheme === 'function') {
1253
+ webComponent.refreshTheme();
1254
+ }
1255
+ });
1256
+ }
1257
+
1258
+ /**
1259
+ * Set global code highlighter for all OverType instances
1260
+ * @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
1261
+ */
1262
+ static setCodeHighlighter(highlighter) {
1263
+ MarkdownParser.setCodeHighlighter(highlighter);
1264
+
1265
+ // Update all existing instances in light DOM
1266
+ document.querySelectorAll('.overtype-wrapper').forEach(wrapper => {
1267
+ const instance = wrapper._instance;
1268
+ if (instance && instance.updatePreview) {
1269
+ instance.updatePreview();
1270
+ }
1271
+ });
1272
+
1273
+ // Update web components (shadow DOM instances)
1274
+ document.querySelectorAll('overtype-editor').forEach(webComponent => {
1275
+ if (typeof webComponent.getEditor === 'function') {
1276
+ const instance = webComponent.getEditor();
1277
+ if (instance && instance.updatePreview) {
1278
+ instance.updatePreview();
1279
+ }
1280
+ }
1281
+ });
1158
1282
  }
1159
1283
 
1160
1284
  /**
@@ -1227,4 +1351,7 @@ OverType.currentTheme = solar;
1227
1351
 
1228
1352
  // Export for module systems
1229
1353
  export default OverType;
1230
- export { OverType };
1354
+ export { OverType };
1355
+
1356
+ // Export toolbar buttons for custom toolbar configurations
1357
+ export { toolbarButtons, defaultToolbarButtons } from './toolbar-buttons.js';