overtype 1.2.7 → 2.0.1

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,49 @@ 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
+ this.updatePreview(); // Re-render with normal mode (e.g., show syntax markers)
1079
+
1080
+ // Always sync scroll from preview to textarea
1081
+ requestAnimationFrame(() => {
1082
+ this.textarea.scrollTop = this.preview.scrollTop;
1083
+ this.textarea.scrollLeft = this.preview.scrollLeft;
1084
+ });
1085
+
1086
+ return this;
1087
+ }
1088
+
1089
+ /**
1090
+ * Show plain textarea mode (no overlay)
1091
+ * @returns {this} Returns this for chaining
1092
+ */
1093
+ showPlainTextarea() {
1094
+ this.container.dataset.mode = 'plain';
1095
+
1002
1096
  // Update toolbar button if exists
1003
1097
  if (this.toolbar) {
1004
1098
  const toggleBtn = this.container.querySelector('[data-action="toggle-plain"]');
1005
1099
  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';
1100
+ toggleBtn.classList.remove('active');
1101
+ toggleBtn.title = 'Show markdown preview';
1009
1102
  }
1010
1103
  }
1011
-
1012
- return show;
1104
+
1105
+ return this;
1013
1106
  }
1014
1107
 
1015
1108
  /**
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
1109
+ * Show preview mode (read-only view)
1110
+ * @returns {this} Returns this for chaining
1019
1111
  */
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;
1112
+ showPreviewMode() {
1113
+ this.container.dataset.mode = 'preview';
1114
+ this.updatePreview(); // Re-render with preview mode (e.g., checkboxes)
1115
+ return this;
1030
1116
  }
1031
1117
 
1032
1118
  /**
@@ -1120,18 +1206,18 @@ class OverType {
1120
1206
  static setTheme(theme, customColors = null) {
1121
1207
  // Process theme
1122
1208
  let themeObj = typeof theme === 'string' ? getTheme(theme) : theme;
1123
-
1209
+
1124
1210
  // Apply custom colors if provided
1125
1211
  if (customColors) {
1126
1212
  themeObj = mergeTheme(themeObj, customColors);
1127
1213
  }
1128
-
1214
+
1129
1215
  // Store as current theme
1130
1216
  OverType.currentTheme = themeObj;
1131
-
1217
+
1132
1218
  // Re-inject styles with new theme
1133
1219
  OverType.injectStyles(true);
1134
-
1220
+
1135
1221
  // Update all existing instances - update container theme attribute
1136
1222
  document.querySelectorAll('.overtype-container').forEach(container => {
1137
1223
  const themeName = typeof themeObj === 'string' ? themeObj : themeObj.name;
@@ -1139,7 +1225,7 @@ class OverType {
1139
1225
  container.setAttribute('data-theme', themeName);
1140
1226
  }
1141
1227
  });
1142
-
1228
+
1143
1229
  // Also handle any old-style wrappers without containers
1144
1230
  document.querySelectorAll('.overtype-wrapper').forEach(wrapper => {
1145
1231
  if (!wrapper.closest('.overtype-container')) {
@@ -1148,13 +1234,53 @@ class OverType {
1148
1234
  wrapper.setAttribute('data-theme', themeName);
1149
1235
  }
1150
1236
  }
1151
-
1237
+
1152
1238
  // Trigger preview update for the instance
1153
1239
  const instance = wrapper._instance;
1154
1240
  if (instance) {
1155
1241
  instance.updatePreview();
1156
1242
  }
1157
1243
  });
1244
+
1245
+ // Update web components (shadow DOM instances)
1246
+ const themeName = typeof themeObj === 'string' ? themeObj : themeObj.name;
1247
+ document.querySelectorAll('overtype-editor').forEach(webComponent => {
1248
+ // Set the theme attribute to update the theme name
1249
+ if (themeName && typeof webComponent.setAttribute === 'function') {
1250
+ webComponent.setAttribute('theme', themeName);
1251
+ }
1252
+ // Also call refreshTheme() to handle cases where the theme name stays the same
1253
+ // but the theme object's properties have changed
1254
+ if (typeof webComponent.refreshTheme === 'function') {
1255
+ webComponent.refreshTheme();
1256
+ }
1257
+ });
1258
+ }
1259
+
1260
+ /**
1261
+ * Set global code highlighter for all OverType instances
1262
+ * @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
1263
+ */
1264
+ static setCodeHighlighter(highlighter) {
1265
+ MarkdownParser.setCodeHighlighter(highlighter);
1266
+
1267
+ // Update all existing instances in light DOM
1268
+ document.querySelectorAll('.overtype-wrapper').forEach(wrapper => {
1269
+ const instance = wrapper._instance;
1270
+ if (instance && instance.updatePreview) {
1271
+ instance.updatePreview();
1272
+ }
1273
+ });
1274
+
1275
+ // Update web components (shadow DOM instances)
1276
+ document.querySelectorAll('overtype-editor').forEach(webComponent => {
1277
+ if (typeof webComponent.getEditor === 'function') {
1278
+ const instance = webComponent.getEditor();
1279
+ if (instance && instance.updatePreview) {
1280
+ instance.updatePreview();
1281
+ }
1282
+ }
1283
+ });
1158
1284
  }
1159
1285
 
1160
1286
  /**
@@ -1227,4 +1353,7 @@ OverType.currentTheme = solar;
1227
1353
 
1228
1354
  // Export for module systems
1229
1355
  export default OverType;
1230
- export { OverType };
1356
+ export { OverType };
1357
+
1358
+ // Export toolbar buttons for custom toolbar configurations
1359
+ export { toolbarButtons, defaultToolbarButtons } from './toolbar-buttons.js';