trysem commited on
Commit
dd341d7
·
verified ·
1 Parent(s): e57cdf0

Create rich-manglish-text-editor

Browse files
Files changed (1) hide show
  1. rich-manglish-text-editor +566 -0
rich-manglish-text-editor ADDED
@@ -0,0 +1,566 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="light">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Scribe | Intelligent Text Editor</title>
7
+
8
+ <!-- Tailwind CSS (CDN) -->
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <script>
11
+ tailwind.config = {
12
+ darkMode: 'class',
13
+ theme: {
14
+ extend: {
15
+ colors: {
16
+ zinc: { 850: '#202022', 900: '#18181b', 950: '#09090b' },
17
+ },
18
+ fontFamily: {
19
+ sans: ['Inter', 'sans-serif'],
20
+ serif: ['Merriweather', 'serif'],
21
+ mono: ['JetBrains Mono', 'monospace'],
22
+ }
23
+ }
24
+ }
25
+ }
26
+ </script>
27
+
28
+ <!-- Google Fonts -->
29
+ <link rel="preconnect" href="https://fonts.googleapis.com">
30
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
31
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono&family=Merriweather&display=swap" rel="stylesheet">
32
+
33
+ <!-- Lucide Icons -->
34
+ <script src="https://unpkg.com/lucide@latest"></script>
35
+
36
+ <style>
37
+ /* Custom Scrollbar for a premium feel */
38
+ ::-webkit-scrollbar {
39
+ width: 8px;
40
+ height: 8px;
41
+ }
42
+ ::-webkit-scrollbar-track {
43
+ background: transparent;
44
+ }
45
+ ::-webkit-scrollbar-thumb {
46
+ background: #d4d4d8;
47
+ border-radius: 4px;
48
+ }
49
+ .dark ::-webkit-scrollbar-thumb {
50
+ background: #3f3f46;
51
+ }
52
+ ::-webkit-scrollbar-thumb:hover {
53
+ background: #a1a1aa;
54
+ }
55
+ .dark ::-webkit-scrollbar-thumb:hover {
56
+ background: #52525b;
57
+ }
58
+
59
+ /* Hide native details marker */
60
+ details > summary::-webkit-details-marker {
61
+ display: none;
62
+ }
63
+
64
+ /* Focus Mode Transitions */
65
+ body {
66
+ transition: background-color 0.3s ease;
67
+ }
68
+ #sidebar, #topbar, #bottombar {
69
+ transition: transform 0.3s ease, opacity 0.3s ease, width 0.3s ease;
70
+ }
71
+
72
+ /* Focus Mode Classes applied via JS */
73
+ .focus-mode #sidebar {
74
+ transform: translateX(-100%);
75
+ width: 0;
76
+ opacity: 0;
77
+ overflow: hidden;
78
+ border: none;
79
+ }
80
+ .focus-mode #topbar, .focus-mode #bottombar {
81
+ transform: translateY(-100%);
82
+ opacity: 0;
83
+ position: absolute;
84
+ pointer-events: none;
85
+ }
86
+ .focus-mode #bottombar {
87
+ transform: translateY(100%);
88
+ }
89
+ .focus-mode #editor-container {
90
+ padding-top: 2rem;
91
+ padding-bottom: 2rem;
92
+ }
93
+ </style>
94
+ </head>
95
+ <body class="h-screen w-full flex flex-col bg-white text-zinc-800 dark:bg-zinc-950 dark:text-zinc-200 font-sans overflow-hidden">
96
+
97
+ <!-- Floating Exit Focus Button (Hidden by default) -->
98
+ <button id="exit-focus-btn" class="hidden fixed top-4 right-4 z-50 bg-zinc-800 hover:bg-zinc-700 text-white px-4 py-2 rounded-full shadow-lg items-center gap-2 transition-all duration-300">
99
+ <i data-lucide="minimize"></i> Exit Focus Mode
100
+ </button>
101
+
102
+ <!-- Top Navigation Bar -->
103
+ <header id="topbar" class="h-14 flex items-center justify-between px-4 border-b border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900 shrink-0 z-10">
104
+ <div class="flex items-center gap-4">
105
+ <div class="flex items-center gap-2 font-semibold text-lg">
106
+ <i data-lucide="feather" class="text-blue-500"></i> Scribe
107
+ </div>
108
+ <div class="h-6 w-px bg-zinc-300 dark:bg-zinc-700 mx-2"></div>
109
+
110
+ <!-- Font Controls -->
111
+ <div class="flex items-center bg-zinc-200 dark:bg-zinc-800 rounded-lg p-1 text-sm">
112
+ <button onclick="setEditorFont('sans')" class="px-3 py-1 rounded hover:bg-white dark:hover:bg-zinc-700 font-sans" title="Sans Serif">Sans</button>
113
+ <button onclick="setEditorFont('serif')" class="px-3 py-1 rounded hover:bg-white dark:hover:bg-zinc-700 font-serif" title="Serif">Serif</button>
114
+ <button onclick="setEditorFont('mono')" class="px-3 py-1 rounded hover:bg-white dark:hover:bg-zinc-700 font-mono" title="Monospace">Mono</button>
115
+ </div>
116
+ </div>
117
+
118
+ <div class="flex items-center gap-3">
119
+ <!-- Manglish Toggle -->
120
+ <label class="flex items-center cursor-pointer gap-2 mr-2" title="Transliterate English to Malayalam automatically">
121
+ <span class="text-sm font-medium">Manglish</span>
122
+ <div class="relative">
123
+ <input type="checkbox" id="manglish-toggle" class="sr-only">
124
+ <div class="block bg-zinc-300 dark:bg-zinc-700 w-10 h-6 rounded-full transition-colors toggle-bg"></div>
125
+ <div class="dot absolute left-1 top-1 bg-white w-4 h-4 rounded-full transition-transform"></div>
126
+ </div>
127
+ </label>
128
+
129
+ <button id="theme-toggle" class="p-2 rounded-md hover:bg-zinc-200 dark:hover:bg-zinc-800 transition" title="Toggle Theme">
130
+ <i data-lucide="moon" id="theme-icon"></i>
131
+ </button>
132
+ <button id="focus-toggle" class="p-2 rounded-md hover:bg-zinc-200 dark:hover:bg-zinc-800 transition" title="Focus Mode">
133
+ <i data-lucide="maximize"></i>
134
+ </button>
135
+ <div class="relative group">
136
+ <button class="p-2 rounded-md hover:bg-zinc-200 dark:hover:bg-zinc-800 transition flex items-center gap-2" title="Export">
137
+ <i data-lucide="download"></i>
138
+ </button>
139
+ <div class="absolute right-0 top-full mt-1 hidden group-hover:block bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-md shadow-lg w-32 overflow-hidden">
140
+ <button onclick="exportFile('txt')" class="w-full text-left px-4 py-2 text-sm hover:bg-zinc-100 dark:hover:bg-zinc-700">As .txt</button>
141
+ <button onclick="exportFile('md')" class="w-full text-left px-4 py-2 text-sm hover:bg-zinc-100 dark:hover:bg-zinc-700">As .md</button>
142
+ </div>
143
+ </div>
144
+ <button onclick="clearEditor()" class="p-2 text-red-500 rounded-md hover:bg-red-50 dark:hover:bg-red-900/20 transition" title="Clear Editor">
145
+ <i data-lucide="trash-2"></i>
146
+ </button>
147
+ </div>
148
+ </header>
149
+
150
+ <!-- Main Content Area -->
151
+ <main class="flex-1 flex overflow-hidden relative">
152
+
153
+ <!-- Sidebar: Bulk Tools -->
154
+ <aside id="sidebar" class="w-80 flex-shrink-0 border-r border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/50 overflow-y-auto flex flex-col">
155
+ <div class="p-4 border-b border-zinc-200 dark:border-zinc-800 font-medium flex justify-between items-center text-sm uppercase tracking-wider text-zinc-500">
156
+ Bulk Actions
157
+ <i data-lucide="wrench" class="w-4 h-4"></i>
158
+ </div>
159
+
160
+ <div class="p-4 space-y-4">
161
+
162
+ <!-- Editor Settings -->
163
+ <div class="flex items-center justify-between p-3 bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">
164
+ <span class="text-sm font-medium">Smart Paste (Strip HTML)</span>
165
+ <label class="relative cursor-pointer">
166
+ <input type="checkbox" id="smart-paste-toggle" class="sr-only" checked>
167
+ <div class="block bg-zinc-300 dark:bg-zinc-600 w-8 h-5 rounded-full toggle-bg transition-colors"></div>
168
+ <div class="dot absolute left-1 top-1 bg-white w-3 h-3 rounded-full transition-transform"></div>
169
+ </label>
170
+ </div>
171
+
172
+ <!-- Case Operations -->
173
+ <details class="group border border-zinc-200 dark:border-zinc-700 rounded-lg overflow-hidden bg-white dark:bg-zinc-800" open>
174
+ <summary class="flex justify-between items-center cursor-pointer p-3 font-medium text-sm select-none">
175
+ <span>Change Case</span>
176
+ <i data-lucide="chevron-down" class="w-4 h-4 transition-transform group-open:rotate-180"></i>
177
+ </summary>
178
+ <div class="p-3 pt-0 grid grid-cols-2 gap-2 text-xs">
179
+ <button onclick="applyAction('upper')" class="btn-tool">UPPERCASE</button>
180
+ <button onclick="applyAction('lower')" class="btn-tool">lowercase</button>
181
+ <button onclick="applyAction('title')" class="btn-tool">Title Case</button>
182
+ <button onclick="applyAction('sentence')" class="btn-tool">Sentence case.</button>
183
+ </div>
184
+ </details>
185
+
186
+ <!-- Line Operations -->
187
+ <details class="group border border-zinc-200 dark:border-zinc-700 rounded-lg overflow-hidden bg-white dark:bg-zinc-800">
188
+ <summary class="flex justify-between items-center cursor-pointer p-3 font-medium text-sm select-none">
189
+ <span>Line Operations</span>
190
+ <i data-lucide="chevron-down" class="w-4 h-4 transition-transform group-open:rotate-180"></i>
191
+ </summary>
192
+ <div class="p-3 pt-0 flex flex-col gap-2 text-xs">
193
+ <button onclick="applyAction('sort')" class="btn-tool w-full justify-start"><i data-lucide="arrow-down-a-z" class="w-3 h-3 mr-2"></i> Sort Lines Alphabetically</button>
194
+ <button onclick="applyAction('reverse')" class="btn-tool w-full justify-start"><i data-lucide="arrow-up-down" class="w-3 h-3 mr-2"></i> Reverse Lines</button>
195
+ <button onclick="applyAction('rm-empty')" class="btn-tool w-full justify-start"><i data-lucide="align-justify" class="w-3 h-3 mr-2"></i> Remove Empty Lines</button>
196
+ <button onclick="applyAction('rm-dupes')" class="btn-tool w-full justify-start"><i data-lucide="copy-minus" class="w-3 h-3 mr-2"></i> Remove Duplicates</button>
197
+ </div>
198
+ </details>
199
+
200
+ <!-- Spacing & Formatting -->
201
+ <details class="group border border-zinc-200 dark:border-zinc-700 rounded-lg overflow-hidden bg-white dark:bg-zinc-800">
202
+ <summary class="flex justify-between items-center cursor-pointer p-3 font-medium text-sm select-none">
203
+ <span>Spacing</span>
204
+ <i data-lucide="chevron-down" class="w-4 h-4 transition-transform group-open:rotate-180"></i>
205
+ </summary>
206
+ <div class="p-3 pt-0 flex flex-col gap-2 text-xs">
207
+ <button onclick="applyAction('rm-spaces')" class="btn-tool w-full justify-start"><i data-lucide="space" class="w-3 h-3 mr-2"></i> Remove Extra Spaces</button>
208
+ </div>
209
+ </details>
210
+
211
+ <!-- Prefix & Suffix -->
212
+ <details class="group border border-zinc-200 dark:border-zinc-700 rounded-lg overflow-hidden bg-white dark:bg-zinc-800">
213
+ <summary class="flex justify-between items-center cursor-pointer p-3 font-medium text-sm select-none">
214
+ <span>Prefix / Suffix</span>
215
+ <i data-lucide="chevron-down" class="w-4 h-4 transition-transform group-open:rotate-180"></i>
216
+ </summary>
217
+ <div class="p-3 pt-0 flex flex-col gap-3 text-xs">
218
+ <div>
219
+ <label class="block text-zinc-500 mb-1">Add to start of every line:</label>
220
+ <div class="flex gap-2">
221
+ <input type="text" id="prefix-input" class="flex-1 bg-zinc-50 dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-600 rounded px-2 py-1 outline-none focus:border-blue-500" placeholder="- ">
222
+ <button onclick="applyAction('prefix')" class="bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded transition">Apply</button>
223
+ </div>
224
+ </div>
225
+ <div>
226
+ <label class="block text-zinc-500 mb-1">Add to end of every line:</label>
227
+ <div class="flex gap-2">
228
+ <input type="text" id="suffix-input" class="flex-1 bg-zinc-50 dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-600 rounded px-2 py-1 outline-none focus:border-blue-500" placeholder=";">
229
+ <button onclick="applyAction('suffix')" class="bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded transition">Apply</button>
230
+ </div>
231
+ </div>
232
+ </div>
233
+ </details>
234
+
235
+ <!-- Find & Replace -->
236
+ <details class="group border border-zinc-200 dark:border-zinc-700 rounded-lg overflow-hidden bg-white dark:bg-zinc-800">
237
+ <summary class="flex justify-between items-center cursor-pointer p-3 font-medium text-sm select-none">
238
+ <span>Find & Replace</span>
239
+ <i data-lucide="chevron-down" class="w-4 h-4 transition-transform group-open:rotate-180"></i>
240
+ </summary>
241
+ <div class="p-3 pt-0 flex flex-col gap-2 text-xs">
242
+ <input type="text" id="find-input" class="w-full bg-zinc-50 dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-600 rounded px-2 py-1.5 outline-none focus:border-blue-500" placeholder="Find...">
243
+ <input type="text" id="replace-input" class="w-full bg-zinc-50 dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-600 rounded px-2 py-1.5 outline-none focus:border-blue-500" placeholder="Replace with...">
244
+ <button onclick="applyAction('replace')" class="btn-tool w-full justify-center bg-zinc-100 dark:bg-zinc-700 mt-1"><i data-lucide="replace" class="w-3 h-3 mr-2"></i> Replace All</button>
245
+ </div>
246
+ </details>
247
+
248
+ </div>
249
+ </aside>
250
+
251
+ <!-- Main Editor Area -->
252
+ <div id="editor-container" class="flex-1 flex flex-col bg-white dark:bg-zinc-950 relative h-full">
253
+ <textarea
254
+ id="main-editor"
255
+ class="w-full h-full resize-none outline-none bg-transparent text-lg leading-relaxed p-8 md:p-12 text-zinc-800 dark:text-zinc-200 font-sans"
256
+ placeholder="Start typing or paste your text here...&#10;&#10;(Enable Manglish mode in the top right to type in Malayalam phonetically)"></textarea>
257
+ </div>
258
+
259
+ </main>
260
+
261
+ <!-- Status Bar -->
262
+ <footer id="bottombar" class="h-8 border-t border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900 flex items-center justify-between px-4 text-xs text-zinc-500 dark:text-zinc-400 shrink-0">
263
+ <div class="flex items-center gap-4">
264
+ <span id="stat-words">0 words</span>
265
+ <span id="stat-chars">0 characters</span>
266
+ <span id="stat-lines">0 lines</span>
267
+ </div>
268
+ <div class="flex items-center gap-2">
269
+ <i data-lucide="clock" class="w-3 h-3"></i>
270
+ <span id="stat-readtime">0 min read</span>
271
+ </div>
272
+ </footer>
273
+
274
+ <!-- Styles specifically for interactive components inside script tags for easier maintenance -->
275
+ <style>
276
+ /* Custom Button Component for Sidebar */
277
+ .btn-tool {
278
+ display: inline-flex;
279
+ align-items: center;
280
+ padding: 0.375rem 0.75rem;
281
+ background-color: var(--tw-bg-opacity, #f4f4f5); /* zinc-100 */
282
+ border-radius: 0.375rem;
283
+ transition: all 0.2s;
284
+ border: 1px solid transparent;
285
+ }
286
+ .dark .btn-tool {
287
+ background-color: #27272a; /* zinc-800 */
288
+ }
289
+ .btn-tool:hover {
290
+ background-color: #e4e4e7; /* zinc-200 */
291
+ border-color: #d4d4d8;
292
+ }
293
+ .dark .btn-tool:hover {
294
+ background-color: #3f3f46; /* zinc-700 */
295
+ border-color: #52525b;
296
+ }
297
+
298
+ /* Toggle Switch Animation */
299
+ input:checked ~ .dot {
300
+ transform: translateX(100%);
301
+ }
302
+ input:checked ~ .toggle-bg {
303
+ background-color: #3b82f6; /* blue-500 */
304
+ }
305
+ </style>
306
+
307
+ <script>
308
+ // Initialize Icons
309
+ lucide.createIcons();
310
+
311
+ // DOM Elements
312
+ const editor = document.getElementById('main-editor');
313
+ const themeToggle = document.getElementById('theme-toggle');
314
+ const themeIcon = document.getElementById('theme-icon');
315
+ const focusToggle = document.getElementById('focus-toggle');
316
+ const exitFocusBtn = document.getElementById('exit-focus-btn');
317
+ const manglishToggle = document.getElementById('manglish-toggle');
318
+ const smartPasteToggle = document.getElementById('smart-paste-toggle');
319
+
320
+ // State
321
+ let isManglishEnabled = false;
322
+
323
+ // Initialization
324
+ document.addEventListener('DOMContentLoaded', () => {
325
+ // Load saved text
326
+ const savedText = localStorage.getItem('scribe_text');
327
+ if (savedText) {
328
+ editor.value = savedText;
329
+ }
330
+
331
+ // Load theme preference
332
+ if (localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
333
+ document.documentElement.classList.add('dark');
334
+ themeIcon.setAttribute('data-lucide', 'sun');
335
+ } else {
336
+ themeIcon.setAttribute('data-lucide', 'moon');
337
+ }
338
+ lucide.createIcons();
339
+
340
+ updateStats();
341
+ editor.focus();
342
+ });
343
+
344
+ // ----------------------------------------------------
345
+ // Editor Core Features (Stats, Auto-Save, Smart Paste)
346
+ // ----------------------------------------------------
347
+
348
+ editor.addEventListener('input', () => {
349
+ updateStats();
350
+ saveText();
351
+ });
352
+
353
+ function updateStats() {
354
+ const text = editor.value;
355
+ const words = text.trim() ? text.trim().split(/\s+/).length : 0;
356
+ const chars = text.length;
357
+ const lines = text ? text.split('\n').length : 0;
358
+ const readTime = Math.max(1, Math.ceil(words / 200));
359
+
360
+ document.getElementById('stat-words').textContent = `${words} word${words !== 1 ? 's' : ''}`;
361
+ document.getElementById('stat-chars').textContent = `${chars} character${chars !== 1 ? 's' : ''}`;
362
+ document.getElementById('stat-lines').textContent = `${lines} line${lines !== 1 ? 's' : ''}`;
363
+ document.getElementById('stat-readtime').textContent = `${readTime} min read`;
364
+ }
365
+
366
+ function saveText() {
367
+ localStorage.setItem('scribe_text', editor.value);
368
+ }
369
+
370
+ function clearEditor() {
371
+ if (confirm("Are you sure you want to clear the editor?")) {
372
+ editor.value = '';
373
+ updateStats();
374
+ saveText();
375
+ editor.focus();
376
+ }
377
+ }
378
+
379
+ editor.addEventListener('paste', (e) => {
380
+ if (smartPasteToggle.checked) {
381
+ e.preventDefault();
382
+ // Get text and strip basic HTML tags if accidentally copied as raw string
383
+ let text = (e.originalEvent || e).clipboardData.getData('text/plain');
384
+ text = text.replace(/<[^>]*>?/gm, ''); // Strip remaining HTML tags
385
+ document.execCommand('insertText', false, text);
386
+ }
387
+ });
388
+
389
+ // ----------------------------------------------------
390
+ // UI & Theme Controls
391
+ // ----------------------------------------------------
392
+
393
+ themeToggle.addEventListener('click', () => {
394
+ document.documentElement.classList.toggle('dark');
395
+ const isDark = document.documentElement.classList.contains('dark');
396
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
397
+ themeIcon.setAttribute('data-lucide', isDark ? 'sun' : 'moon');
398
+ lucide.createIcons();
399
+ });
400
+
401
+ // Focus Mode
402
+ function toggleFocusMode() {
403
+ document.body.classList.toggle('focus-mode');
404
+ const isFocus = document.body.classList.contains('focus-mode');
405
+
406
+ if (isFocus) {
407
+ exitFocusBtn.classList.remove('hidden');
408
+ exitFocusBtn.classList.add('flex');
409
+ } else {
410
+ exitFocusBtn.classList.add('hidden');
411
+ exitFocusBtn.classList.remove('flex');
412
+ }
413
+ editor.focus();
414
+ }
415
+ focusToggle.addEventListener('click', toggleFocusMode);
416
+ exitFocusBtn.addEventListener('click', toggleFocusMode);
417
+
418
+ function setEditorFont(fontType) {
419
+ editor.classList.remove('font-sans', 'font-serif', 'font-mono');
420
+ editor.classList.add(`font-${fontType}`);
421
+ editor.focus();
422
+ }
423
+
424
+ // Export functionality
425
+ function exportFile(extension) {
426
+ const text = editor.value;
427
+ const blob = new Blob([text], { type: 'text/plain' });
428
+ const url = URL.createObjectURL(blob);
429
+ const a = document.createElement('a');
430
+ a.href = url;
431
+ a.download = `Scribe_Document_${new Date().toISOString().slice(0,10)}.${extension}`;
432
+ a.click();
433
+ URL.revokeObjectURL(url);
434
+ }
435
+
436
+ // ----------------------------------------------------
437
+ // Bulk Editing Actions
438
+ // ----------------------------------------------------
439
+
440
+ function applyAction(action) {
441
+ let val = editor.value;
442
+ if (!val) return;
443
+
444
+ switch (action) {
445
+ case 'upper':
446
+ val = val.toUpperCase();
447
+ break;
448
+ case 'lower':
449
+ val = val.toLowerCase();
450
+ break;
451
+ case 'title':
452
+ val = val.toLowerCase().replace(/\b\w/g, c => c.toUpperCase());
453
+ break;
454
+ case 'sentence':
455
+ val = val.split(/(?<=[.?!])\s+/).map(s => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()).join(' ');
456
+ break;
457
+ case 'sort':
458
+ val = val.split('\n').sort().join('\n');
459
+ break;
460
+ case 'reverse':
461
+ val = val.split('\n').reverse().join('\n');
462
+ break;
463
+ case 'rm-empty':
464
+ val = val.split('\n').filter(l => l.trim().length > 0).join('\n');
465
+ break;
466
+ case 'rm-dupes':
467
+ val = [...new Set(val.split('\n'))].join('\n');
468
+ break;
469
+ case 'rm-spaces':
470
+ val = val.replace(/[ \t]+/g, ' ');
471
+ break;
472
+ case 'prefix':
473
+ const pre = document.getElementById('prefix-input').value;
474
+ val = val.split('\n').map(l => l.trim().length > 0 ? pre + l : l).join('\n');
475
+ break;
476
+ case 'suffix':
477
+ const suf = document.getElementById('suffix-input').value;
478
+ val = val.split('\n').map(l => l.trim().length > 0 ? l + suf : l).join('\n');
479
+ break;
480
+ case 'replace':
481
+ const f = document.getElementById('find-input').value;
482
+ const r = document.getElementById('replace-input').value;
483
+ if (f) val = val.split(f).join(r);
484
+ break;
485
+ }
486
+
487
+ // Using execCommand preserves undo history in most browsers
488
+ editor.focus();
489
+ editor.select();
490
+ const success = document.execCommand('insertText', false, val);
491
+ if (!success) {
492
+ editor.value = val; // Fallback
493
+ }
494
+ updateStats();
495
+ saveText();
496
+ }
497
+
498
+ // ----------------------------------------------------
499
+ // Manglish (Malayalam-English) Transliteration Logic
500
+ // ----------------------------------------------------
501
+
502
+ manglishToggle.addEventListener('change', (e) => {
503
+ isManglishEnabled = e.target.checked;
504
+ editor.focus();
505
+ });
506
+
507
+ editor.addEventListener('keydown', async (e) => {
508
+ if (!isManglishEnabled) return;
509
+
510
+ // Trigger transliteration when user hits Space or Enter
511
+ if (e.key === ' ' || e.key === 'Enter') {
512
+ const cursor = editor.selectionStart;
513
+ const text = editor.value;
514
+
515
+ // Find the contiguous English alphabetic word just preceding the cursor
516
+ let i = cursor - 1;
517
+ while (i >= 0 && /[a-zA-Z]/.test(text[i])) {
518
+ i--;
519
+ }
520
+ const wordStart = i + 1;
521
+ const word = text.slice(wordStart, cursor);
522
+
523
+ if (word.length > 0) {
524
+ e.preventDefault(); // Stop space/enter from rendering immediately
525
+
526
+ try {
527
+ // Google Input Tools Public Transliteration API
528
+ const response = await fetch(`https://inputtools.google.com/request?text=${word}&itc=ml-t-i0-und&num=1`);
529
+ const data = await response.json();
530
+
531
+ // Extract the best transliteration suggestion
532
+ const mlWord = data?.[1]?.[0]?.[1]?.[0];
533
+
534
+ if (mlWord) {
535
+ // Insert translated word + the key they pressed (space or newline)
536
+ const insertStr = mlWord + (e.key === 'Enter' ? '\n' : ' ');
537
+ const before = text.slice(0, wordStart);
538
+ const after = text.slice(cursor);
539
+
540
+ editor.value = before + insertStr + after;
541
+
542
+ // Adjust cursor position
543
+ const newCursorPos = wordStart + insertStr.length;
544
+ editor.selectionStart = editor.selectionEnd = newCursorPos;
545
+ } else {
546
+ throw new Error("No transliteration found");
547
+ }
548
+ } catch (error) {
549
+ console.error("Transliteration Error:", error);
550
+ // Graceful Fallback: Just insert what they typed originally
551
+ const insertStr = word + (e.key === 'Enter' ? '\n' : ' ');
552
+ const before = text.slice(0, wordStart);
553
+ const after = text.slice(cursor);
554
+ editor.value = before + insertStr + after;
555
+ editor.selectionStart = editor.selectionEnd = wordStart + insertStr.length;
556
+ }
557
+
558
+ updateStats();
559
+ saveText();
560
+ }
561
+ }
562
+ });
563
+
564
+ </script>
565
+ </body>
566
+ </html>