overtype 1.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/toolbar.js ADDED
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Toolbar component for OverType editor
3
+ * Provides markdown formatting buttons with icons
4
+ */
5
+
6
+ import * as icons from './icons.js';
7
+ import * as markdownActions from 'markdown-actions';
8
+
9
+ export class Toolbar {
10
+ constructor(editor) {
11
+ this.editor = editor;
12
+ this.container = null;
13
+ this.buttons = {};
14
+ }
15
+
16
+ /**
17
+ * Create and attach toolbar to editor
18
+ */
19
+ create() {
20
+ // Create toolbar container
21
+ this.container = document.createElement('div');
22
+ this.container.className = 'overtype-toolbar';
23
+ this.container.setAttribute('role', 'toolbar');
24
+ this.container.setAttribute('aria-label', 'Text formatting');
25
+
26
+ // Define toolbar buttons
27
+ const buttonConfig = [
28
+ { name: 'bold', icon: icons.boldIcon, title: 'Bold (Ctrl+B)', action: 'toggleBold' },
29
+ { name: 'italic', icon: icons.italicIcon, title: 'Italic (Ctrl+I)', action: 'toggleItalic' },
30
+ { separator: true },
31
+ { name: 'h1', icon: icons.h1Icon, title: 'Heading 1', action: 'insertH1' },
32
+ { name: 'h2', icon: icons.h2Icon, title: 'Heading 2', action: 'insertH2' },
33
+ { name: 'h3', icon: icons.h3Icon, title: 'Heading 3', action: 'insertH3' },
34
+ { separator: true },
35
+ { name: 'link', icon: icons.linkIcon, title: 'Insert Link (Ctrl+K)', action: 'insertLink' },
36
+ { name: 'code', icon: icons.codeIcon, title: 'Inline Code', action: 'toggleCode' },
37
+ { name: 'codeBlock', icon: icons.codeBlockIcon, title: 'Code Block', action: 'insertCodeBlock' },
38
+ { separator: true },
39
+ { name: 'quote', icon: icons.quoteIcon, title: 'Quote', action: 'toggleQuote' },
40
+ { separator: true },
41
+ { name: 'bulletList', icon: icons.bulletListIcon, title: 'Bullet List', action: 'toggleBulletList' },
42
+ { name: 'orderedList', icon: icons.orderedListIcon, title: 'Numbered List', action: 'toggleNumberedList' },
43
+ { name: 'taskList', icon: icons.taskListIcon, title: 'Task List', action: 'toggleTaskList' }
44
+ ];
45
+
46
+ // Create buttons
47
+ buttonConfig.forEach(config => {
48
+ if (config.separator) {
49
+ const separator = document.createElement('div');
50
+ separator.className = 'overtype-toolbar-separator';
51
+ separator.setAttribute('role', 'separator');
52
+ this.container.appendChild(separator);
53
+ } else {
54
+ const button = this.createButton(config);
55
+ this.buttons[config.name] = button;
56
+ this.container.appendChild(button);
57
+ }
58
+ });
59
+
60
+ // Insert toolbar into container before editor wrapper
61
+ const container = this.editor.element.querySelector('.overtype-container');
62
+ const wrapper = this.editor.element.querySelector('.overtype-wrapper');
63
+ if (container && wrapper) {
64
+ container.insertBefore(this.container, wrapper);
65
+ }
66
+
67
+ return this.container;
68
+ }
69
+
70
+ /**
71
+ * Create individual toolbar button
72
+ */
73
+ createButton(config) {
74
+ const button = document.createElement('button');
75
+ button.className = 'overtype-toolbar-button';
76
+ button.type = 'button';
77
+ button.title = config.title;
78
+ button.setAttribute('aria-label', config.title);
79
+ button.setAttribute('data-action', config.action);
80
+ button.innerHTML = config.icon;
81
+
82
+ // Add click handler
83
+ button.addEventListener('click', (e) => {
84
+ e.preventDefault();
85
+ this.handleAction(config.action);
86
+ });
87
+
88
+ return button;
89
+ }
90
+
91
+ /**
92
+ * Handle toolbar button actions
93
+ */
94
+ async handleAction(action) {
95
+ const textarea = this.editor.textarea;
96
+ if (!textarea) return;
97
+
98
+ // Focus textarea
99
+ textarea.focus();
100
+
101
+ try {
102
+
103
+ switch (action) {
104
+ case 'toggleBold':
105
+ markdownActions.toggleBold(textarea);
106
+ break;
107
+ case 'toggleItalic':
108
+ markdownActions.toggleItalic(textarea);
109
+ break;
110
+ case 'insertH1':
111
+ markdownActions.toggleH1(textarea);
112
+ break;
113
+ case 'insertH2':
114
+ markdownActions.toggleH2(textarea);
115
+ break;
116
+ case 'insertH3':
117
+ markdownActions.toggleH3(textarea);
118
+ break;
119
+ case 'insertLink':
120
+ markdownActions.insertLink(textarea);
121
+ break;
122
+ case 'toggleCode':
123
+ markdownActions.toggleCode(textarea);
124
+ break;
125
+ case 'insertCodeBlock':
126
+ // For code blocks, we'll insert the markdown directly
127
+ const start = textarea.selectionStart;
128
+ const end = textarea.selectionEnd;
129
+ const selectedText = textarea.value.slice(start, end);
130
+ const codeBlock = '```\n' + selectedText + '\n```';
131
+ textarea.setRangeText(codeBlock, start, end, 'end');
132
+ break;
133
+ case 'toggleBulletList':
134
+ markdownActions.toggleBulletList(textarea);
135
+ break;
136
+ case 'toggleNumberedList':
137
+ markdownActions.toggleNumberedList(textarea);
138
+ break;
139
+ case 'toggleQuote':
140
+ markdownActions.toggleQuote(textarea);
141
+ break;
142
+ case 'toggleTaskList':
143
+ markdownActions.toggleTaskList(textarea);
144
+ break;
145
+ }
146
+
147
+ // Trigger input event to update preview
148
+ textarea.dispatchEvent(new Event('input', { bubbles: true }));
149
+ } catch (error) {
150
+ console.error('Error loading markdown-actions:', error);
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Update toolbar button states based on current selection
156
+ */
157
+ async updateButtonStates() {
158
+ const textarea = this.editor.textarea;
159
+ if (!textarea) return;
160
+
161
+ try {
162
+ const activeFormats = markdownActions.getActiveFormats(textarea);
163
+
164
+ // Update button states
165
+ Object.entries(this.buttons).forEach(([name, button]) => {
166
+ let isActive = false;
167
+
168
+ switch (name) {
169
+ case 'bold':
170
+ isActive = activeFormats.includes('bold');
171
+ break;
172
+ case 'italic':
173
+ isActive = activeFormats.includes('italic');
174
+ break;
175
+ case 'code':
176
+ // Disabled: code detection is unreliable in code blocks
177
+ // isActive = activeFormats.includes('code');
178
+ isActive = false;
179
+ break;
180
+ case 'bulletList':
181
+ isActive = activeFormats.includes('bullet-list');
182
+ break;
183
+ case 'orderedList':
184
+ isActive = activeFormats.includes('numbered-list');
185
+ break;
186
+ case 'quote':
187
+ isActive = activeFormats.includes('quote');
188
+ break;
189
+ case 'taskList':
190
+ isActive = activeFormats.includes('task-list');
191
+ break;
192
+ case 'h1':
193
+ isActive = activeFormats.includes('header');
194
+ break;
195
+ case 'h2':
196
+ isActive = activeFormats.includes('header-2');
197
+ break;
198
+ case 'h3':
199
+ isActive = activeFormats.includes('header-3');
200
+ break;
201
+ }
202
+
203
+ button.classList.toggle('active', isActive);
204
+ button.setAttribute('aria-pressed', isActive.toString());
205
+ });
206
+ } catch (error) {
207
+ // Silently fail if markdown-actions not available
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Destroy toolbar
213
+ */
214
+ destroy() {
215
+ if (this.container) {
216
+ this.container.remove();
217
+ this.container = null;
218
+ this.buttons = {};
219
+ }
220
+ }
221
+ }