@ape-egg/codie 0.1.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 +232 -0
- package/codie.css +7 -0
- package/codie.js +252 -0
- package/constants.js +228 -0
- package/editable.js +196 -0
- package/foldable.js +185 -0
- package/format.js +195 -0
- package/highlight-css.css +14 -0
- package/highlight-html.css +41 -0
- package/highlight-js.css +25 -0
- package/highlight-json.css +21 -0
- package/highlight.js +423 -0
- package/keyboard.js +61 -0
- package/numberRows.js +51 -0
- package/package.json +29 -0
- package/structure.css +194 -0
- package/theme.css +102 -0
package/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# Codie
|
|
2
|
+
|
|
3
|
+
Modular code display and editing package for the web.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
**Codie** is a lightweight, modular code highlighter and editor that bridges the gap between static code display and full in-browser IDEs. Built with vanilla JavaScript, it provides syntax highlighting, live editing, and seamless integration with reactive frameworks like Vibe.
|
|
8
|
+
|
|
9
|
+
**Vision:** Codie is a spectrum from static highlighter to full in-browser IDE:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Minimal (vanilla) → Editor → Full REPL (vibe-powered)
|
|
13
|
+
highlight + format +editable +live output, inspect,
|
|
14
|
+
+numberedRows visual editing, zero-build HMR
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
### Core Features
|
|
20
|
+
|
|
21
|
+
- **Syntax Highlighting**: HTML, CSS, and JavaScript with language-aware highlighting
|
|
22
|
+
- **Editable Mode**: Transform static code into a live editor with textarea overlay
|
|
23
|
+
- **Line Numbers**: Optional numbered rows for code reference
|
|
24
|
+
- **Template Support**: Use `<template codie>` to preserve HTML without browser parsing
|
|
25
|
+
- **Language Modes**: Granular control via attributes (`highlight-html`, `highlight-css`, `highlight-js`)
|
|
26
|
+
- **Dark/Light Themes**: Toggle with `[dark]` attribute
|
|
27
|
+
- **Runtime State Toggle**: Change features dynamically via `codieRef.editable`, `codieRef.numberedRows`
|
|
28
|
+
|
|
29
|
+
### Editing Features
|
|
30
|
+
|
|
31
|
+
- **Tab Handling**: Tab key inserts 2 spaces
|
|
32
|
+
- **Multi-line Indent**: Select multiple lines + Tab indents all
|
|
33
|
+
- **Outdent**: Shift+Tab removes indentation
|
|
34
|
+
- **Live Preview**: `onEdit` callback fires on every change
|
|
35
|
+
- **Error Handling**: `onError` callback for execution failures
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @ape-egg/codie
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### Basic Syntax Highlighting
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<link rel="stylesheet" href="@ape-egg/codie/codie.css" />
|
|
49
|
+
<script type="module">
|
|
50
|
+
import codie from '@ape-egg/codie';
|
|
51
|
+
|
|
52
|
+
codie('[codie]');
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template codie>
|
|
56
|
+
<div>
|
|
57
|
+
<p>Hello, world!</p>
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Editable Code with Line Numbers
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
import codie from '@ape-egg/codie';
|
|
66
|
+
|
|
67
|
+
const editor = codie('[codie]');
|
|
68
|
+
editor.editable = true;
|
|
69
|
+
editor.numberedRows = true;
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Live Preview with onEdit
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
const outputRef = document.querySelector('output');
|
|
76
|
+
|
|
77
|
+
const editor = codie('[codie]');
|
|
78
|
+
editor.editable = true;
|
|
79
|
+
editor.onEdit = ({ formatted, raw }) => {
|
|
80
|
+
outputRef.innerHTML = formatted;
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Language-Specific Highlighting
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<!-- Only highlight HTML -->
|
|
88
|
+
<template codie highlight-html>...</template>
|
|
89
|
+
|
|
90
|
+
<!-- Only highlight CSS -->
|
|
91
|
+
<template codie highlight-css>...</template>
|
|
92
|
+
|
|
93
|
+
<!-- Only highlight JavaScript -->
|
|
94
|
+
<template codie highlight-js>...</template>
|
|
95
|
+
|
|
96
|
+
<!-- Highlight all languages (default) -->
|
|
97
|
+
<template codie>...</template>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## API
|
|
101
|
+
|
|
102
|
+
### `codie(selector | element | config)`
|
|
103
|
+
|
|
104
|
+
Initialize a codie instance.
|
|
105
|
+
|
|
106
|
+
**Parameters:**
|
|
107
|
+
- `selector` (string): Query selector for codie element
|
|
108
|
+
- `element` (Element): DOM element reference
|
|
109
|
+
- `config` (object): Configuration object with optional `editable`, `numberedRows`, `foldable` properties
|
|
110
|
+
|
|
111
|
+
**Returns:** Codie instance proxy
|
|
112
|
+
|
|
113
|
+
### Instance Properties
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
const editor = codie('[codie]');
|
|
117
|
+
|
|
118
|
+
// Get/set code content
|
|
119
|
+
editor.code; // Get current code
|
|
120
|
+
editor.code = '<div>New code</div>'; // Set code (updates display + textarea)
|
|
121
|
+
|
|
122
|
+
// Toggle features
|
|
123
|
+
editor.editable = true; // Enable editing
|
|
124
|
+
editor.numberedRows = true; // Show line numbers
|
|
125
|
+
editor.foldable = false; // Disable code folding (experimental)
|
|
126
|
+
|
|
127
|
+
// Callbacks
|
|
128
|
+
editor.onEdit = ({ formatted, raw }) => {
|
|
129
|
+
// formatted: cleaned up for display
|
|
130
|
+
// raw: exact code content
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
editor.onError = (error) => {
|
|
134
|
+
// Handle errors during editing
|
|
135
|
+
};
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Exported Utilities
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
import {
|
|
142
|
+
highlightHTML,
|
|
143
|
+
highlightJS,
|
|
144
|
+
highlightCSS,
|
|
145
|
+
highlightJSRaw,
|
|
146
|
+
escapeHTML,
|
|
147
|
+
unescapeHTML,
|
|
148
|
+
formatDocument,
|
|
149
|
+
normalizeInlineWhitespace,
|
|
150
|
+
ATTR,
|
|
151
|
+
CLASS,
|
|
152
|
+
} from '@ape-egg/codie';
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Attributes
|
|
156
|
+
|
|
157
|
+
### Container Attributes
|
|
158
|
+
|
|
159
|
+
- `codie` - Marks element as codie instance
|
|
160
|
+
- `dehydrate` - Skip initialization (for documentation)
|
|
161
|
+
- `highlight-html` - Enable HTML highlighting only
|
|
162
|
+
- `highlight-css` - Enable CSS highlighting only
|
|
163
|
+
- `highlight-js` - Enable JavaScript highlighting only
|
|
164
|
+
- `dark` - Apply dark theme
|
|
165
|
+
|
|
166
|
+
### Runtime Attributes (applied by codie)
|
|
167
|
+
|
|
168
|
+
- `codie-editable` - Applied when editable mode is active
|
|
169
|
+
- `codie-numbered` - Applied when line numbers are shown
|
|
170
|
+
- `codie-foldable` - Applied when folding is enabled (experimental)
|
|
171
|
+
|
|
172
|
+
## Architecture
|
|
173
|
+
|
|
174
|
+
Codie uses a **layered approach** with CSS Grid:
|
|
175
|
+
|
|
176
|
+
1. **Display Layer** (`<pre>`) - Syntax-highlighted code
|
|
177
|
+
2. **Textarea Layer** (optional) - Editable overlay when `editable = true`
|
|
178
|
+
3. **Line Numbers** (optional) - Positioned overlay when `numberedRows = true`
|
|
179
|
+
|
|
180
|
+
All layers are synchronized via a reactive proxy that updates the DOM when properties change.
|
|
181
|
+
|
|
182
|
+
## Vibe Integration
|
|
183
|
+
|
|
184
|
+
Codie pairs naturally with Vibe for live, reactive code editing:
|
|
185
|
+
|
|
186
|
+
```js
|
|
187
|
+
import { init } from '@ape-egg/vibe';
|
|
188
|
+
import codie from '@ape-egg/codie';
|
|
189
|
+
|
|
190
|
+
init({ output: '' });
|
|
191
|
+
|
|
192
|
+
const editor = codie('[codie]');
|
|
193
|
+
editor.editable = true;
|
|
194
|
+
editor.onEdit = ({ formatted }) => {
|
|
195
|
+
$.output = formatted;
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```html
|
|
200
|
+
<template codie>
|
|
201
|
+
<p>Edit me!</p>
|
|
202
|
+
</template>
|
|
203
|
+
|
|
204
|
+
<output>@[output]</output>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Roadmap
|
|
208
|
+
|
|
209
|
+
### Implemented
|
|
210
|
+
|
|
211
|
+
- ✓ Syntax highlighting (HTML, CSS, JS)
|
|
212
|
+
- ✓ Editable mode with textarea overlay
|
|
213
|
+
- ✓ Line numbers
|
|
214
|
+
- ✓ Tab handling (insert spaces, multi-line indent, outdent)
|
|
215
|
+
- ✓ onEdit/onError callbacks
|
|
216
|
+
- ✓ Template support
|
|
217
|
+
- ✓ Language modes
|
|
218
|
+
- ✓ Dark/light themes
|
|
219
|
+
|
|
220
|
+
### Planned
|
|
221
|
+
|
|
222
|
+
- Self-documenting REPL (execute code from editor)
|
|
223
|
+
- Smart script execution (only re-run when changed)
|
|
224
|
+
- Vibe exclusion zones (prevent parent vibe from watching preview)
|
|
225
|
+
- Hot state preservation (preserve state across re-execution)
|
|
226
|
+
- Code folding (deferred due to complexity with editable mode)
|
|
227
|
+
- Inspect mode (click preview element to highlight code)
|
|
228
|
+
- Visual editing (drag-drop reorder in preview)
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT
|
package/codie.css
ADDED
package/codie.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// Codie - Modular Code Display & Editing
|
|
2
|
+
import { ATTR, CLASS } from './constants.js';
|
|
3
|
+
import {
|
|
4
|
+
highlightHTML,
|
|
5
|
+
highlightJS,
|
|
6
|
+
highlightCSS,
|
|
7
|
+
highlightJSON,
|
|
8
|
+
highlightJSRaw,
|
|
9
|
+
highlightCSSOnly,
|
|
10
|
+
highlightJSOnly,
|
|
11
|
+
} from './highlight.js';
|
|
12
|
+
import {
|
|
13
|
+
escapeHTML,
|
|
14
|
+
unescapeHTML,
|
|
15
|
+
formatDocument,
|
|
16
|
+
dedent,
|
|
17
|
+
normalizeInlineWhitespace,
|
|
18
|
+
cleanupBooleanAttrs,
|
|
19
|
+
} from './format.js';
|
|
20
|
+
import { initNumberRows, updateNumberRows } from './numberRows.js';
|
|
21
|
+
import { initEditable, destroyEditable } from './editable.js';
|
|
22
|
+
import { initFoldable, destroyFoldable } from './foldable.js';
|
|
23
|
+
|
|
24
|
+
// Detect which languages to highlight from element attributes
|
|
25
|
+
const getHighlightModes = (el) => {
|
|
26
|
+
const hasHTML = el.hasAttribute(ATTR.HIGHLIGHT_HTML);
|
|
27
|
+
const hasCSS = el.hasAttribute(ATTR.HIGHLIGHT_CSS);
|
|
28
|
+
const hasJS = el.hasAttribute(ATTR.HIGHLIGHT_JS);
|
|
29
|
+
const hasJSON = el.hasAttribute(ATTR.HIGHLIGHT_JSON);
|
|
30
|
+
const explicit = hasHTML || hasCSS || hasJS || hasJSON;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
html: !explicit || hasHTML,
|
|
34
|
+
css: !explicit || hasCSS,
|
|
35
|
+
js: !explicit || hasJS,
|
|
36
|
+
json: !explicit || hasJSON,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Highlight code content based on enabled modes
|
|
41
|
+
const highlight = (code, modes, options = {}) => {
|
|
42
|
+
const escaped = escapeHTML(code);
|
|
43
|
+
|
|
44
|
+
// When all modes are enabled (no explicit attribute), detect JSON by content
|
|
45
|
+
const looksLikeJSON = /^\s*[{\[]/.test(code);
|
|
46
|
+
|
|
47
|
+
// JSON takes priority over HTML when content looks like JSON
|
|
48
|
+
if (modes.json && looksLikeJSON) {
|
|
49
|
+
return highlightJSON(escaped);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (modes.html && !looksLikeJSON) {
|
|
53
|
+
return highlightHTML(escaped, {
|
|
54
|
+
...options,
|
|
55
|
+
highlightJS: modes.js,
|
|
56
|
+
highlightCSS: modes.css,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Fallback: explicit mode selected or no HTML detected
|
|
61
|
+
if (modes.json && !modes.html) {
|
|
62
|
+
return highlightJSON(escaped);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let result = escaped;
|
|
66
|
+
if (modes.js) result = highlightJSOnly(result);
|
|
67
|
+
if (modes.css) result = highlightCSSOnly(result);
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Toggle a feature on the instance
|
|
72
|
+
const toggleFeature = (instance, feature, enabled) => {
|
|
73
|
+
const { el, display, modes } = instance;
|
|
74
|
+
|
|
75
|
+
switch (feature) {
|
|
76
|
+
case 'numberedRows':
|
|
77
|
+
if (enabled && !instance.lineNumbers) {
|
|
78
|
+
instance.lineNumbers = initNumberRows(el, instance._code);
|
|
79
|
+
el.setAttribute(ATTR.NUMBERED, '');
|
|
80
|
+
} else if (!enabled && instance.lineNumbers) {
|
|
81
|
+
instance.lineNumbers.remove();
|
|
82
|
+
instance.lineNumbers = null;
|
|
83
|
+
el.removeAttribute(ATTR.NUMBERED);
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
|
|
87
|
+
case 'editable':
|
|
88
|
+
if (enabled && !instance.textarea) {
|
|
89
|
+
instance.textarea = initEditable(el, instance, instance._config);
|
|
90
|
+
el.setAttribute(ATTR.EDITABLE, '');
|
|
91
|
+
} else if (!enabled && instance.textarea) {
|
|
92
|
+
destroyEditable(instance);
|
|
93
|
+
el.removeAttribute(ATTR.EDITABLE);
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
|
|
97
|
+
case 'foldable':
|
|
98
|
+
if (enabled && !el.hasAttribute(ATTR.FOLDABLE)) {
|
|
99
|
+
initFoldable(el, display, modes);
|
|
100
|
+
el.setAttribute(ATTR.FOLDABLE, '');
|
|
101
|
+
} else if (!enabled && el.hasAttribute(ATTR.FOLDABLE)) {
|
|
102
|
+
destroyFoldable(el, display);
|
|
103
|
+
el.removeAttribute(ATTR.FOLDABLE);
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Main init function - one call per codie element
|
|
110
|
+
const codie = (elOrConfig = {}) => {
|
|
111
|
+
// Determine element and config
|
|
112
|
+
let el, config;
|
|
113
|
+
|
|
114
|
+
if (elOrConfig instanceof Element) {
|
|
115
|
+
el = elOrConfig;
|
|
116
|
+
config = {};
|
|
117
|
+
} else if (typeof elOrConfig === 'string') {
|
|
118
|
+
el = document.querySelector(elOrConfig);
|
|
119
|
+
config = {};
|
|
120
|
+
} else {
|
|
121
|
+
config = elOrConfig;
|
|
122
|
+
el = document.querySelector(`[${ATTR.CODIE}]`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!el) {
|
|
126
|
+
console.info('[codie] No element found');
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Skip dehydrated elements
|
|
131
|
+
if (el.hasAttribute(ATTR.DEHYDRATE)) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const modes = getHighlightModes(el);
|
|
136
|
+
let raw = el.tagName === 'TEMPLATE' ? el.innerHTML : el.textContent;
|
|
137
|
+
|
|
138
|
+
// Decode HTML entities from template innerHTML
|
|
139
|
+
raw = unescapeHTML(raw);
|
|
140
|
+
|
|
141
|
+
// Strip empty values from boolean attributes
|
|
142
|
+
raw = cleanupBooleanAttrs(raw);
|
|
143
|
+
|
|
144
|
+
const code = dedent(raw);
|
|
145
|
+
|
|
146
|
+
// Create display layer
|
|
147
|
+
const display = document.createElement('pre');
|
|
148
|
+
display.className = `${CLASS.CODE_LAYER} ${CLASS.DISPLAY}`;
|
|
149
|
+
display.innerHTML = highlight(code, modes, {
|
|
150
|
+
preserveWhitespace: !config.foldable,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Clear and setup element
|
|
154
|
+
el.textContent = '';
|
|
155
|
+
el.appendChild(display);
|
|
156
|
+
|
|
157
|
+
// Internal state
|
|
158
|
+
const internal = {
|
|
159
|
+
el,
|
|
160
|
+
display,
|
|
161
|
+
modes,
|
|
162
|
+
_code: code,
|
|
163
|
+
_config: { ...config },
|
|
164
|
+
textarea: null,
|
|
165
|
+
lineNumbers: null,
|
|
166
|
+
onEdit: null,
|
|
167
|
+
onError: null,
|
|
168
|
+
_editable: config.editable || false,
|
|
169
|
+
_numberedRows: config.numberedRows || false,
|
|
170
|
+
_foldable: config.foldable || false,
|
|
171
|
+
_proxy: null, // Will hold reference to proxy for editable.js
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Create reactive proxy
|
|
175
|
+
const instance = new Proxy(internal, {
|
|
176
|
+
get(target, prop) {
|
|
177
|
+
if (prop === 'code') return target._code;
|
|
178
|
+
if (prop === 'editable') return target._editable;
|
|
179
|
+
if (prop === 'numberedRows') return target._numberedRows;
|
|
180
|
+
if (prop === 'foldable') return target._foldable;
|
|
181
|
+
return target[prop];
|
|
182
|
+
},
|
|
183
|
+
set(target, prop, value) {
|
|
184
|
+
if (prop === 'code') {
|
|
185
|
+
// Decode HTML entities when setting code (same as initialization)
|
|
186
|
+
const decoded = unescapeHTML(value);
|
|
187
|
+
target._code = decoded;
|
|
188
|
+
target.display.innerHTML = highlight(decoded, target.modes, {
|
|
189
|
+
preserveWhitespace: !target._foldable,
|
|
190
|
+
});
|
|
191
|
+
if (target.lineNumbers) {
|
|
192
|
+
updateNumberRows(target.lineNumbers, decoded);
|
|
193
|
+
}
|
|
194
|
+
if (target.textarea) {
|
|
195
|
+
target.textarea.value = decoded;
|
|
196
|
+
}
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (prop === 'editable' && value !== target._editable) {
|
|
201
|
+
target._editable = value;
|
|
202
|
+
toggleFeature(target, 'editable', value);
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (prop === 'numberedRows' && value !== target._numberedRows) {
|
|
207
|
+
target._numberedRows = value;
|
|
208
|
+
toggleFeature(target, 'numberedRows', value);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (prop === 'foldable' && value !== target._foldable) {
|
|
213
|
+
target._foldable = value;
|
|
214
|
+
toggleFeature(target, 'foldable', value);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Fire onEdit immediately when callback is set
|
|
219
|
+
if (prop === 'onEdit' && typeof value === 'function') {
|
|
220
|
+
target.onEdit = value;
|
|
221
|
+
value({ formatted: normalizeInlineWhitespace(cleanupBooleanAttrs(target._code)), raw: target._code });
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
target[prop] = value;
|
|
226
|
+
return true;
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Store proxy reference for editable.js to access callbacks
|
|
231
|
+
internal._proxy = instance;
|
|
232
|
+
|
|
233
|
+
// Initialize features based on config
|
|
234
|
+
if (config.numberedRows) {
|
|
235
|
+
toggleFeature(internal, 'numberedRows', true);
|
|
236
|
+
}
|
|
237
|
+
if (config.editable) {
|
|
238
|
+
toggleFeature(internal, 'editable', true);
|
|
239
|
+
}
|
|
240
|
+
if (config.foldable) {
|
|
241
|
+
toggleFeature(internal, 'foldable', true);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return instance;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export default codie;
|
|
248
|
+
|
|
249
|
+
// Export utilities for direct use
|
|
250
|
+
export { highlightHTML, highlightJS, highlightCSS, highlightJSRaw };
|
|
251
|
+
export { escapeHTML, unescapeHTML, formatDocument, normalizeInlineWhitespace };
|
|
252
|
+
export { ATTR, CLASS };
|
package/constants.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// Codie constants - DRY naming foundation
|
|
2
|
+
|
|
3
|
+
// Attribute names for codie elements
|
|
4
|
+
export const ATTR = {
|
|
5
|
+
CODIE: 'codie',
|
|
6
|
+
HIGHLIGHT_HTML: 'highlight-html',
|
|
7
|
+
HIGHLIGHT_CSS: 'highlight-css',
|
|
8
|
+
HIGHLIGHT_JS: 'highlight-js',
|
|
9
|
+
HIGHLIGHT_JSON: 'highlight-json',
|
|
10
|
+
NUMBERED: 'numbered',
|
|
11
|
+
EDITABLE: 'editable',
|
|
12
|
+
FOLDABLE: 'foldable',
|
|
13
|
+
DEHYDRATE: 'dehydrate',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// CSS class names
|
|
17
|
+
export const CLASS = {
|
|
18
|
+
// Layout layers
|
|
19
|
+
CODE_LAYER: 'codie-layer',
|
|
20
|
+
TEXTAREA: 'codie-textarea',
|
|
21
|
+
DISPLAY: 'codie-display',
|
|
22
|
+
LINE_NUMBERS: 'codie-line-numbers',
|
|
23
|
+
|
|
24
|
+
// States
|
|
25
|
+
DARK: 'codie-dark',
|
|
26
|
+
HAS_SELECTION: 'codie-has-selection',
|
|
27
|
+
FOCUSED: 'codie-focused',
|
|
28
|
+
|
|
29
|
+
// Line highlighting
|
|
30
|
+
LINE: 'codie-line',
|
|
31
|
+
LINE_HIGHLIGHT: 'codie-line-highlight',
|
|
32
|
+
|
|
33
|
+
// Folding
|
|
34
|
+
FOLD_WRAPPER: 'codie-fold-wrapper',
|
|
35
|
+
FOLD_HEADER: 'codie-fold-header',
|
|
36
|
+
FOLD_TOGGLE: 'codie-fold-toggle',
|
|
37
|
+
FOLD_LINE: 'codie-fold-line',
|
|
38
|
+
FOLD_CONTENT: 'codie-fold-content',
|
|
39
|
+
FOLD_PREVIEW: 'codie-fold-preview',
|
|
40
|
+
FOLD_OPEN: 'codie-fold-open',
|
|
41
|
+
|
|
42
|
+
// Syntax tokens - HTML
|
|
43
|
+
TAG: 'codie-tag',
|
|
44
|
+
ATTR_NAME: 'codie-attr-name',
|
|
45
|
+
ATTR_VALUE: 'codie-attr-value',
|
|
46
|
+
PUNCTUATION: 'codie-punct',
|
|
47
|
+
COMMENT: 'codie-comment',
|
|
48
|
+
TEXT: 'codie-text',
|
|
49
|
+
|
|
50
|
+
// Syntax tokens - CSS
|
|
51
|
+
CSS_SELECTOR: 'codie-css-selector',
|
|
52
|
+
CSS_PROPERTY: 'codie-css-property',
|
|
53
|
+
CSS_VALUE: 'codie-css-value',
|
|
54
|
+
|
|
55
|
+
// Syntax tokens - JS
|
|
56
|
+
JS_KEYWORD: 'codie-js-keyword',
|
|
57
|
+
JS_IMPORT: 'codie-js-import',
|
|
58
|
+
JS_STRING: 'codie-js-string',
|
|
59
|
+
JS_FUNCTION: 'codie-js-function',
|
|
60
|
+
JS_NUMBER: 'codie-js-number',
|
|
61
|
+
JS_IDENT: 'codie-js-ident',
|
|
62
|
+
|
|
63
|
+
// Syntax tokens - JSON
|
|
64
|
+
JSON_KEY: 'codie-json-key',
|
|
65
|
+
JSON_STRING: 'codie-json-string',
|
|
66
|
+
JSON_NUMBER: 'codie-json-number',
|
|
67
|
+
JSON_BOOLEAN: 'codie-json-boolean',
|
|
68
|
+
JSON_NULL: 'codie-json-null',
|
|
69
|
+
|
|
70
|
+
// Vibe-specific tokens
|
|
71
|
+
VIBE_COLOR: 'codie-vibe',
|
|
72
|
+
VIBE_VARIABLE: 'codie-vibe-var',
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// CSS variable prefix
|
|
76
|
+
export const VAR_PREFIX = '--codie-'
|
|
77
|
+
|
|
78
|
+
// Void HTML elements (self-closing)
|
|
79
|
+
export const VOID_ELEMENTS = new Set([
|
|
80
|
+
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
|
|
81
|
+
'link', 'meta', 'param', 'source', 'track', 'wbr',
|
|
82
|
+
])
|
|
83
|
+
|
|
84
|
+
// Attributes where the string value is meaningful (should keep empty values like attr="")
|
|
85
|
+
// All other attributes are boolean-like (should be stripped to just attr when empty)
|
|
86
|
+
export const VALUE_ATTRS = new Set([
|
|
87
|
+
// Global attributes
|
|
88
|
+
'class',
|
|
89
|
+
'style',
|
|
90
|
+
'id',
|
|
91
|
+
'title',
|
|
92
|
+
'lang',
|
|
93
|
+
'dir',
|
|
94
|
+
'tabindex',
|
|
95
|
+
'accesskey',
|
|
96
|
+
'slot',
|
|
97
|
+
'part',
|
|
98
|
+
'is',
|
|
99
|
+
'nonce',
|
|
100
|
+
'popover',
|
|
101
|
+
'anchor',
|
|
102
|
+
|
|
103
|
+
// Enumerated (take specific string values, not truly boolean)
|
|
104
|
+
'contenteditable',
|
|
105
|
+
'draggable',
|
|
106
|
+
'spellcheck',
|
|
107
|
+
'translate',
|
|
108
|
+
'autocapitalize',
|
|
109
|
+
'inputmode',
|
|
110
|
+
'enterkeyhint',
|
|
111
|
+
'virtualkeyboardpolicy',
|
|
112
|
+
|
|
113
|
+
// URLs and sources
|
|
114
|
+
'href',
|
|
115
|
+
'src',
|
|
116
|
+
'action',
|
|
117
|
+
'cite',
|
|
118
|
+
'data',
|
|
119
|
+
'poster',
|
|
120
|
+
'srcset',
|
|
121
|
+
'imagesrcset',
|
|
122
|
+
'formaction',
|
|
123
|
+
'ping',
|
|
124
|
+
'usemap',
|
|
125
|
+
'manifest',
|
|
126
|
+
'codebase',
|
|
127
|
+
|
|
128
|
+
// Form attributes
|
|
129
|
+
'name',
|
|
130
|
+
'type',
|
|
131
|
+
'value',
|
|
132
|
+
'placeholder',
|
|
133
|
+
'pattern',
|
|
134
|
+
'min',
|
|
135
|
+
'max',
|
|
136
|
+
'step',
|
|
137
|
+
'minlength',
|
|
138
|
+
'maxlength',
|
|
139
|
+
'size',
|
|
140
|
+
'accept',
|
|
141
|
+
'autocomplete',
|
|
142
|
+
'list',
|
|
143
|
+
'form',
|
|
144
|
+
'formmethod',
|
|
145
|
+
'formtarget',
|
|
146
|
+
'formenctype',
|
|
147
|
+
'wrap',
|
|
148
|
+
'method',
|
|
149
|
+
'enctype',
|
|
150
|
+
'for',
|
|
151
|
+
'dirname',
|
|
152
|
+
|
|
153
|
+
// Text/accessibility
|
|
154
|
+
'alt',
|
|
155
|
+
'label',
|
|
156
|
+
'summary',
|
|
157
|
+
'abbr',
|
|
158
|
+
|
|
159
|
+
// Dimensions and layout
|
|
160
|
+
'width',
|
|
161
|
+
'height',
|
|
162
|
+
'cols',
|
|
163
|
+
'rows',
|
|
164
|
+
'span',
|
|
165
|
+
'rowspan',
|
|
166
|
+
'colspan',
|
|
167
|
+
'low',
|
|
168
|
+
'high',
|
|
169
|
+
'optimum',
|
|
170
|
+
|
|
171
|
+
// Link/resource hints
|
|
172
|
+
'target',
|
|
173
|
+
'rel',
|
|
174
|
+
'hreflang',
|
|
175
|
+
'download',
|
|
176
|
+
'as',
|
|
177
|
+
'media',
|
|
178
|
+
'charset',
|
|
179
|
+
'crossorigin',
|
|
180
|
+
'integrity',
|
|
181
|
+
'loading',
|
|
182
|
+
'decoding',
|
|
183
|
+
'fetchpriority',
|
|
184
|
+
'referrerpolicy',
|
|
185
|
+
'blocking',
|
|
186
|
+
'imagesizes',
|
|
187
|
+
'sizes',
|
|
188
|
+
|
|
189
|
+
// Media
|
|
190
|
+
'preload',
|
|
191
|
+
'kind',
|
|
192
|
+
'srclang',
|
|
193
|
+
|
|
194
|
+
// Meta
|
|
195
|
+
'content',
|
|
196
|
+
'http-equiv',
|
|
197
|
+
|
|
198
|
+
// iframe/embed
|
|
199
|
+
'sandbox',
|
|
200
|
+
'allow',
|
|
201
|
+
'srcdoc',
|
|
202
|
+
'credentialless',
|
|
203
|
+
|
|
204
|
+
// Table
|
|
205
|
+
'headers',
|
|
206
|
+
'scope',
|
|
207
|
+
|
|
208
|
+
// Datetime
|
|
209
|
+
'datetime',
|
|
210
|
+
|
|
211
|
+
// Object/embed legacy
|
|
212
|
+
'coords',
|
|
213
|
+
'shape',
|
|
214
|
+
])
|
|
215
|
+
|
|
216
|
+
// JavaScript keywords for syntax highlighting
|
|
217
|
+
export const JS_KEYWORDS = new Set([
|
|
218
|
+
'const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'do',
|
|
219
|
+
'switch', 'case', 'break', 'continue', 'try', 'catch', 'finally', 'throw', 'new',
|
|
220
|
+
'typeof', 'instanceof', 'class', 'extends', 'default', 'async', 'await', 'yield',
|
|
221
|
+
'this', 'super', 'null', 'undefined', 'true', 'false',
|
|
222
|
+
])
|
|
223
|
+
|
|
224
|
+
// JavaScript import/export keywords
|
|
225
|
+
export const JS_IMPORT_KEYWORDS = new Set(['import', 'export', 'from'])
|
|
226
|
+
|
|
227
|
+
// JavaScript bracket characters
|
|
228
|
+
export const JS_BRACKETS = new Set(['{', '}', '(', ')', '[', ']'])
|