@pfmcodes/caret 0.1.5 → 0.2.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/README.md +38 -30
- package/commonjs/editor.js +58 -11
- package/esm/editor.js +58 -11
- package/index.css +7 -2
- package/package.json +1 -1
- package/types/editor.ts +81 -31
- package/types/languages.ts +19 -0
package/README.md
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
# Caret
|
|
2
2
|
|
|
3
3
|
[](https://opensource.org/licenses/MIT)
|
|
4
|
-
[](https://github.com/PFMCODES/lexius-edior)
|
|
5
|
+
[](https://github.com/PFMCODES/lexius-edior)
|
|
6
|
+
[](https://github.com/pfmcodes/lexius-editor)
|
|
7
7
|
|
|
8
8
|
A lightweight, feature-rich code editor with real-time syntax highlighting and custom caret rendering. Built with vanilla JavaScript and powered by Highlight.js, caret delivers a smooth coding experience with professional-grade features.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Features
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
12
|
+
- **Live Syntax Highlighting** - Real-time code highlighting powered by Highlight.js
|
|
13
|
+
- **Custom Caret** - Smooth, pixel-perfect Caret positioning and rendering
|
|
14
|
+
- **Line Numbers** - Built-in line counter with dynamic updates
|
|
15
|
+
- **Smart Indentation** - Tab/Shift+Tab support for indenting/unindenting code blocks
|
|
16
|
+
- **Theme Support** - Multiple syntax highlighting themes (light/dark modes)
|
|
17
|
+
- **Smooth Scrolling** - Synchronized scrolling for code, highlights, and line numbers
|
|
18
|
+
- **ES Modules** - Modern ESM architecture for easy integration
|
|
19
|
+
- **TypeScript Ready** - Full TypeScript definitions included
|
|
20
|
+
- **Lightweight** - Pure JavaScript, no heavy frameworks required
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## Table of Contents
|
|
23
23
|
|
|
24
24
|
- [What's new](#What's-New?)
|
|
25
25
|
- [Installation](#installation)
|
|
@@ -29,12 +29,14 @@ A lightweight, feature-rich code editor with real-time syntax highlighting and c
|
|
|
29
29
|
- [Customization](#customization)
|
|
30
30
|
- [Contributing](#contributing)
|
|
31
31
|
- [License](#license)
|
|
32
|
+
- [Performance Notes](#performance-notes)
|
|
32
33
|
|
|
33
34
|
## What's-New?
|
|
34
35
|
|
|
35
|
-
###
|
|
36
|
+
### The editor has been optimized like crazy, the editor used to lag at 500 lines not it does not lag until 10k+ lines(x20 performance increase)
|
|
37
|
+
### for more info about the performance check out [this.](#performance-notes)
|
|
36
38
|
|
|
37
|
-
##
|
|
39
|
+
## Installation
|
|
38
40
|
|
|
39
41
|
### NPM
|
|
40
42
|
|
|
@@ -54,7 +56,7 @@ yarn add @pfmcodes/caret
|
|
|
54
56
|
pnpm add @pfmcodes/caret
|
|
55
57
|
```
|
|
56
58
|
|
|
57
|
-
##
|
|
59
|
+
## Quick Start
|
|
58
60
|
|
|
59
61
|
### Basic Usage
|
|
60
62
|
|
|
@@ -102,7 +104,7 @@ const editorInstance = await editor.editor.createEditor(
|
|
|
102
104
|
);
|
|
103
105
|
```
|
|
104
106
|
|
|
105
|
-
##
|
|
107
|
+
## Project Structure
|
|
106
108
|
|
|
107
109
|
```
|
|
108
110
|
caret/
|
|
@@ -116,7 +118,7 @@ caret/
|
|
|
116
118
|
└── LICENSE # MIT License
|
|
117
119
|
```
|
|
118
120
|
|
|
119
|
-
##
|
|
121
|
+
## Usage
|
|
120
122
|
|
|
121
123
|
### JavaScript Editor
|
|
122
124
|
|
|
@@ -168,7 +170,7 @@ const emptyEditor = await editor.createEditor(
|
|
|
168
170
|
);
|
|
169
171
|
```
|
|
170
172
|
|
|
171
|
-
##
|
|
173
|
+
## Customization
|
|
172
174
|
|
|
173
175
|
### Custom Styling
|
|
174
176
|
|
|
@@ -205,7 +207,7 @@ The editor comes with default styles that you can override:
|
|
|
205
207
|
}
|
|
206
208
|
```
|
|
207
209
|
|
|
208
|
-
##
|
|
210
|
+
## Advanced Features
|
|
209
211
|
|
|
210
212
|
### Multi-Language Support
|
|
211
213
|
|
|
@@ -290,7 +292,7 @@ Caret supports all Highlight.js themes. Popular options include:
|
|
|
290
292
|
- `stackoverflow-light`
|
|
291
293
|
- `xcode`
|
|
292
294
|
|
|
293
|
-
##
|
|
295
|
+
## API Reference
|
|
294
296
|
|
|
295
297
|
### createEditor(container, options)
|
|
296
298
|
|
|
@@ -339,7 +341,7 @@ The editor features a custom-rendered Caret that adapts to your theme (light/dar
|
|
|
339
341
|
#### Synchronized Scrolling
|
|
340
342
|
All editor components (code, highlights, line numbers) scroll together smoothly.
|
|
341
343
|
|
|
342
|
-
##
|
|
344
|
+
## Complete Example
|
|
343
345
|
|
|
344
346
|
Here's a complete working example:
|
|
345
347
|
|
|
@@ -457,7 +459,7 @@ for (let i = 0; i < 10; i++) {
|
|
|
457
459
|
</html>
|
|
458
460
|
```
|
|
459
461
|
|
|
460
|
-
##
|
|
462
|
+
## Technical Details
|
|
461
463
|
|
|
462
464
|
### How It Works
|
|
463
465
|
|
|
@@ -478,6 +480,7 @@ The editor synchronizes all layers during:
|
|
|
478
480
|
- **Real-time Highlighting**: Uses Highlight.js for fast, accurate syntax highlighting
|
|
479
481
|
- **Canvas Measurement**: Employs HTML5 Canvas API for precise text width calculations
|
|
480
482
|
- **Event Optimization**: Efficiently updates only what's necessary on each interaction
|
|
483
|
+
- **Heavy Optimization**: previous version(0.1.6) used to handle 500 lines before lagging, with new caret@0.2.0, there's almost 20 times perfomace increase now it can handle 10K+ lines smoothly before lagging(better results in optimized browsers like firfox)
|
|
481
484
|
|
|
482
485
|
### Browser Support
|
|
483
486
|
|
|
@@ -485,7 +488,7 @@ The editor synchronizes all layers during:
|
|
|
485
488
|
- Chrome, Firefox, Safari, Edge (latest versions)
|
|
486
489
|
- Requires JavaScript modules support
|
|
487
490
|
|
|
488
|
-
##
|
|
491
|
+
## Contributing
|
|
489
492
|
|
|
490
493
|
Contributions are welcome! Please follow these steps:
|
|
491
494
|
|
|
@@ -514,26 +517,31 @@ npm run dev
|
|
|
514
517
|
npm run build
|
|
515
518
|
```
|
|
516
519
|
|
|
517
|
-
##
|
|
520
|
+
## License
|
|
518
521
|
|
|
519
522
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
520
523
|
|
|
521
|
-
##
|
|
524
|
+
## Performance Notes
|
|
525
|
+
- Handles 10k+ lines smoothly in all browsers
|
|
526
|
+
- Firefox DevTools can inspect up to 3M lines without breaking a sweat
|
|
527
|
+
- Chrome DevTools politely requests you don't inspect past 300k lines
|
|
528
|
+
|
|
529
|
+
## Acknowledgments
|
|
522
530
|
|
|
523
531
|
- Built with modern JavaScript/TypeScript
|
|
524
532
|
- Syntax highlighting powered by Highlight.js
|
|
525
533
|
- Inspired by various text editor projects in the JavaScript ecosystem
|
|
526
534
|
|
|
527
|
-
##
|
|
535
|
+
## Support
|
|
528
536
|
|
|
529
537
|
- **Issues**: [GitHub Issues](https://github.com/pfmcodes/lexius-editor/issues)
|
|
530
538
|
- **Discussions**: [GitHub Discussions](https://github.com/pfmcodes/lexius-editor/discussions)
|
|
531
539
|
|
|
532
|
-
##
|
|
540
|
+
## Links
|
|
533
541
|
|
|
534
542
|
- [GitHub Repository](https://github.com/pfmcodes/lexius-editor)
|
|
535
543
|
- [NPM Package](https://www.npmjs.com/package/@pfmcodes/caret) *(if published)*
|
|
536
544
|
|
|
537
545
|
---
|
|
538
546
|
|
|
539
|
-
Made with ❤️ by [PFMCODES](https://github.com/PFMCODES)
|
|
547
|
+
Made with ❤️ by [PFMCODES](https://github.com/PFMCODES)
|
package/commonjs/editor.js
CHANGED
|
@@ -59,12 +59,13 @@ async function createEditor(editor, data) {
|
|
|
59
59
|
editor1.autocomplete = "off";
|
|
60
60
|
editor1.autocorrect = "off";
|
|
61
61
|
editor.style = "position: relative; width: 600px; height: 300px; overflow: hidden; /* 👈 CRITICAL */ font-size: 14px;"
|
|
62
|
-
if (code) {
|
|
63
|
-
editor1.value = code;
|
|
62
|
+
if (code && editor && editor1 && language && highlighted) {
|
|
64
63
|
editor1.style.paddingTop = "-9px";
|
|
65
|
-
|
|
64
|
+
console.log(data.value + " data.value");
|
|
65
|
+
editor1.value = data.value;
|
|
66
|
+
highlighted.innerHTML = await _render(data.value, language, editor1);
|
|
66
67
|
}
|
|
67
|
-
const keyDown = (e) => {
|
|
68
|
+
const keyDown = async (e) => {
|
|
68
69
|
if (e.key !== "Tab") return;
|
|
69
70
|
|
|
70
71
|
e.preventDefault();
|
|
@@ -117,7 +118,7 @@ async function createEditor(editor, data) {
|
|
|
117
118
|
editor1.selectionEnd =
|
|
118
119
|
end + delta * newLines.length;
|
|
119
120
|
|
|
120
|
-
highlighted.innerHTML =
|
|
121
|
+
highlighted.innerHTML = await _render(editor1.value, language, editor1);
|
|
121
122
|
updateLineNumbers();
|
|
122
123
|
updateCaret();
|
|
123
124
|
}
|
|
@@ -194,17 +195,17 @@ async function createEditor(editor, data) {
|
|
|
194
195
|
|
|
195
196
|
caret.style.height = `${lineHeight - 5}px`;
|
|
196
197
|
}
|
|
197
|
-
const input = () => {
|
|
198
|
+
const input = async () => {
|
|
198
199
|
caret.style.opacity = "1";
|
|
199
|
-
highlighted.innerHTML =
|
|
200
|
+
highlighted.innerHTML = await _render(editor1.value, language, editor1);
|
|
200
201
|
updateLineNumbers();
|
|
201
202
|
updateCaret();
|
|
202
203
|
};
|
|
203
204
|
editor1.addEventListener("input", input);
|
|
204
|
-
const scroll = () => {
|
|
205
|
+
const scroll = async () => {
|
|
205
206
|
const x = -editor1.scrollLeft;
|
|
206
207
|
const y = -editor1.scrollTop;
|
|
207
|
-
|
|
208
|
+
highlighted.innerHTML = await _render(editor1.value, language, editor1);
|
|
208
209
|
highlighted.style.transform = `translate(${x}px, ${y}px)`;
|
|
209
210
|
caret.style.transform = `translate(${x}px, ${y}px)`;
|
|
210
211
|
lineCounter.style.transform = `translateY(${y}px)`;
|
|
@@ -230,8 +231,8 @@ async function createEditor(editor, data) {
|
|
|
230
231
|
editor1.removeEventListener('keydown', keyDown);
|
|
231
232
|
editor.innerHTML = "";
|
|
232
233
|
}
|
|
233
|
-
function refresh() {
|
|
234
|
-
highlighted.innerHTML =
|
|
234
|
+
async function refresh() {
|
|
235
|
+
highlighted.innerHTML = await _render(editor1.value, language, editor1);
|
|
235
236
|
updateLineNumbers();
|
|
236
237
|
updateCaret();
|
|
237
238
|
}
|
|
@@ -263,6 +264,52 @@ async function createEditor(editor, data) {
|
|
|
263
264
|
};
|
|
264
265
|
}
|
|
265
266
|
|
|
267
|
+
function escapeHtml(str) {
|
|
268
|
+
return str
|
|
269
|
+
.replace(/&/g, "&")
|
|
270
|
+
.replace(/</g, "<")
|
|
271
|
+
.replace(/>/g, ">");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function _render(code, language, editor) {
|
|
275
|
+
// If no editor context provided, just highlight everything (initial load)
|
|
276
|
+
if (!editor) {
|
|
277
|
+
return hljs.highlight(code, { language }).value;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const scrollTop = editor.scrollTop;
|
|
281
|
+
const scrollBottom = scrollTop + editor.clientHeight;
|
|
282
|
+
const style = getComputedStyle(editor);
|
|
283
|
+
const lineHeight = parseFloat(style.lineHeight);
|
|
284
|
+
|
|
285
|
+
// Calculate visible line range
|
|
286
|
+
const startLine = Math.floor(scrollTop / lineHeight);
|
|
287
|
+
const endLine = Math.ceil(scrollBottom / lineHeight);
|
|
288
|
+
|
|
289
|
+
const lines = code.split("\n");
|
|
290
|
+
|
|
291
|
+
// Add buffer (render extra lines above/below for smooth scrolling)
|
|
292
|
+
const bufferLines = 10;
|
|
293
|
+
const visibleStart = Math.max(0, startLine - bufferLines) || 0;
|
|
294
|
+
const visibleEnd = Math.min(lines.length, endLine + bufferLines) || 0;
|
|
295
|
+
|
|
296
|
+
// Split into three sections
|
|
297
|
+
const beforeLines = lines.slice(0, visibleStart);
|
|
298
|
+
const visibleLines = lines.slice(visibleStart, visibleEnd);
|
|
299
|
+
const afterLines = lines.slice(visibleEnd);
|
|
300
|
+
|
|
301
|
+
// Only highlight visible portion
|
|
302
|
+
|
|
303
|
+
const highlightedVisible = hljs.highlight(visibleLines.join("\n"), { language }).value;
|
|
304
|
+
// Plain text for non-visible areas (no highlighting = faster)
|
|
305
|
+
if (highlightedVisible.trim() === "") {
|
|
306
|
+
return hljs.highlight(escapeHtml(code), { language }).value;
|
|
307
|
+
}
|
|
308
|
+
const beforeHTML = "\n".repeat(beforeLines.length);
|
|
309
|
+
const afterHTML = "\n".repeat(afterLines.length);
|
|
310
|
+
return beforeHTML + highlightedVisible + afterHTML;
|
|
311
|
+
}
|
|
312
|
+
|
|
266
313
|
const editor = {
|
|
267
314
|
createEditor
|
|
268
315
|
};
|
package/esm/editor.js
CHANGED
|
@@ -59,12 +59,13 @@ async function createEditor(editor, data) {
|
|
|
59
59
|
editor1.autocomplete = "off";
|
|
60
60
|
editor1.autocorrect = "off";
|
|
61
61
|
editor.style = "position: relative; width: 600px; height: 300px; overflow: hidden; /* 👈 CRITICAL */ font-size: 14px;"
|
|
62
|
-
if (code) {
|
|
63
|
-
editor1.value = code;
|
|
62
|
+
if (code && editor && editor1 && language && highlighted) {
|
|
64
63
|
editor1.style.paddingTop = "-9px";
|
|
65
|
-
|
|
64
|
+
console.log(data.value + " data.value");
|
|
65
|
+
editor1.value = data.value;
|
|
66
|
+
highlighted.innerHTML = await _render(data.value, language, editor1);
|
|
66
67
|
}
|
|
67
|
-
const keyDown = (e) => {
|
|
68
|
+
const keyDown = async (e) => {
|
|
68
69
|
if (e.key !== "Tab") return;
|
|
69
70
|
|
|
70
71
|
e.preventDefault();
|
|
@@ -117,7 +118,7 @@ async function createEditor(editor, data) {
|
|
|
117
118
|
editor1.selectionEnd =
|
|
118
119
|
end + delta * newLines.length;
|
|
119
120
|
|
|
120
|
-
highlighted.innerHTML =
|
|
121
|
+
highlighted.innerHTML = await _render(editor1.value, language, editor1);
|
|
121
122
|
updateLineNumbers();
|
|
122
123
|
updateCaret();
|
|
123
124
|
}
|
|
@@ -194,17 +195,17 @@ async function createEditor(editor, data) {
|
|
|
194
195
|
|
|
195
196
|
caret.style.height = `${lineHeight - 5}px`;
|
|
196
197
|
}
|
|
197
|
-
const input = () => {
|
|
198
|
+
const input = async () => {
|
|
198
199
|
caret.style.opacity = "1";
|
|
199
|
-
highlighted.innerHTML =
|
|
200
|
+
highlighted.innerHTML = await _render(editor1.value, language, editor1);
|
|
200
201
|
updateLineNumbers();
|
|
201
202
|
updateCaret();
|
|
202
203
|
};
|
|
203
204
|
editor1.addEventListener("input", input);
|
|
204
|
-
const scroll = () => {
|
|
205
|
+
const scroll = async () => {
|
|
205
206
|
const x = -editor1.scrollLeft;
|
|
206
207
|
const y = -editor1.scrollTop;
|
|
207
|
-
|
|
208
|
+
highlighted.innerHTML = await _render(editor1.value, language, editor1);
|
|
208
209
|
highlighted.style.transform = `translate(${x}px, ${y}px)`;
|
|
209
210
|
caret.style.transform = `translate(${x}px, ${y}px)`;
|
|
210
211
|
lineCounter.style.transform = `translateY(${y}px)`;
|
|
@@ -230,8 +231,8 @@ async function createEditor(editor, data) {
|
|
|
230
231
|
editor1.removeEventListener('keydown', keyDown);
|
|
231
232
|
editor.innerHTML = "";
|
|
232
233
|
}
|
|
233
|
-
function refresh() {
|
|
234
|
-
highlighted.innerHTML =
|
|
234
|
+
async function refresh() {
|
|
235
|
+
highlighted.innerHTML = await _render(editor1.value, language, editor1);
|
|
235
236
|
updateLineNumbers();
|
|
236
237
|
updateCaret();
|
|
237
238
|
}
|
|
@@ -263,6 +264,52 @@ async function createEditor(editor, data) {
|
|
|
263
264
|
};
|
|
264
265
|
}
|
|
265
266
|
|
|
267
|
+
function escapeHtml(str) {
|
|
268
|
+
return str
|
|
269
|
+
.replace(/&/g, "&")
|
|
270
|
+
.replace(/</g, "<")
|
|
271
|
+
.replace(/>/g, ">");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function _render(code, language, editor) {
|
|
275
|
+
// If no editor context provided, just highlight everything (initial load)
|
|
276
|
+
if (!editor) {
|
|
277
|
+
return hljs.highlight(code, { language }).value;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const scrollTop = editor.scrollTop;
|
|
281
|
+
const scrollBottom = scrollTop + editor.clientHeight;
|
|
282
|
+
const style = getComputedStyle(editor);
|
|
283
|
+
const lineHeight = parseFloat(style.lineHeight);
|
|
284
|
+
|
|
285
|
+
// Calculate visible line range
|
|
286
|
+
const startLine = Math.floor(scrollTop / lineHeight);
|
|
287
|
+
const endLine = Math.ceil(scrollBottom / lineHeight);
|
|
288
|
+
|
|
289
|
+
const lines = code.split("\n");
|
|
290
|
+
|
|
291
|
+
// Add buffer (render extra lines above/below for smooth scrolling)
|
|
292
|
+
const bufferLines = 10;
|
|
293
|
+
const visibleStart = Math.max(0, startLine - bufferLines) || 0;
|
|
294
|
+
const visibleEnd = Math.min(lines.length, endLine + bufferLines) || 0;
|
|
295
|
+
|
|
296
|
+
// Split into three sections
|
|
297
|
+
const beforeLines = lines.slice(0, visibleStart);
|
|
298
|
+
const visibleLines = lines.slice(visibleStart, visibleEnd);
|
|
299
|
+
const afterLines = lines.slice(visibleEnd);
|
|
300
|
+
|
|
301
|
+
// Only highlight visible portion
|
|
302
|
+
|
|
303
|
+
const highlightedVisible = hljs.highlight(visibleLines.join("\n"), { language }).value;
|
|
304
|
+
// Plain text for non-visible areas (no highlighting = faster)
|
|
305
|
+
if (highlightedVisible.trim() === "") {
|
|
306
|
+
return hljs.highlight(escapeHtml(code), { language }).value;
|
|
307
|
+
}
|
|
308
|
+
const beforeHTML = "\n".repeat(beforeLines.length);
|
|
309
|
+
const afterHTML = "\n".repeat(afterLines.length);
|
|
310
|
+
return beforeHTML + highlightedVisible + afterHTML;
|
|
311
|
+
}
|
|
312
|
+
|
|
266
313
|
const editor = {
|
|
267
314
|
createEditor
|
|
268
315
|
};
|
package/index.css
CHANGED
|
@@ -2,16 +2,21 @@
|
|
|
2
2
|
position: absolute;
|
|
3
3
|
border: none;
|
|
4
4
|
inset: 0;
|
|
5
|
-
padding: 10px;
|
|
5
|
+
padding: 10px 10px 10px 0px;
|
|
6
6
|
font-family: monospace;
|
|
7
7
|
background: transparent;
|
|
8
8
|
color: transparent;
|
|
9
9
|
caret-color: #fff;
|
|
10
10
|
border: 1px solid #ccc;
|
|
11
|
-
overflow: scroll; /* 👈 ONLY THIS SCROLLS */
|
|
11
|
+
overflow-x: scroll; /* 👈 ONLY THIS SCROLLS */
|
|
12
12
|
z-index: 1;
|
|
13
13
|
width: 100%;
|
|
14
14
|
}
|
|
15
|
+
|
|
16
|
+
#Caret-texarea::-webkit-scrollbar {
|
|
17
|
+
z-index: 3;
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
#Caret-textarea:focus {
|
|
16
21
|
outline: none;
|
|
17
22
|
}
|
package/package.json
CHANGED
package/types/editor.ts
CHANGED
|
@@ -3,15 +3,18 @@ import languages from "./languages.ts";
|
|
|
3
3
|
|
|
4
4
|
languages.init();
|
|
5
5
|
|
|
6
|
-
async function createEditor(editor: HTMLElement, data:
|
|
6
|
+
async function createEditor(editor: HTMLElement, data: { value?: string, language: string, theme?: string }) {
|
|
7
7
|
const editor1: HTMLTextAreaElement = document.createElement("textarea");
|
|
8
8
|
const highlighted: HTMLPreElement = document.createElement("pre");
|
|
9
9
|
const caret: HTMLDivElement = document.createElement("div");
|
|
10
10
|
const measureCanvas: HTMLCanvasElement = document.createElement("canvas");
|
|
11
|
-
const measureCtx: CanvasRenderingContext2D
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
const measureCtx: CanvasRenderingContext2D = measureCanvas.getContext("2d") as CanvasRenderingContext2D;
|
|
12
|
+
if (!measureCtx) {
|
|
13
|
+
throw new Error("Failed to get 2D context from canvas");
|
|
14
|
+
}
|
|
15
|
+
const isDark = data.theme && (data.theme.includes("dark") || data.theme.includes("night"));
|
|
16
|
+
const caretColor = isDark ? "#fff" : "#7116d8";
|
|
17
|
+
const lineColor = isDark ? "#fff" : "#000";
|
|
15
18
|
const lineCounter: HTMLDivElement = document.createElement("div");
|
|
16
19
|
|
|
17
20
|
editor1.id = "Caret-textarea";
|
|
@@ -23,32 +26,32 @@ async function createEditor(editor: HTMLElement, data: any) {
|
|
|
23
26
|
caret.className = 'dark';
|
|
24
27
|
lineCounter.className = 'dark';
|
|
25
28
|
editor1.style.backgroundColor = isDark ? "#222" : "#fff";
|
|
26
|
-
let code
|
|
27
|
-
let language
|
|
28
|
-
let theme
|
|
29
|
+
let code = data.value || "";
|
|
30
|
+
let language = data.language;
|
|
31
|
+
let theme = data.theme;
|
|
29
32
|
if (!languages.registeredLanguages.includes(language)) {
|
|
30
33
|
const mod = await import(`https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/${language}.js`);
|
|
31
34
|
languages.registerLanguage(language, mod.default);
|
|
32
35
|
languages.registeredLanguages.push(language);
|
|
33
36
|
}
|
|
34
37
|
if (theme) {
|
|
35
|
-
let themeLink = document.getElementById("Caret-theme")
|
|
38
|
+
let themeLink: HTMLLinkElement | null = document.getElementById("Caret-theme") as HTMLLinkElement;
|
|
36
39
|
if (!themeLink) {
|
|
37
40
|
const link = document.createElement("link");
|
|
38
41
|
link.rel = "stylesheet";
|
|
39
42
|
link.id = "Caret-theme";
|
|
40
|
-
link.href =
|
|
43
|
+
link.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/${theme}.css`;
|
|
41
44
|
document.head.appendChild(link);
|
|
42
45
|
} else {
|
|
43
|
-
themeLink.href =
|
|
46
|
+
themeLink.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/${theme}.css`;
|
|
44
47
|
}
|
|
45
48
|
} else {
|
|
46
|
-
let themeLink
|
|
49
|
+
let themeLink: HTMLLinkElement | null = document.getElementById("Caret-theme") as HTMLLinkElement;
|
|
47
50
|
if (!themeLink) {
|
|
48
51
|
const link = document.createElement("link");
|
|
49
52
|
link.rel = "stylesheet";
|
|
50
53
|
link.id = "Caret-theme";
|
|
51
|
-
link.href =
|
|
54
|
+
link.href = `https://esm.sh/@pfmcodes/highlight.js@1.0.0/styles/hybrid.css`;
|
|
52
55
|
document.head.appendChild(link);
|
|
53
56
|
} else {
|
|
54
57
|
themeLink.href = `./highlight.js/styles/hybrid.css`;
|
|
@@ -57,14 +60,15 @@ async function createEditor(editor: HTMLElement, data: any) {
|
|
|
57
60
|
editor1.spellcheck = false;
|
|
58
61
|
editor1.autocapitalize = "off";
|
|
59
62
|
editor1.autocomplete = "off";
|
|
60
|
-
|
|
63
|
+
editor1.autocorrect = "off" as any;
|
|
61
64
|
editor.style = "position: relative; width: 600px; height: 300px; overflow: hidden; /* 👈 CRITICAL */ font-size: 14px;"
|
|
62
|
-
if (code) {
|
|
63
|
-
editor1.value = code;
|
|
65
|
+
if (code && editor && editor1 && language && highlighted) {
|
|
64
66
|
editor1.style.paddingTop = "-9px";
|
|
65
|
-
|
|
67
|
+
console.log(data.value + " data.value");
|
|
68
|
+
editor1.value = data.value as string;
|
|
69
|
+
highlighted.innerHTML = await _render(code, language, editor1);
|
|
66
70
|
}
|
|
67
|
-
const keyDown = (e:
|
|
71
|
+
const keyDown = async (e: KeyboardEvent) => {
|
|
68
72
|
if (e.key !== "Tab") return;
|
|
69
73
|
|
|
70
74
|
e.preventDefault();
|
|
@@ -83,8 +87,8 @@ async function createEditor(editor: HTMLElement, data: any) {
|
|
|
83
87
|
const block = value.slice(lineStart, finalLineEnd);
|
|
84
88
|
const lines = block.split("\n");
|
|
85
89
|
|
|
86
|
-
let newLines
|
|
87
|
-
let delta
|
|
90
|
+
let newLines;
|
|
91
|
+
let delta = 0;
|
|
88
92
|
|
|
89
93
|
if (e.shiftKey) {
|
|
90
94
|
// UNINDENT
|
|
@@ -117,7 +121,7 @@ async function createEditor(editor: HTMLElement, data: any) {
|
|
|
117
121
|
editor1.selectionEnd =
|
|
118
122
|
end + delta * newLines.length;
|
|
119
123
|
|
|
120
|
-
highlighted.innerHTML =
|
|
124
|
+
highlighted.innerHTML = await _render(editor1.value, language, editor1);
|
|
121
125
|
updateLineNumbers();
|
|
122
126
|
updateCaret();
|
|
123
127
|
}
|
|
@@ -128,15 +132,15 @@ async function createEditor(editor: HTMLElement, data: any) {
|
|
|
128
132
|
editor.appendChild(caret);
|
|
129
133
|
|
|
130
134
|
function updateFontMetrics() {
|
|
131
|
-
const style
|
|
135
|
+
const style = getComputedStyle(editor1);
|
|
132
136
|
measureCtx.font = `${style.fontSize} ${style.fontFamily}`;
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
function updateLineNumbers() {
|
|
136
|
-
const lineCount
|
|
140
|
+
const lineCount = editor1.value.split("\n").length;
|
|
137
141
|
|
|
138
|
-
let html
|
|
139
|
-
for (let i
|
|
142
|
+
let html = "";
|
|
143
|
+
for (let i = 1; i <= lineCount; i++) {
|
|
140
144
|
html += `<div class="Caret-lineCounter-number" style="color: ${lineColor}">${i}</div>`;
|
|
141
145
|
}
|
|
142
146
|
|
|
@@ -194,17 +198,17 @@ async function createEditor(editor: HTMLElement, data: any) {
|
|
|
194
198
|
|
|
195
199
|
caret.style.height = `${lineHeight - 5}px`;
|
|
196
200
|
}
|
|
197
|
-
const input = () => {
|
|
201
|
+
const input = async () => {
|
|
198
202
|
caret.style.opacity = "1";
|
|
199
|
-
highlighted.innerHTML =
|
|
203
|
+
highlighted.innerHTML = await _render(editor1.value, language, editor1);
|
|
200
204
|
updateLineNumbers();
|
|
201
205
|
updateCaret();
|
|
202
206
|
};
|
|
203
207
|
editor1.addEventListener("input", input);
|
|
204
|
-
const scroll = () => {
|
|
208
|
+
const scroll = async () => {
|
|
205
209
|
const x = -editor1.scrollLeft;
|
|
206
210
|
const y = -editor1.scrollTop;
|
|
207
|
-
|
|
211
|
+
highlighted.innerHTML = await _render(editor1.value, language, editor1);
|
|
208
212
|
highlighted.style.transform = `translate(${x}px, ${y}px)`;
|
|
209
213
|
caret.style.transform = `translate(${x}px, ${y}px)`;
|
|
210
214
|
lineCounter.style.transform = `translateY(${y}px)`;
|
|
@@ -230,8 +234,8 @@ async function createEditor(editor: HTMLElement, data: any) {
|
|
|
230
234
|
editor1.removeEventListener('keydown', keyDown);
|
|
231
235
|
editor.innerHTML = "";
|
|
232
236
|
}
|
|
233
|
-
function refresh() {
|
|
234
|
-
highlighted.innerHTML =
|
|
237
|
+
async function refresh() {
|
|
238
|
+
highlighted.innerHTML = await _render(editor1.value, language, editor1);
|
|
235
239
|
updateLineNumbers();
|
|
236
240
|
updateCaret();
|
|
237
241
|
}
|
|
@@ -263,6 +267,52 @@ async function createEditor(editor: HTMLElement, data: any) {
|
|
|
263
267
|
};
|
|
264
268
|
}
|
|
265
269
|
|
|
270
|
+
function escapeHtml(str: string) {
|
|
271
|
+
return str
|
|
272
|
+
.replace(/&/g, "&")
|
|
273
|
+
.replace(/</g, "<")
|
|
274
|
+
.replace(/>/g, ">");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function _render(code: string, language: string, editor: HTMLElement) {
|
|
278
|
+
// If no editor context provided, just highlight everything (initial load)
|
|
279
|
+
if (!editor) {
|
|
280
|
+
return hljs.highlight(code, { language }).value;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const scrollTop = editor.scrollTop;
|
|
284
|
+
const scrollBottom = scrollTop + editor.clientHeight;
|
|
285
|
+
const style = getComputedStyle(editor);
|
|
286
|
+
const lineHeight = parseFloat(style.lineHeight);
|
|
287
|
+
|
|
288
|
+
// Calculate visible line range
|
|
289
|
+
const startLine = Math.floor(scrollTop / lineHeight);
|
|
290
|
+
const endLine = Math.ceil(scrollBottom / lineHeight);
|
|
291
|
+
|
|
292
|
+
const lines = code.split("\n");
|
|
293
|
+
|
|
294
|
+
// Add buffer (render extra lines above/below for smooth scrolling)
|
|
295
|
+
const bufferLines = 10;
|
|
296
|
+
const visibleStart = Math.max(0, startLine - bufferLines) || 0;
|
|
297
|
+
const visibleEnd = Math.min(lines.length, endLine + bufferLines) || 0;
|
|
298
|
+
|
|
299
|
+
// Split into three sections
|
|
300
|
+
const beforeLines = lines.slice(0, visibleStart);
|
|
301
|
+
const visibleLines = lines.slice(visibleStart, visibleEnd);
|
|
302
|
+
const afterLines = lines.slice(visibleEnd);
|
|
303
|
+
|
|
304
|
+
// Only highlight visible portion
|
|
305
|
+
|
|
306
|
+
const highlightedVisible = hljs.highlight(visibleLines.join("\n"), { language }).value;
|
|
307
|
+
// Plain text for non-visible areas (no highlighting = faster)
|
|
308
|
+
if (highlightedVisible.trim() === "") {
|
|
309
|
+
return hljs.highlight(escapeHtml(code), { language }).value;
|
|
310
|
+
}
|
|
311
|
+
const beforeHTML = "\n".repeat(beforeLines.length);
|
|
312
|
+
const afterHTML = "\n".repeat(afterLines.length);
|
|
313
|
+
return beforeHTML + highlightedVisible + afterHTML;
|
|
314
|
+
}
|
|
315
|
+
|
|
266
316
|
const editor = {
|
|
267
317
|
createEditor
|
|
268
318
|
};
|
package/types/languages.ts
CHANGED
|
@@ -1,21 +1,40 @@
|
|
|
1
|
+
// @ts-ignore
|
|
1
2
|
import javascript from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/javascript.js";
|
|
3
|
+
// @ts-ignore
|
|
2
4
|
import xml from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/xml.js";
|
|
5
|
+
// @ts-ignore
|
|
3
6
|
import css from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/css.js";
|
|
7
|
+
// @ts-ignore
|
|
4
8
|
import python from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/python.js";
|
|
9
|
+
// @ts-ignore
|
|
5
10
|
import java from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/java.js";
|
|
11
|
+
// @ts-ignore
|
|
6
12
|
import csharp from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/csharp.js";
|
|
13
|
+
// @ts-ignore
|
|
7
14
|
import cpp from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/cpp.js";
|
|
15
|
+
// @ts-ignore
|
|
8
16
|
import ruby from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/ruby.js";
|
|
17
|
+
// @ts-ignore
|
|
9
18
|
import php from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/php.js";
|
|
19
|
+
// @ts-ignore
|
|
10
20
|
import go from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/go.js";
|
|
21
|
+
// @ts-ignore
|
|
11
22
|
import c from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/c.js";
|
|
23
|
+
// @ts-ignore
|
|
12
24
|
import rust from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/rust.js";
|
|
25
|
+
// @ts-ignore
|
|
13
26
|
import kotlin from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/kotlin.js";
|
|
27
|
+
// @ts-ignore
|
|
14
28
|
import swift from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/swift.js";
|
|
29
|
+
// @ts-ignore
|
|
15
30
|
import typescript from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/typescript.js";
|
|
31
|
+
// @ts-ignore
|
|
16
32
|
import json from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/json.js";
|
|
33
|
+
// @ts-ignore
|
|
17
34
|
import bash from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/bash.js";
|
|
35
|
+
// @ts-ignore
|
|
18
36
|
import plaintext from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/languages/plaintext.js";
|
|
37
|
+
// @ts-ignore
|
|
19
38
|
import hljs from "https://esm.sh/@pfmcodes/highlight.js@1.0.0/es/core.js";
|
|
20
39
|
|
|
21
40
|
let registeredLanguages: Array<T> = [];
|