enigmatic 0.22.0 → 0.23.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/components.js CHANGED
@@ -1,8 +1,169 @@
1
1
  window.components = {
2
+ "hello-world": (data) => `Hello ${data?.name || 'World'}!`,
2
3
  "markdown-block": {
3
4
  async init() {
4
5
  await loadJS('https://cdn.jsdelivr.net/npm/marked/marked.min.js')
5
6
  this.innerHTML = marked.parse(this.innerText)
6
7
  }
8
+ },
9
+ "auto-complete": {
10
+ init() {
11
+ // Prevent re-initialization
12
+ if (this._initialized) return
13
+ this._initialized = true
14
+
15
+ // Ensure element is visible
16
+ this.style.display = 'block'
17
+ this.style.width = '100%'
18
+ this.style.minWidth = '200px'
19
+ this.style.margin = '10px 0'
20
+ this.style.padding = '0'
21
+ this.style.backgroundColor = 'transparent'
22
+
23
+ this.input = document.createElement('input')
24
+ this.input.type = 'text'
25
+ this.input.placeholder = this.getAttribute('placeholder') || 'Type to search...'
26
+ this.input.style.cssText = 'width:100%;padding:8px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;font-size:14px;'
27
+
28
+ this.dropdown = document.createElement('div')
29
+ this.dropdown.style.cssText = 'position:absolute;top:100%;left:0;right:0;background:white;border:1px solid #ccc;border-top:none;max-height:200px;overflow-y:auto;z-index:1000;display:none;box-shadow:0 2px 4px rgba(0,0,0,0.1);'
30
+
31
+ this.container = document.createElement('div')
32
+ this.container.style.cssText = 'position:relative;width:100%;min-width:200px;'
33
+ this.container.appendChild(this.input)
34
+ this.container.appendChild(this.dropdown)
35
+
36
+ // Clear existing content
37
+ this.innerHTML = ''
38
+ this.appendChild(this.container)
39
+
40
+ this.items = []
41
+ this.filtered = []
42
+ this.selectedIndex = -1
43
+
44
+ this.input.addEventListener('input', () => this.filter())
45
+ this.input.addEventListener('keydown', (e) => this.handleKey(e))
46
+ this.input.addEventListener('focus', () => {
47
+ if (this.filtered.length > 0) this.dropdown.style.display = 'block'
48
+ })
49
+
50
+ // Use a unique handler per instance
51
+ this._clickHandler = (e) => {
52
+ if (!this.contains(e.target)) {
53
+ this.dropdown.style.display = 'none'
54
+ }
55
+ }
56
+ document.addEventListener('click', this._clickHandler)
57
+ },
58
+ set(data) {
59
+ this.items = Array.isArray(data) ? data : (data?.items || [])
60
+ this.filter()
61
+ },
62
+ filter() {
63
+ const query = this.input.value.toLowerCase()
64
+ this.filtered = this.items.filter(item => {
65
+ const text = typeof item === 'string' ? item : (item.label || item.name || String(item))
66
+ return text.toLowerCase().includes(query)
67
+ })
68
+ this.render()
69
+ },
70
+ render() {
71
+ if (this.filtered.length === 0) {
72
+ this.dropdown.style.display = 'none'
73
+ return
74
+ }
75
+
76
+ this.dropdown.innerHTML = this.filtered.map((item, i) => {
77
+ const text = typeof item === 'string' ? item : (item.label || item.name || String(item))
78
+ const selected = i === this.selectedIndex ? 'background:#f0f0f0;' : ''
79
+ return `<div data-index="${i}" style="padding:8px;cursor:pointer;${selected}">${text}</div>`
80
+ }).join('')
81
+
82
+ this.dropdown.style.display = 'block'
83
+
84
+ this.dropdown.querySelectorAll('div').forEach((div, i) => {
85
+ div.addEventListener('click', () => this.select(i))
86
+ div.addEventListener('mouseenter', () => {
87
+ this.selectedIndex = i
88
+ this.render()
89
+ })
90
+ })
91
+ },
92
+ select(index) {
93
+ const item = this.filtered[index]
94
+ const text = typeof item === 'string' ? item : (item.label || item.name || String(item))
95
+ this.input.value = text
96
+ this.dropdown.style.display = 'none'
97
+ this.selectedIndex = -1
98
+
99
+ const event = new CustomEvent('select', { detail: item })
100
+ this.dispatchEvent(event)
101
+ },
102
+ handleKey(e) {
103
+ if (e.key === 'ArrowDown') {
104
+ e.preventDefault()
105
+ this.selectedIndex = Math.min(this.selectedIndex + 1, this.filtered.length - 1)
106
+ this.render()
107
+ } else if (e.key === 'ArrowUp') {
108
+ e.preventDefault()
109
+ this.selectedIndex = Math.max(this.selectedIndex - 1, -1)
110
+ this.render()
111
+ } else if (e.key === 'Enter' && this.selectedIndex >= 0) {
112
+ e.preventDefault()
113
+ this.select(this.selectedIndex)
114
+ } else if (e.key === 'Escape') {
115
+ this.dropdown.style.display = 'none'
116
+ this.selectedIndex = -1
117
+ }
118
+ }
7
119
  }
8
- }
120
+ }
121
+
122
+ /**
123
+ * Component Definitions
124
+ *
125
+ * Components are registered via window.components object.
126
+ * Each component is a custom HTML element that can be used in your HTML.
127
+ *
128
+ * Structure:
129
+ * window.components = {
130
+ * "component-name": {
131
+ * // Component methods and properties
132
+ * }
133
+ * }
134
+ *
135
+ * Available Options:
136
+ *
137
+ * 1. init(element) - Called when component is first connected to DOM
138
+ * - Receives the element instance as parameter
139
+ * - Use for setup, loading resources, initial rendering
140
+ * - Can be async
141
+ *
142
+ * 2. set(data) - Called when component receives data (via data attribute binding)
143
+ * - Receives data object from state or fetch
144
+ * - Use to update component content based on data
145
+ *
146
+ * 3. click(ev), mouseover(ev), etc. - Event handlers
147
+ * - Automatically bound as event listeners
148
+ * - Any method matching /click|mouseover/ pattern is auto-bound
149
+ * - Receives the event object
150
+ *
151
+ * 4. style - Object with CSS properties
152
+ * - Applied as inline styles when component connects
153
+ * - Use camelCase or kebab-case (with quotes) for CSS properties
154
+ *
155
+ * 5. Any other methods - Available as instance methods
156
+ * - Can be called directly on element: element.myMethod()
157
+ *
158
+ * Usage in HTML:
159
+ * <component-name data="stateKey"></component-name>
160
+ *
161
+ * Example:
162
+ * window.components = {
163
+ * "my-button": {
164
+ * init: (e) => e.innerText = 'Click me',
165
+ * click: (ev) => alert('Clicked!'),
166
+ * style: { color: 'blue', padding: '10px' }
167
+ * }
168
+ * }
169
+ */
package/enigmatic.js CHANGED
@@ -1,28 +1,32 @@
1
1
  const w = {}, d = document
2
- w.enigmatic = { version: '2023-07-29 0.21.2' }
2
+ w.enigmatic = { version: '2026-01-03 0.23.0' }
3
3
 
4
- window.onerror = (err, l, n) => {
5
- document.write(`<h2 style="border:8px solid Tomato;">${[l, 'line: ' + n, err].join('<br>')}</h2>`)
4
+ const showError = (err, source, line, col) => {
5
+ const errorDiv = d.createElement('div')
6
+ errorDiv.style.cssText = 'position:fixed;top:0;left:0;right:0;background:#ff4444;color:white;padding:20px;z-index:10000;font-family:monospace;border-bottom:4px solid #cc0000;'
7
+ errorDiv.innerHTML = `<strong>JavaScript Error:</strong><br>${err}<br><small>${source ? source + ':' : ''}${line ? ' line ' + line : ''}${col ? ':' + col : ''}</small>`
8
+ d.body.insertBefore(errorDiv, d.body.firstChild)
6
9
  }
7
10
 
11
+ window.onerror = (err, source, line, col) => {
12
+ showError(err, source, line, col)
13
+ return false
14
+ }
15
+
16
+ window.addEventListener('unhandledrejection', (e) => {
17
+ showError(e.reason?.message || e.reason || 'Unhandled Promise Rejection', '', '', '')
18
+ })
19
+
8
20
  w.$ = d.querySelector.bind(d)
9
21
  w.$$ = d.querySelectorAll.bind(d)
22
+
10
23
  w.loadJS = (src) => {
11
24
  return new Promise((r, j) => {
12
- if ($(`script[src="${src}"]`)) return r(true)
25
+ if (w.$(`script[src="${src}"]`)) return r(true)
13
26
  const s = d.createElement('script')
14
27
  s.src = src
15
28
  s.addEventListener('load', r)
16
- d.head.appendChild(s)
17
- })
18
- }
19
-
20
- w.loadCSS = (src) => {
21
- return new Promise((r, j) => {
22
- const s = document.createElement('link')
23
- s.rel = 'stylesheet'
24
- s.href = src
25
- s.addEventListener('load', r)
29
+ s.addEventListener('error', j)
26
30
  d.head.appendChild(s)
27
31
  })
28
32
  }
@@ -31,188 +35,168 @@ w.wait = (ms) => new Promise((r) => setTimeout(r, ms))
31
35
 
32
36
  w.ready = async () => {
33
37
  return new Promise((r) => {
34
- if (document.readyState === 'complete') r(true)
35
- document.onreadystatechange = () => {
38
+ if (document.readyState === 'complete') return r(true)
39
+ document.addEventListener('readystatechange', () => {
36
40
  if (document.readyState === 'complete') r()
37
- }
41
+ })
38
42
  })
39
43
  }
40
44
 
41
- w.flattenMap = (obj, text) => {
42
- let template = ''
43
- if (text.match(/\$key|\$val/i)) {
44
- for (let k in obj) {
45
- template += text.replaceAll('{$key}', k).replaceAll('{$val}', obj[k])
46
- }
47
- return template
48
- }
49
- for (let k in obj) {
50
- text = text.replaceAll(`{${k}}`, obj[k])
51
- }
52
- return text
53
- }
54
-
55
- w.flatten = (obj, text) => {
56
- if (!(obj instanceof Array) && typeof Object.values(obj)[0] === 'string') {
57
- return w.flattenMap(obj, text)
58
- }
59
- let htmls = ''
60
- if (obj instanceof Array) obj = { ...obj }
61
- for (let k in obj) {
62
- let html = text.replaceAll('{$key}', k)
63
- for (let j in obj[k]) {
64
- const val = typeof obj[k] === 'object' ? obj[k][j] : obj[k]
65
- html = html.replaceAll('{_key_}', j).replaceAll('{$val}', val)
66
- html = html.replaceAll(`{${j}}`, val)
67
- }
68
- htmls += html
69
- }
70
- return htmls
71
- }
72
-
73
45
  w.e = (name, fn = {}, style = {}) => {
46
+ console.log(`registering component ${name}`)
74
47
  customElements.define(name, class extends HTMLElement {
75
48
  connectedCallback() {
76
49
  Object.assign(this.style, style)
77
- Object.assign(this, fn)
78
- Object.keys(fn).filter(k=>k.match(/click/)).forEach(k=>{
79
- this.addEventListener(k, fn[k], true)
80
- })
81
- if(this.init) this.init(this)
82
- }
83
- })
84
- }
85
-
86
- w.element = (
87
- name,
88
- { onMount = x => x, beforeData = (x) => x, style, template = '', fn = {} }
89
- ) => {
90
- customElements.define(
91
- name,
92
- class extends HTMLElement {
93
- connectedCallback(props) {
94
- onMount(this)
95
- if (style) {
96
- const s = document.createElement('style')
97
- s.innerHTML = `${name} {${style}}`
98
- d.body.appendChild(s)
50
+ if (typeof fn === 'function') {
51
+ this.set = (data) => {
52
+ this.innerHTML = fn(data)
99
53
  }
100
- this.template = template
101
- if (!this.template.match('{')) this.innerHTML = this.template
54
+ } else {
102
55
  Object.assign(this, fn)
103
- }
104
- set(o) {
105
- o = beforeData(o)
106
- this.innerHTML = w.flatten(o, this.template)
107
- return o
56
+ Object.keys(fn).filter(k=>k.match(/click|mouseover/)).forEach(k=>{
57
+ this.addEventListener(k, fn[k], true)
58
+ })
59
+ if(this.init) this.init(this)
108
60
  }
109
61
  }
110
- )
111
- }
112
-
113
- if (window.components) {
114
- for (let name in window.components) w.e(name, window.components[name], window.components[name]?.style)
62
+ })
115
63
  }
116
64
 
117
65
  w.state = new Proxy({}, {
118
66
  set: async (obj, prop, value) => {
119
67
  await w.ready()
120
68
  console.log('state change:', prop, value)
121
- if (this[prop] === value) {
69
+ if (obj[prop] === value) {
122
70
  return true
123
71
  }
124
- for (const e of $$(`[data=${prop}]`)) {
72
+ for (const e of w.$$(`[data="${prop}"]`)) {
125
73
  if (e.set) e.set(value)
126
74
  }
127
75
  obj[prop] = value
128
- return value
76
+ return true
129
77
  },
130
78
  get: (obj, prop, receiver) => {
131
79
  if (prop == '_all') return obj
132
80
  return obj[prop]
133
81
  }
134
- }
135
- )
136
-
137
- w.get = async (url, options = {}, transform, key) => {
138
- console.log(`fetching ${url}`)
139
- let data
140
- if (url.startsWith('{') || url.startsWith('[')) {
141
- data = JSON.parse(url)
142
- } else {
143
- let f = await fetch(url, options)
144
- if (!f.ok) throw Error(`Could not fetch ${url}`)
145
- data = await f.json()
146
- }
147
- if (transform) {
148
- console.log('transforming ' + data)
149
- data = transform(data)
150
- }
82
+ })
83
+
84
+ w.get = async (url, opts = {}, transform, key) => {
85
+ const res = await fetch(url, opts)
86
+ if (!res.ok) throw Error(`Could not fetch ${url}`)
87
+ let data = await res.json()
88
+ if (transform) data = transform(data)
151
89
  if (key) w.state[key] = data
152
90
  return data
153
91
  }
154
92
 
155
93
  w.stream = async (url, key) => {
156
94
  const ev = new EventSource(url)
157
- ev.onmessage = (ev) => {
158
- const data = JSON.parse(ev.data)
159
- if (key) w.state[key] = data
160
- return data
95
+ ev.onmessage = (e) => {
96
+ try {
97
+ const data = JSON.parse(e.data)
98
+ if (key) w.state[key] = data
99
+ return data
100
+ } catch (err) {
101
+ showError(err.message || err, '', '', '')
102
+ }
161
103
  }
104
+ ev.onerror = (e) => {
105
+ showError('EventSource error', '', '', '')
106
+ ev.close()
107
+ }
108
+ return ev
162
109
  }
163
110
 
164
- w.start = async () => {
165
- await w.ready();
166
- [...$$('*')].map(e => {
167
- e.attr = {};
168
- [...e.attributes].map((a) => (e.attr[a.name] = a.value))
169
- if (e.attr.fetch) {
170
- e.fetch = async () => {
171
- let template = e.innerHTML
172
- let ignore = template.match(/<!--IGNORE-->.*>/gms) || ''
173
- if(ignore)
174
- template = template.replace(ignore, '')
175
- const obj = await w.get(e.attr.fetch, {}, null, e.attr.data)
176
- e.innerHTML = w.flatten(obj, template) + ignore
177
- let pos = 0
178
- for(c in e.children) {
179
- const ele = e.children[c]
180
- if(typeof ele === 'object' && 'set' in ele)
181
- e.children[c].set(obj[pos++])
182
- }
183
- return obj
111
+ w.flatten = (obj, text, context = {}) => {
112
+ const hasSpecialVars = /{\$key}|{\$val}|{\$index}/.test(text)
113
+
114
+ if (obj instanceof Array)
115
+ return obj.map((o, i) => w.flatten(o, text, { ...context, $key: i, $index: i, $val: o })).join('')
116
+
117
+ if (hasSpecialVars && obj && typeof obj === 'object' && !Array.isArray(obj))
118
+ return Object.entries(obj).map(([k, v]) => w.flatten(v, text, { ...context, $key: k, $val: v })).join('')
119
+
120
+ const m = text.match(/{([^}]*)}/gm) || []
121
+ for (const txt of m) {
122
+ const key = txt.replaceAll(/{|}/g, '')
123
+ let val = context[key] !== undefined ? context[key] : undefined
124
+ if (val === undefined && obj && typeof obj === 'object') {
125
+ val = obj
126
+ for (let k of key.split('.')) {
127
+ val = val?.[k]
184
128
  }
185
- if (!e.hasAttribute('defer'))
186
- e.fetch()
187
- }
188
- if (e.attr?.stream) {
189
- e.stream = w.stream.bind(null, e.pr.stream, null, window[e.pr.transform], e.id)
129
+ } else if (val === undefined) {
130
+ val = obj
190
131
  }
191
- let dta = e.attr?.data
192
- if (dta) {
193
- console.log(`reactive ${e} ${dta}`)
194
- if (!e.set) {
195
- if (e.innerHTML) {
196
- e.template = e.innerHTML
197
- if (e.innerHTML.match('{') && !e.attr.preserve) {
198
- e.innerHTML = ''
132
+ text = text.replaceAll(txt, val ?? '')
133
+ }
134
+ return text
135
+ }
136
+
137
+ const props = {
138
+ async init() {
139
+ let ignore = this.innerHTML.match(/<!--IGNORE-->.*?<!--ENDIGNORE-->/gms) || []
140
+ if (!ignore.length) {
141
+ this.template = this.innerHTML
142
+ } else {
143
+ this.ignoreblock = ignore
144
+ this.template = this.innerHTML
145
+ ignore.forEach(block => {
146
+ this.template = this.template.replace(block, '')
147
+ })
148
+ }
149
+ this.innerHTML = ''
150
+ if (!this.hasAttribute('defer')) {
151
+ this.fetch()
152
+ }
153
+ },
154
+ set(o) {
155
+ console.log('setting', this, this.template, o)
156
+ this.innerHTML = w.flatten(o, this.template)
157
+ const dt = this.getAttribute('data')
158
+ if(dt)
159
+ w.state[dt] = o
160
+ },
161
+ async fetch() {
162
+ try {
163
+ const u = this.getAttribute('fetch')
164
+ if(!u) return
165
+ if (u.startsWith('[') || u.startsWith('{'))
166
+ return this.set(JSON.parse(u))
167
+ const opts = {}
168
+ const f = await fetch(u, opts)
169
+ console.log(f)
170
+ if (!f.ok) throw Error(`Could not fetch ${u}`)
171
+ let data = await f.json()
172
+ const tf = this.getAttribute('t')
173
+ if (tf) {
174
+ try {
175
+ data = new Function('return ' + tf)()(data)
176
+ } catch (err) {
177
+ showError(`Transform error: ${err.message}`, '', '', '')
199
178
  }
200
179
  }
201
- e.set = (o) => {
202
- e.innerHTML = w.flatten(o, e.template) || o
203
- }
204
- }
205
- if (e.attr.value) {
206
- let o = e.attr.value
207
- try { o = JSON.parse(o) } catch (e) { }
208
- w.state[dta] = o
180
+ this.set(data)
181
+ } catch (err) {
182
+ showError(err.message || err, '', '', '')
209
183
  }
210
184
  }
211
- })
212
185
  }
213
186
 
187
+ for (let name in window.components)
188
+ w.e(name, window.components[name], window.components[name]?.style)
189
+
214
190
  Object.assign(window, w);
215
191
 
216
192
  (async () => {
217
- await w.start()
193
+ try {
194
+ await ready()
195
+ for (const i of w.$$('div')) {
196
+ Object.assign(i, props)
197
+ i?.init()
198
+ }
199
+ } catch (err) {
200
+ showError(err.message || err, '', '', '')
201
+ }
218
202
  })()
package/index.html CHANGED
@@ -1,76 +1,33 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Enigmatic Library</title>
8
- <style>
9
- body {
10
- font-family: Arial, sans-serif;
11
- background-color: #f4f4f4;
12
- margin: 0;
13
- padding: 0;
14
- }
15
-
16
- .header {
17
- background-color: #333;
18
- color: #fff;
19
- text-align: center;
20
- padding: 1rem;
21
- }
22
-
23
- .container {
24
- max-width: 800px;
25
- margin: auto;
26
- padding: 2rem;
27
- background-color: #fff;
28
- border-radius: 5px;
29
- box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
30
- }
31
-
32
- .description {
33
- margin-top: 1rem;
34
- font-size: 1.2rem;
1
+ <script src="components.js"></script>
2
+ <script src="enigmatic.js"></script>
3
+ <link rel="stylesheet" href="enigmatic.css">
4
+
5
+ <body style="--cols:1fr; --rows:1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr">
6
+ <hello-world data="name"></hello-world>
7
+ <my-element></my-element>
8
+
9
+ <div id=one fetch="https://randomuser.me/api/?results=1" t="e=>e.results">{email}</div>
10
+
11
+ <script>
12
+ // Wait for DOM and enigmatic.js to be ready
13
+ document.addEventListener('DOMContentLoaded', async () => {
14
+ // Wait for e to be available (enigmatic.js assigns it)
15
+ while (typeof window.e === 'undefined') {
16
+ await new Promise(r => setTimeout(r, 10))
35
17
  }
36
-
37
- .install {
38
- margin-top: 2rem;
39
- text-align: center;
40
- }
41
-
42
- .install-code {
43
- background-color: #f0f0f0;
44
- padding: 1rem;
45
- border-radius: 5px;
46
- }
47
-
48
- .footer {
49
- text-align: center;
50
- padding: 1rem;
51
- }
52
- </style>
53
- </head>
54
-
55
- <body>
56
- <div class="header">
57
- <h1>Enigmatic</h1>
58
- <p>Simpler front-end JS</p>
59
- </div>
60
- <div class="container">
61
- <h2>What is Enigmatic?</h2>
62
- <p class="description">
63
- Enigmatic is a powerful JavaScript library designed to solve complex problems with ease.
64
- </p>
65
- <h2>Getting Started</h2>
66
- <div class="install">
67
- <p>To start using Enigmatic in your projects, simply include the following script tag:</p>
68
- <pre class="install-code"><code>&lt;script src="https://unpkg.com/enigmatic"&gt;&lt;/script&gt;</code></pre>
69
- </div>
70
- </div>
71
- <div class="footer">
72
- <p>&copy; 2023 Enigmatic. All rights reserved.</p>
73
- </div>
18
+
19
+ e('my-element', {
20
+ init: (e) => e.innerText = 'ready',
21
+ click: (ev) => ev.target.style.color = 'green',
22
+ mouseover: (ev) => ev.target.style.color = 'yellow',
23
+ }, {
24
+ color: 'red',
25
+ "text-decoration": 'underline'
26
+ })
27
+
28
+ // Set state for components
29
+ await ready()
30
+ state.name = { name: 'World' }
31
+ })
32
+ </script>
74
33
  </body>
75
-
76
- </html>
package/jest.config.js ADDED
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ testEnvironment: 'jsdom',
3
+ testMatch: ['**/__tests__/**/*.test.js'],
4
+ setupFilesAfterEnv: ['<rootDir>/jest.setup.js']
5
+ }
6
+
package/jest.setup.js ADDED
@@ -0,0 +1,5 @@
1
+ // Mock window.components if not defined
2
+ if (typeof window !== 'undefined' && !window.components) {
3
+ window.components = {}
4
+ }
5
+
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "enigmatic",
3
3
  "main": "enigmatic.js",
4
- "version": "0.22.0",
4
+ "version": "0.23.0",
5
5
  "scripts": {
6
- "test": "node test.mjs"
6
+ "test": "jest",
7
+ "test:watch": "jest --watch"
7
8
  },
8
9
  "devDependencies": {
9
- "instax": "^0.1.5",
10
- "puppeteer": "^17.1.3"
10
+ "jest": "^29.7.0",
11
+ "jest-environment-jsdom": "^29.7.0"
11
12
  }
12
13
  }
@@ -1,5 +0,0 @@
1
- <script src='../enigmatic.js'></script>
2
-
3
- <script>
4
- not a keyword
5
- </script>