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/LICENSE +21 -0
- package/README.md +441 -0
- package/dist/overtype.esm.js +2576 -0
- package/dist/overtype.esm.js.map +7 -0
- package/dist/overtype.js +2599 -0
- package/dist/overtype.js.map +7 -0
- package/dist/overtype.min.js +546 -0
- package/package.json +50 -0
- package/src/icons.js +77 -0
- package/src/index.js +4 -0
- package/src/overtype.js +781 -0
- package/src/parser.js +222 -0
- package/src/shortcuts.js +125 -0
- package/src/styles.js +486 -0
- package/src/themes.js +124 -0
- package/src/toolbar.js +221 -0
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
|
+
}
|