@webcoder49/code-input 1.5.0 → 1.5.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/.github/workflows/minify.yml +21 -21
- package/.github/workflows/npm-publish.yml +21 -0
- package/LICENSE +21 -21
- package/README.md +112 -115
- package/code-input.css +100 -100
- package/code-input.js +759 -422
- package/code-input.min.js +1 -1
- package/package.json +1 -1
- package/plugins/README.md +68 -68
- package/plugins/autocomplete.css +14 -14
- package/plugins/autocomplete.js +79 -79
- package/plugins/autodetect.js +28 -28
- package/plugins/debounce-update.js +40 -40
- package/plugins/indent.js +153 -153
- package/plugins/indent.min.js +1 -1
- package/plugins/prism-line-numbers.css +19 -19
- package/plugins/special-chars.css +97 -97
- package/plugins/special-chars.js +218 -218
- package/plugins/special-chars.min.css +4 -4
- package/plugins/test.js +37 -37
package/plugins/special-chars.js
CHANGED
|
@@ -1,219 +1,219 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Render special characters and control characters as a symbol with their hex code.
|
|
3
|
-
* Files: special-chars.js, special-chars.css
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// INCOMPLETE: TODO Optimise regex - compile at start; Update CSS for character display; clean up + comment
|
|
7
|
-
|
|
8
|
-
codeInput.plugins.SpecialChars = class extends codeInput.Plugin {
|
|
9
|
-
specialCharRegExp;
|
|
10
|
-
|
|
11
|
-
cachedColors; // ascii number > [background color, text color]
|
|
12
|
-
cachedWidths; // font > {character > character width}
|
|
13
|
-
canvasContext;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Create a special characters plugin instance
|
|
17
|
-
* @param {Boolean} colorInSpecialChars Whether or not to give special characters custom background colors based on their hex code
|
|
18
|
-
* @param {Boolean} inheritTextColor If `colorInSpecialChars` is false, forces the color of the hex code to inherit from syntax highlighting. Otherwise, the base colour of the `pre code` element is used to give contrast to the small characters.
|
|
19
|
-
* @param {RegExp} specialCharRegExp The regular expression which matches special characters
|
|
20
|
-
*/
|
|
21
|
-
constructor(colorInSpecialChars = false, inheritTextColor = false, specialCharRegExp = /(?!\n)(?!\t)[\u{0000}-\u{001F}]|[\u{007F}-\u{009F}]|[\u{0200}-\u{FFFF}]/ug) { // By default, covers many non-renderable ASCII characters
|
|
22
|
-
super();
|
|
23
|
-
|
|
24
|
-
this.specialCharRegExp = specialCharRegExp;
|
|
25
|
-
this.colorInSpecialChars = colorInSpecialChars;
|
|
26
|
-
this.inheritTextColor = inheritTextColor;
|
|
27
|
-
|
|
28
|
-
this.cachedColors = {};
|
|
29
|
-
this.cachedWidths = {};
|
|
30
|
-
|
|
31
|
-
let canvas = document.createElement("canvas");
|
|
32
|
-
this.canvasContext = canvas.getContext("2d");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/* Runs before elements are added into a `code-input`; Params: codeInput element) */
|
|
36
|
-
beforeElementsAdded(codeInput) {
|
|
37
|
-
codeInput.classList.add("code-input_special-char_container");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/* Runs after elements are added into a `code-input` (useful for adding events to the textarea); Params: codeInput element) */
|
|
41
|
-
afterElementsAdded(codeInput) {
|
|
42
|
-
// For some reason, special chars aren't synced the first time - TODO is there a cleaner way to do this?
|
|
43
|
-
setTimeout(() => { codeInput.update(codeInput.value); }, 100);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/* Runs after code is highlighted; Params: codeInput element) */
|
|
47
|
-
afterHighlight(codeInput) {
|
|
48
|
-
let result_element = codeInput.querySelector("pre code");
|
|
49
|
-
|
|
50
|
-
// Reset data each highlight so can change if font size, etc. changes
|
|
51
|
-
codeInput.pluginData.specialChars = {};
|
|
52
|
-
codeInput.pluginData.specialChars.textarea = codeInput.getElementsByTagName("textarea")[0];
|
|
53
|
-
codeInput.pluginData.specialChars.contrastColor = window.getComputedStyle(result_element).color;
|
|
54
|
-
|
|
55
|
-
this.recursivelyReplaceText(codeInput, result_element);
|
|
56
|
-
|
|
57
|
-
this.lastFont = window.getComputedStyle(codeInput.pluginData.specialChars.textarea).font;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
recursivelyReplaceText(codeInput, element) {
|
|
61
|
-
for(let i = 0; i < element.childNodes.length; i++) {
|
|
62
|
-
|
|
63
|
-
let nextNode = element.childNodes[i];
|
|
64
|
-
if(nextNode.nodeName == "#text" && nextNode.nodeValue != "") {
|
|
65
|
-
// Replace in here
|
|
66
|
-
let oldValue = nextNode.nodeValue;
|
|
67
|
-
|
|
68
|
-
this.specialCharRegExp.lastIndex = 0;
|
|
69
|
-
let searchResult = this.specialCharRegExp.exec(oldValue);
|
|
70
|
-
if(searchResult != null) {
|
|
71
|
-
let charIndex = searchResult.index; // Start as returns end
|
|
72
|
-
|
|
73
|
-
nextNode = nextNode.splitText(charIndex+1).previousSibling;
|
|
74
|
-
|
|
75
|
-
if(charIndex > 0) {
|
|
76
|
-
nextNode = nextNode.splitText(charIndex); // Keep those before in difft. span
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if(nextNode.textContent != "") {
|
|
80
|
-
let replacementElement = this.specialCharReplacer(codeInput, nextNode.textContent);
|
|
81
|
-
nextNode.parentNode.insertBefore(replacementElement, nextNode);
|
|
82
|
-
nextNode.textContent = "";
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
} else if(nextNode.nodeType == 1) {
|
|
86
|
-
if(nextNode.className != "code-input_special-char" && nextNode.nodeValue != "") {
|
|
87
|
-
// Element - recurse
|
|
88
|
-
this.recursivelyReplaceText(codeInput, nextNode);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
specialCharReplacer(codeInput, match_char) {
|
|
95
|
-
let hex_code = match_char.codePointAt(0);
|
|
96
|
-
|
|
97
|
-
let colors;
|
|
98
|
-
if(this.colorInSpecialChars) colors = this.getCharacterColor(hex_code);
|
|
99
|
-
|
|
100
|
-
hex_code = hex_code.toString(16);
|
|
101
|
-
hex_code = ("0000" + hex_code).substring(hex_code.length); // So 2 chars with leading 0
|
|
102
|
-
hex_code = hex_code.toUpperCase();
|
|
103
|
-
|
|
104
|
-
let char_width = this.getCharacterWidth(codeInput, match_char);
|
|
105
|
-
|
|
106
|
-
// Create element with hex code
|
|
107
|
-
let result = document.createElement("span");
|
|
108
|
-
result.classList.add("code-input_special-char");
|
|
109
|
-
result.style.setProperty("--hex-0", "var(--code-input_special-chars_" + hex_code[0] + ")");
|
|
110
|
-
result.style.setProperty("--hex-1", "var(--code-input_special-chars_" + hex_code[1] + ")");
|
|
111
|
-
result.style.setProperty("--hex-2", "var(--code-input_special-chars_" + hex_code[2] + ")");
|
|
112
|
-
result.style.setProperty("--hex-3", "var(--code-input_special-chars_" + hex_code[3] + ")");
|
|
113
|
-
|
|
114
|
-
// Handle zero-width chars
|
|
115
|
-
if(char_width == 0) result.classList.add("code-input_special-char_zero-width");
|
|
116
|
-
else result.style.width = char_width + "px";
|
|
117
|
-
|
|
118
|
-
if(this.colorInSpecialChars) {
|
|
119
|
-
result.style.backgroundColor = "#" + colors[0];
|
|
120
|
-
result.style.setProperty("--code-input_special-char_color", colors[1]);
|
|
121
|
-
} else if(!this.inheritTextColor) {
|
|
122
|
-
result.style.setProperty("--code-input_special-char_color", codeInput.pluginData.specialChars.contrastColor);
|
|
123
|
-
}
|
|
124
|
-
return result;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
getCharacterColor(ascii_code) {
|
|
128
|
-
// Choose colors based on character code - lazy load and return [background color, text color]
|
|
129
|
-
let background_color;
|
|
130
|
-
let text_color;
|
|
131
|
-
if(!(ascii_code in this.cachedColors)) {
|
|
132
|
-
// Get background color - arbitrary bit manipulation to get a good range of colours
|
|
133
|
-
background_color = ascii_code^(ascii_code << 3)^(ascii_code << 7)^(ascii_code << 14)^(ascii_code << 16); // Arbitrary
|
|
134
|
-
background_color = background_color^0x1fc627; // Arbitrary
|
|
135
|
-
background_color = background_color.toString(16);
|
|
136
|
-
background_color = ("000000" + background_color).substring(background_color.length); // So 6 chars with leading 0
|
|
137
|
-
|
|
138
|
-
// Get most suitable text color - white or black depending on background brightness
|
|
139
|
-
let color_brightness = 0;
|
|
140
|
-
let luminance_coefficients = [0.299, 0.587, 0.114];
|
|
141
|
-
for(let i = 0; i < 6; i += 2) {
|
|
142
|
-
color_brightness += parseInt(background_color.substring(i, i+2), 16) * luminance_coefficients[i/2];
|
|
143
|
-
}
|
|
144
|
-
// Calculate darkness
|
|
145
|
-
text_color = color_brightness < 128 ? "white" : "black";
|
|
146
|
-
|
|
147
|
-
// console.log(background_color, color_brightness, text_color);
|
|
148
|
-
|
|
149
|
-
this.cachedColors[ascii_code] = [background_color, text_color];
|
|
150
|
-
return [background_color, text_color];
|
|
151
|
-
} else {
|
|
152
|
-
return this.cachedColors[ascii_code];
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
getCharacterWidth(codeInput, char) { // TODO: Check StackOverflow question
|
|
157
|
-
// Force zero-width characters
|
|
158
|
-
if(new RegExp("\u00AD|\u02de|[\u0300-\u036F]|[\u0483-\u0489]|\u200b").test(char) ) { return 0 }
|
|
159
|
-
// Non-renderable ASCII characters should all be rendered at same size
|
|
160
|
-
if(char != "\u0096" && new RegExp("[\u{0000}-\u{001F}]|[\u{007F}-\u{009F}]", "g").test(char)) {
|
|
161
|
-
let fallbackWidth = this.getCharacterWidth("\u0096");
|
|
162
|
-
return fallbackWidth;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
let font = window.getComputedStyle(codeInput.pluginData.specialChars.textarea).font;
|
|
166
|
-
|
|
167
|
-
// Lazy-load - TODO: Get a cleaner way of doing this
|
|
168
|
-
if(this.cachedWidths[font] == undefined) {
|
|
169
|
-
this.cachedWidths[font] = {}; // Create new cached widths for this font
|
|
170
|
-
}
|
|
171
|
-
if(this.cachedWidths[font][char] != undefined) { // Use cached width
|
|
172
|
-
return this.cachedWidths[font][char];
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Ensure font the same
|
|
176
|
-
// console.log(font);
|
|
177
|
-
this.canvasContext.font = font;
|
|
178
|
-
|
|
179
|
-
// Try to get width from canvas
|
|
180
|
-
let width = this.canvasContext.measureText(char).width;
|
|
181
|
-
if(width > Number(font.split("px")[0])) {
|
|
182
|
-
width /= 2; // Fix double-width-in-canvas Firefox bug
|
|
183
|
-
} else if(width == 0 && char != "\u0096") {
|
|
184
|
-
let fallbackWidth = this.getCharacterWidth("\u0096");
|
|
185
|
-
return fallbackWidth; // In Firefox some control chars don't render, but all control chars are the same width
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
this.cachedWidths[font][char] = width;
|
|
189
|
-
|
|
190
|
-
// console.log(this.cachedWidths);
|
|
191
|
-
return width;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// getCharacterWidth(char) { // Doesn't work for now - from StackOverflow suggestion https://stackoverflow.com/a/76146120/21785620
|
|
195
|
-
// let textarea = codeInput.pluginData.specialChars.textarea;
|
|
196
|
-
|
|
197
|
-
// // Create a temporary element to measure the width of the character
|
|
198
|
-
// const span = document.createElement('span');
|
|
199
|
-
// span.textContent = char;
|
|
200
|
-
|
|
201
|
-
// // Copy the textarea's font to the temporary element
|
|
202
|
-
// span.style.fontSize = window.getComputedStyle(textarea).fontSize;
|
|
203
|
-
// span.style.fontFamily = window.getComputedStyle(textarea).fontFamily;
|
|
204
|
-
// span.style.fontWeight = window.getComputedStyle(textarea).fontWeight;
|
|
205
|
-
// span.style.visibility = 'hidden';
|
|
206
|
-
// span.style.position = 'absolute';
|
|
207
|
-
|
|
208
|
-
// // Add the temporary element to the document so we can measure its width
|
|
209
|
-
// document.body.appendChild(span);
|
|
210
|
-
|
|
211
|
-
// // Get the width of the character in pixels
|
|
212
|
-
// const width = span.offsetWidth;
|
|
213
|
-
|
|
214
|
-
// // Remove the temporary element from the document
|
|
215
|
-
// document.body.removeChild(span);
|
|
216
|
-
|
|
217
|
-
// return width;
|
|
218
|
-
// }
|
|
1
|
+
/**
|
|
2
|
+
* Render special characters and control characters as a symbol with their hex code.
|
|
3
|
+
* Files: special-chars.js, special-chars.css
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// INCOMPLETE: TODO Optimise regex - compile at start; Update CSS for character display; clean up + comment
|
|
7
|
+
|
|
8
|
+
codeInput.plugins.SpecialChars = class extends codeInput.Plugin {
|
|
9
|
+
specialCharRegExp;
|
|
10
|
+
|
|
11
|
+
cachedColors; // ascii number > [background color, text color]
|
|
12
|
+
cachedWidths; // font > {character > character width}
|
|
13
|
+
canvasContext;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create a special characters plugin instance
|
|
17
|
+
* @param {Boolean} colorInSpecialChars Whether or not to give special characters custom background colors based on their hex code
|
|
18
|
+
* @param {Boolean} inheritTextColor If `colorInSpecialChars` is false, forces the color of the hex code to inherit from syntax highlighting. Otherwise, the base colour of the `pre code` element is used to give contrast to the small characters.
|
|
19
|
+
* @param {RegExp} specialCharRegExp The regular expression which matches special characters
|
|
20
|
+
*/
|
|
21
|
+
constructor(colorInSpecialChars = false, inheritTextColor = false, specialCharRegExp = /(?!\n)(?!\t)[\u{0000}-\u{001F}]|[\u{007F}-\u{009F}]|[\u{0200}-\u{FFFF}]/ug) { // By default, covers many non-renderable ASCII characters
|
|
22
|
+
super();
|
|
23
|
+
|
|
24
|
+
this.specialCharRegExp = specialCharRegExp;
|
|
25
|
+
this.colorInSpecialChars = colorInSpecialChars;
|
|
26
|
+
this.inheritTextColor = inheritTextColor;
|
|
27
|
+
|
|
28
|
+
this.cachedColors = {};
|
|
29
|
+
this.cachedWidths = {};
|
|
30
|
+
|
|
31
|
+
let canvas = document.createElement("canvas");
|
|
32
|
+
this.canvasContext = canvas.getContext("2d");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Runs before elements are added into a `code-input`; Params: codeInput element) */
|
|
36
|
+
beforeElementsAdded(codeInput) {
|
|
37
|
+
codeInput.classList.add("code-input_special-char_container");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Runs after elements are added into a `code-input` (useful for adding events to the textarea); Params: codeInput element) */
|
|
41
|
+
afterElementsAdded(codeInput) {
|
|
42
|
+
// For some reason, special chars aren't synced the first time - TODO is there a cleaner way to do this?
|
|
43
|
+
setTimeout(() => { codeInput.update(codeInput.value); }, 100);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Runs after code is highlighted; Params: codeInput element) */
|
|
47
|
+
afterHighlight(codeInput) {
|
|
48
|
+
let result_element = codeInput.querySelector("pre code");
|
|
49
|
+
|
|
50
|
+
// Reset data each highlight so can change if font size, etc. changes
|
|
51
|
+
codeInput.pluginData.specialChars = {};
|
|
52
|
+
codeInput.pluginData.specialChars.textarea = codeInput.getElementsByTagName("textarea")[0];
|
|
53
|
+
codeInput.pluginData.specialChars.contrastColor = window.getComputedStyle(result_element).color;
|
|
54
|
+
|
|
55
|
+
this.recursivelyReplaceText(codeInput, result_element);
|
|
56
|
+
|
|
57
|
+
this.lastFont = window.getComputedStyle(codeInput.pluginData.specialChars.textarea).font;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
recursivelyReplaceText(codeInput, element) {
|
|
61
|
+
for(let i = 0; i < element.childNodes.length; i++) {
|
|
62
|
+
|
|
63
|
+
let nextNode = element.childNodes[i];
|
|
64
|
+
if(nextNode.nodeName == "#text" && nextNode.nodeValue != "") {
|
|
65
|
+
// Replace in here
|
|
66
|
+
let oldValue = nextNode.nodeValue;
|
|
67
|
+
|
|
68
|
+
this.specialCharRegExp.lastIndex = 0;
|
|
69
|
+
let searchResult = this.specialCharRegExp.exec(oldValue);
|
|
70
|
+
if(searchResult != null) {
|
|
71
|
+
let charIndex = searchResult.index; // Start as returns end
|
|
72
|
+
|
|
73
|
+
nextNode = nextNode.splitText(charIndex+1).previousSibling;
|
|
74
|
+
|
|
75
|
+
if(charIndex > 0) {
|
|
76
|
+
nextNode = nextNode.splitText(charIndex); // Keep those before in difft. span
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if(nextNode.textContent != "") {
|
|
80
|
+
let replacementElement = this.specialCharReplacer(codeInput, nextNode.textContent);
|
|
81
|
+
nextNode.parentNode.insertBefore(replacementElement, nextNode);
|
|
82
|
+
nextNode.textContent = "";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} else if(nextNode.nodeType == 1) {
|
|
86
|
+
if(nextNode.className != "code-input_special-char" && nextNode.nodeValue != "") {
|
|
87
|
+
// Element - recurse
|
|
88
|
+
this.recursivelyReplaceText(codeInput, nextNode);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
specialCharReplacer(codeInput, match_char) {
|
|
95
|
+
let hex_code = match_char.codePointAt(0);
|
|
96
|
+
|
|
97
|
+
let colors;
|
|
98
|
+
if(this.colorInSpecialChars) colors = this.getCharacterColor(hex_code);
|
|
99
|
+
|
|
100
|
+
hex_code = hex_code.toString(16);
|
|
101
|
+
hex_code = ("0000" + hex_code).substring(hex_code.length); // So 2 chars with leading 0
|
|
102
|
+
hex_code = hex_code.toUpperCase();
|
|
103
|
+
|
|
104
|
+
let char_width = this.getCharacterWidth(codeInput, match_char);
|
|
105
|
+
|
|
106
|
+
// Create element with hex code
|
|
107
|
+
let result = document.createElement("span");
|
|
108
|
+
result.classList.add("code-input_special-char");
|
|
109
|
+
result.style.setProperty("--hex-0", "var(--code-input_special-chars_" + hex_code[0] + ")");
|
|
110
|
+
result.style.setProperty("--hex-1", "var(--code-input_special-chars_" + hex_code[1] + ")");
|
|
111
|
+
result.style.setProperty("--hex-2", "var(--code-input_special-chars_" + hex_code[2] + ")");
|
|
112
|
+
result.style.setProperty("--hex-3", "var(--code-input_special-chars_" + hex_code[3] + ")");
|
|
113
|
+
|
|
114
|
+
// Handle zero-width chars
|
|
115
|
+
if(char_width == 0) result.classList.add("code-input_special-char_zero-width");
|
|
116
|
+
else result.style.width = char_width + "px";
|
|
117
|
+
|
|
118
|
+
if(this.colorInSpecialChars) {
|
|
119
|
+
result.style.backgroundColor = "#" + colors[0];
|
|
120
|
+
result.style.setProperty("--code-input_special-char_color", colors[1]);
|
|
121
|
+
} else if(!this.inheritTextColor) {
|
|
122
|
+
result.style.setProperty("--code-input_special-char_color", codeInput.pluginData.specialChars.contrastColor);
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getCharacterColor(ascii_code) {
|
|
128
|
+
// Choose colors based on character code - lazy load and return [background color, text color]
|
|
129
|
+
let background_color;
|
|
130
|
+
let text_color;
|
|
131
|
+
if(!(ascii_code in this.cachedColors)) {
|
|
132
|
+
// Get background color - arbitrary bit manipulation to get a good range of colours
|
|
133
|
+
background_color = ascii_code^(ascii_code << 3)^(ascii_code << 7)^(ascii_code << 14)^(ascii_code << 16); // Arbitrary
|
|
134
|
+
background_color = background_color^0x1fc627; // Arbitrary
|
|
135
|
+
background_color = background_color.toString(16);
|
|
136
|
+
background_color = ("000000" + background_color).substring(background_color.length); // So 6 chars with leading 0
|
|
137
|
+
|
|
138
|
+
// Get most suitable text color - white or black depending on background brightness
|
|
139
|
+
let color_brightness = 0;
|
|
140
|
+
let luminance_coefficients = [0.299, 0.587, 0.114];
|
|
141
|
+
for(let i = 0; i < 6; i += 2) {
|
|
142
|
+
color_brightness += parseInt(background_color.substring(i, i+2), 16) * luminance_coefficients[i/2];
|
|
143
|
+
}
|
|
144
|
+
// Calculate darkness
|
|
145
|
+
text_color = color_brightness < 128 ? "white" : "black";
|
|
146
|
+
|
|
147
|
+
// console.log(background_color, color_brightness, text_color);
|
|
148
|
+
|
|
149
|
+
this.cachedColors[ascii_code] = [background_color, text_color];
|
|
150
|
+
return [background_color, text_color];
|
|
151
|
+
} else {
|
|
152
|
+
return this.cachedColors[ascii_code];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getCharacterWidth(codeInput, char) { // TODO: Check StackOverflow question
|
|
157
|
+
// Force zero-width characters
|
|
158
|
+
if(new RegExp("\u00AD|\u02de|[\u0300-\u036F]|[\u0483-\u0489]|\u200b").test(char) ) { return 0 }
|
|
159
|
+
// Non-renderable ASCII characters should all be rendered at same size
|
|
160
|
+
if(char != "\u0096" && new RegExp("[\u{0000}-\u{001F}]|[\u{007F}-\u{009F}]", "g").test(char)) {
|
|
161
|
+
let fallbackWidth = this.getCharacterWidth("\u0096");
|
|
162
|
+
return fallbackWidth;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let font = window.getComputedStyle(codeInput.pluginData.specialChars.textarea).font;
|
|
166
|
+
|
|
167
|
+
// Lazy-load - TODO: Get a cleaner way of doing this
|
|
168
|
+
if(this.cachedWidths[font] == undefined) {
|
|
169
|
+
this.cachedWidths[font] = {}; // Create new cached widths for this font
|
|
170
|
+
}
|
|
171
|
+
if(this.cachedWidths[font][char] != undefined) { // Use cached width
|
|
172
|
+
return this.cachedWidths[font][char];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Ensure font the same
|
|
176
|
+
// console.log(font);
|
|
177
|
+
this.canvasContext.font = font;
|
|
178
|
+
|
|
179
|
+
// Try to get width from canvas
|
|
180
|
+
let width = this.canvasContext.measureText(char).width;
|
|
181
|
+
if(width > Number(font.split("px")[0])) {
|
|
182
|
+
width /= 2; // Fix double-width-in-canvas Firefox bug
|
|
183
|
+
} else if(width == 0 && char != "\u0096") {
|
|
184
|
+
let fallbackWidth = this.getCharacterWidth("\u0096");
|
|
185
|
+
return fallbackWidth; // In Firefox some control chars don't render, but all control chars are the same width
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.cachedWidths[font][char] = width;
|
|
189
|
+
|
|
190
|
+
// console.log(this.cachedWidths);
|
|
191
|
+
return width;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// getCharacterWidth(char) { // Doesn't work for now - from StackOverflow suggestion https://stackoverflow.com/a/76146120/21785620
|
|
195
|
+
// let textarea = codeInput.pluginData.specialChars.textarea;
|
|
196
|
+
|
|
197
|
+
// // Create a temporary element to measure the width of the character
|
|
198
|
+
// const span = document.createElement('span');
|
|
199
|
+
// span.textContent = char;
|
|
200
|
+
|
|
201
|
+
// // Copy the textarea's font to the temporary element
|
|
202
|
+
// span.style.fontSize = window.getComputedStyle(textarea).fontSize;
|
|
203
|
+
// span.style.fontFamily = window.getComputedStyle(textarea).fontFamily;
|
|
204
|
+
// span.style.fontWeight = window.getComputedStyle(textarea).fontWeight;
|
|
205
|
+
// span.style.visibility = 'hidden';
|
|
206
|
+
// span.style.position = 'absolute';
|
|
207
|
+
|
|
208
|
+
// // Add the temporary element to the document so we can measure its width
|
|
209
|
+
// document.body.appendChild(span);
|
|
210
|
+
|
|
211
|
+
// // Get the width of the character in pixels
|
|
212
|
+
// const width = span.offsetWidth;
|
|
213
|
+
|
|
214
|
+
// // Remove the temporary element from the document
|
|
215
|
+
// document.body.removeChild(span);
|
|
216
|
+
|
|
217
|
+
// return width;
|
|
218
|
+
// }
|
|
219
219
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
:root,body{--code-input_special-chars_0:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABtJREFUGFdjZGBgYPj///9/RhCAMcA0bg6yHgAPmh/6BoxTcQAAAABJRU5ErkJgggAA');--code-input_special-chars_1:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABZJREFUGFdjZGBgYPj///9/RhAggwMAitIUBr9U6sYAAAAASUVORK5CYII=');--code-input_special-chars_2:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAAB9JREFUGFdj/P///38GKGCEMUCCjCgyYBFGRrAKFBkAuLYT9kYcIu0AAAAASUVORK5CYII=');--code-input_special-chars_3:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABhJREFUGFdj/P///38GKGCEMUCCjMTJAACYiBPyG8sfAgAAAABJRU5ErkJggg==');--code-input_special-chars_4:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAAB5JREFUGFdj/P///39GRkZGMI3BYYACRhgDrAKZAwAYxhvyz0DRIQAAAABJRU5ErkJggg==');--code-input_special-chars_5:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAACJJREFUGFdj/P///38GKGAEcRgZGRlBfDAHLgNjgFUgywAAuR4T9hxJl2YAAAAASUVORK5CYII=');--code-input_special-chars_6:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAACBJREFUGFdj/P///38GKGAEcRgZGRlBfDAHQwasAlkGABcdF/Y4yco2AAAAAElFTkSuQmCC');--code-input_special-chars_7:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABZJREFUGFdj/P///38GKGCEMUCCRHIAWMgT8kue3bQAAAAASUVORK5CYII=');--code-input_special-chars_8:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABlJREFUGFdj/P///38GKGAEcRgZGSE0cTIAvHcb8v+mIfAAAAAASUVORK5CYII=');--code-input_special-chars_9:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAAB9JREFUGFdj/P///38GKGAEcRgZGSE0igxMCVgGmQMAPqcX8hWL1K0AAAAASUVORK5CYII=');--code-input_special-chars_A:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAACBJREFUGFdjZGBgYPj///9/RhCAMcA0iADJggCmDEw5ALdxH/aGuYHqAAAAAElFTkSuQmCC');--code-input_special-chars_B:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABlJREFUGFdj/P///38GBgYGRhAAceA0cTIAvc0b/vRDnVoAAAAASUVORK5CYII=');--code-input_special-chars_C:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAAB5JREFUGFdjZGBgYPj///9/EM0IYjAyMjIS4CDrAQC57hP+uLwvFQAAAABJRU5ErkJggg==');--code-input_special-chars_D:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABtJREFUGFdj/P///38GBgYGRhAAceA0fg5MDwAveh/6ToN9VwAAAABJRU5ErkJggg==');--code-input_special-chars_E:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABxJREFUGFdj/P///38GKGAEcRgZGRlBfDCHsAwA2UwT+mVIH1MAAAAASUVORK5CYII=');--code-input_special-chars_F:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAAB5JREFUGFdj/P///38GKGAEcRgZGRlBfDAHtwxMGQDZZhP+BnB1kwAAAABJRU5ErkJggg==')}.code-input_special-char_container{font-size:20px}.code-input_special-char{display:inline-block;position:relative;top:0;left:0;height:1em;overflow:hidden;text-decoration:none;text-shadow:none;vertical-align:middle;outline:.1px solid currentColor;--hex-0:var(
|
|
2
|
-
--code-input_special-chars_0);--hex-1:var(
|
|
3
|
-
--code-input_special-chars_0);--hex-2:var(
|
|
4
|
-
--code-input_special-chars_0);--hex-3:var(
|
|
1
|
+
:root,body{--code-input_special-chars_0:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABtJREFUGFdjZGBgYPj///9/RhCAMcA0bg6yHgAPmh/6BoxTcQAAAABJRU5ErkJgggAA');--code-input_special-chars_1:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABZJREFUGFdjZGBgYPj///9/RhAggwMAitIUBr9U6sYAAAAASUVORK5CYII=');--code-input_special-chars_2:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAAB9JREFUGFdj/P///38GKGCEMUCCjCgyYBFGRrAKFBkAuLYT9kYcIu0AAAAASUVORK5CYII=');--code-input_special-chars_3:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABhJREFUGFdj/P///38GKGCEMUCCjMTJAACYiBPyG8sfAgAAAABJRU5ErkJggg==');--code-input_special-chars_4:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAAB5JREFUGFdj/P///39GRkZGMI3BYYACRhgDrAKZAwAYxhvyz0DRIQAAAABJRU5ErkJggg==');--code-input_special-chars_5:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAACJJREFUGFdj/P///38GKGAEcRgZGRlBfDAHLgNjgFUgywAAuR4T9hxJl2YAAAAASUVORK5CYII=');--code-input_special-chars_6:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAACBJREFUGFdj/P///38GKGAEcRgZGRlBfDAHQwasAlkGABcdF/Y4yco2AAAAAElFTkSuQmCC');--code-input_special-chars_7:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABZJREFUGFdj/P///38GKGCEMUCCRHIAWMgT8kue3bQAAAAASUVORK5CYII=');--code-input_special-chars_8:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABlJREFUGFdj/P///38GKGAEcRgZGSE0cTIAvHcb8v+mIfAAAAAASUVORK5CYII=');--code-input_special-chars_9:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAAB9JREFUGFdj/P///38GKGAEcRgZGSE0igxMCVgGmQMAPqcX8hWL1K0AAAAASUVORK5CYII=');--code-input_special-chars_A:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAACBJREFUGFdjZGBgYPj///9/RhCAMcA0iADJggCmDEw5ALdxH/aGuYHqAAAAAElFTkSuQmCC');--code-input_special-chars_B:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABlJREFUGFdj/P///38GBgYGRhAAceA0cTIAvc0b/vRDnVoAAAAASUVORK5CYII=');--code-input_special-chars_C:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAAB5JREFUGFdjZGBgYPj///9/EM0IYjAyMjIS4CDrAQC57hP+uLwvFQAAAABJRU5ErkJggg==');--code-input_special-chars_D:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABtJREFUGFdj/P///38GBgYGRhAAceA0fg5MDwAveh/6ToN9VwAAAABJRU5ErkJggg==');--code-input_special-chars_E:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAABxJREFUGFdj/P///38GKGAEcRgZGRlBfDCHsAwA2UwT+mVIH1MAAAAASUVORK5CYII=');--code-input_special-chars_F:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAAB5JREFUGFdj/P///38GKGAEcRgZGRlBfDAHtwxMGQDZZhP+BnB1kwAAAABJRU5ErkJggg==')}.code-input_special-char_container{font-size:20px}.code-input_special-char{display:inline-block;position:relative;top:0;left:0;height:1em;overflow:hidden;text-decoration:none;text-shadow:none;vertical-align:middle;outline:.1px solid currentColor;--hex-0:var(
|
|
2
|
+
--code-input_special-chars_0);--hex-1:var(
|
|
3
|
+
--code-input_special-chars_0);--hex-2:var(
|
|
4
|
+
--code-input_special-chars_0);--hex-3:var(
|
|
5
5
|
--code-input_special-chars_0)}.code-input_special-char::before{margin-left:50%;transform:translate(-50%,0);content:" ";background-color:var(--code-input_special-char_color,currentColor);image-rendering:pixelated;display:inline-block;width:calc(100%-2px);height:100%;mask-image:var(--hex-0),var(--hex-1),var(--hex-2),var(--hex-3);mask-repeat:no-repeat,no-repeat,no-repeat,no-repeat;mask-size:40%,40%,40%,40%;mask-position:10% 10%,90% 10%,10% 90%,90% 90%;-webkit-mask-image:var(--hex-0),var(--hex-1),var(--hex-2),var(--hex-3);-webkit-mask-repeat:no-repeat,no-repeat,no-repeat,no-repeat;-webkit-mask-size:min(40%,.25em),min(40%,.25em),min(40%,.25em),min(40%,.25em);-webkit-mask-position:10% 10%,min(90%,.5em) 10%,10% 90%,min(90%,.5em) 90%}.code-input_special-char_zero-width{z-index:1;width:1em;margin-left:-.5em;margin-right:-.5em;position:relative;opacity:.75}.code-input_special-char_one-byte::before{height:1.5em;top:-1em;content:attr(data-hex2)}.code-input_special-char_one-byte::after{height:1.5em;bottom:-1em;content:attr(data-hex3)}
|
package/plugins/test.js
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copy this to create a plugin, which brings extra,
|
|
3
|
-
* non-central optional functionality to code-input.
|
|
4
|
-
* Instances of plugins can be passed in in an array
|
|
5
|
-
* to the `plugins` argument when registering a template,
|
|
6
|
-
* for example like this:
|
|
7
|
-
* ```javascript
|
|
8
|
-
* codeInput.registerTemplate("syntax-highlighted", codeInput.templates.hljs(hljs, [new codeInput.plugins.Test()]));
|
|
9
|
-
* ```
|
|
10
|
-
*/
|
|
11
|
-
codeInput.plugins.Test = class extends codeInput.Plugin {
|
|
12
|
-
constructor() {
|
|
13
|
-
super();
|
|
14
|
-
}
|
|
15
|
-
/* Runs before code is highlighted; Params: codeInput element) */
|
|
16
|
-
beforeHighlight(codeInput) {
|
|
17
|
-
console.log(codeInput, "before highlight");
|
|
18
|
-
}
|
|
19
|
-
/* Runs after code is highlighted; Params: codeInput element) */
|
|
20
|
-
afterHighlight(codeInput) {
|
|
21
|
-
console.log(codeInput, "after highlight");
|
|
22
|
-
}
|
|
23
|
-
/* Runs before elements are added into a `code-input`; Params: codeInput element) */
|
|
24
|
-
beforeElementsAdded(codeInput) {
|
|
25
|
-
console.log(codeInput, "before elements added");
|
|
26
|
-
}
|
|
27
|
-
/* Runs after elements are added into a `code-input` (useful for adding events to the textarea); Params: codeInput element) */
|
|
28
|
-
afterElementsAdded(codeInput) {
|
|
29
|
-
console.log(codeInput, "after elements added");
|
|
30
|
-
}
|
|
31
|
-
/* Runs when an attribute of a `code-input` is changed (you must add the attribute name to observedAttributes); Params: codeInput element, name attribute name, oldValue previous value of attribute, newValue changed value of attribute) */
|
|
32
|
-
attributeChanged(codeInput, name, oldValue, newValue) {
|
|
33
|
-
if(name == "testattr") {
|
|
34
|
-
console.log(codeInput, "testattr:", oldValue, ">", newValue);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
observedAttributes = ["testattr"]
|
|
1
|
+
/**
|
|
2
|
+
* Copy this to create a plugin, which brings extra,
|
|
3
|
+
* non-central optional functionality to code-input.
|
|
4
|
+
* Instances of plugins can be passed in in an array
|
|
5
|
+
* to the `plugins` argument when registering a template,
|
|
6
|
+
* for example like this:
|
|
7
|
+
* ```javascript
|
|
8
|
+
* codeInput.registerTemplate("syntax-highlighted", codeInput.templates.hljs(hljs, [new codeInput.plugins.Test()]));
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
codeInput.plugins.Test = class extends codeInput.Plugin {
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
}
|
|
15
|
+
/* Runs before code is highlighted; Params: codeInput element) */
|
|
16
|
+
beforeHighlight(codeInput) {
|
|
17
|
+
console.log(codeInput, "before highlight");
|
|
18
|
+
}
|
|
19
|
+
/* Runs after code is highlighted; Params: codeInput element) */
|
|
20
|
+
afterHighlight(codeInput) {
|
|
21
|
+
console.log(codeInput, "after highlight");
|
|
22
|
+
}
|
|
23
|
+
/* Runs before elements are added into a `code-input`; Params: codeInput element) */
|
|
24
|
+
beforeElementsAdded(codeInput) {
|
|
25
|
+
console.log(codeInput, "before elements added");
|
|
26
|
+
}
|
|
27
|
+
/* Runs after elements are added into a `code-input` (useful for adding events to the textarea); Params: codeInput element) */
|
|
28
|
+
afterElementsAdded(codeInput) {
|
|
29
|
+
console.log(codeInput, "after elements added");
|
|
30
|
+
}
|
|
31
|
+
/* Runs when an attribute of a `code-input` is changed (you must add the attribute name to observedAttributes); Params: codeInput element, name attribute name, oldValue previous value of attribute, newValue changed value of attribute) */
|
|
32
|
+
attributeChanged(codeInput, name, oldValue, newValue) {
|
|
33
|
+
if(name == "testattr") {
|
|
34
|
+
console.log(codeInput, "testattr:", oldValue, ">", newValue);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
observedAttributes = ["testattr"]
|
|
38
38
|
}
|