enigmatic 0.34.0 → 0.36.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.
@@ -0,0 +1,202 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Enigmatic Webapp</title>
7
+
8
+ <script>
9
+ window.api_url = "https://digplan.app"
10
+ </script>
11
+
12
+ <style>
13
+ :root {
14
+ --bg: #f1f5f9;
15
+ --surface: #ffffff;
16
+ --text: #0f172a;
17
+ --text-muted: #64748b;
18
+ --accent: #6366f1;
19
+ --accent-hover: #4f46e5;
20
+ --border: #e2e8f0;
21
+ --input-bg: #f8fafc;
22
+ --radius: 12px;
23
+ --radius-sm: 8px;
24
+ --shadow: 0 1px 3px rgba(0,0,0,.06);
25
+ --shadow-md: 0 4px 12px rgba(0,0,0,.06);
26
+ }
27
+ * { box-sizing: border-box; }
28
+ body {
29
+ font-family: "Inter", system-ui, -apple-system, sans-serif;
30
+ font-size: 15px;
31
+ line-height: 1.6;
32
+ max-width: 600px;
33
+ margin: 0 auto;
34
+ padding: 32px 24px;
35
+ background: var(--bg);
36
+ color: var(--text);
37
+ -webkit-font-smoothing: antialiased;
38
+ }
39
+ h1 {
40
+ font-size: 1.75rem;
41
+ font-weight: 700;
42
+ letter-spacing: -0.02em;
43
+ margin: 0 0 28px;
44
+ color: var(--text);
45
+ }
46
+ section {
47
+ background: var(--surface);
48
+ border-radius: var(--radius);
49
+ padding: 20px;
50
+ margin-bottom: 20px;
51
+ box-shadow: var(--shadow);
52
+ border: 1px solid var(--border);
53
+ }
54
+ section h2 {
55
+ font-size: 0.8125rem;
56
+ font-weight: 600;
57
+ letter-spacing: 0.02em;
58
+ text-transform: uppercase;
59
+ color: var(--text-muted);
60
+ margin: 0 0 14px;
61
+ }
62
+ section p { margin: 0 0 12px; color: var(--text-muted); font-size: 0.9375rem; }
63
+ input {
64
+ font-size: 14px;
65
+ padding: 10px 14px;
66
+ border-radius: var(--radius-sm);
67
+ border: 1px solid var(--border);
68
+ background: var(--input-bg);
69
+ color: var(--text);
70
+ width: 100%;
71
+ max-width: 200px;
72
+ transition: border-color .15s, box-shadow .15s;
73
+ }
74
+ input:focus {
75
+ outline: none;
76
+ border-color: var(--accent);
77
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, .15);
78
+ }
79
+ input::placeholder { color: var(--text-muted); opacity: .8; }
80
+ button {
81
+ font-size: 14px;
82
+ font-weight: 500;
83
+ padding: 10px 16px;
84
+ border-radius: var(--radius-sm);
85
+ border: none;
86
+ cursor: pointer;
87
+ transition: background .15s, transform .05s;
88
+ }
89
+ button:active { transform: scale(0.98); }
90
+ button:not(.secondary) {
91
+ background: var(--accent);
92
+ color: #fff;
93
+ margin-right: 8px;
94
+ }
95
+ button:not(.secondary):hover { background: var(--accent-hover); }
96
+ button.secondary {
97
+ background: var(--input-bg);
98
+ color: var(--text);
99
+ border: 1px solid var(--border);
100
+ }
101
+ button.secondary:hover { background: var(--border); }
102
+ #auth-status { margin-right: 12px; color: var(--text-muted); font-size: 14px; }
103
+ .row { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 10px; }
104
+ .row:last-of-type { margin-bottom: 0; }
105
+ #kv-result {
106
+ margin-top: 12px;
107
+ padding: 12px 14px;
108
+ background: var(--input-bg);
109
+ border-radius: var(--radius-sm);
110
+ font-size: 13px;
111
+ font-family: ui-monospace, monospace;
112
+ white-space: pre-wrap;
113
+ word-break: break-all;
114
+ min-height: 24px;
115
+ border: 1px solid var(--border);
116
+ }
117
+ .error { color: #dc2626; }
118
+ .reactive-split { display: flex; gap: 20px; align-items: flex-start; flex-wrap: wrap; }
119
+ .reactive-demo { flex: 1; min-width: 180px; }
120
+ .reactive-snippet {
121
+ flex: 1;
122
+ min-width: 220px;
123
+ font-size: 12px;
124
+ background: #1e293b;
125
+ color: #e2e8f0;
126
+ border-radius: var(--radius-sm);
127
+ padding: 14px;
128
+ overflow-x: auto;
129
+ border: none;
130
+ }
131
+ .reactive-snippet pre { margin: 0; font-family: ui-monospace, monospace; white-space: pre-wrap; word-break: break-word; line-height: 1.5; }
132
+ </style>
133
+ <link rel="preconnect" href="https://fonts.googleapis.com">
134
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
135
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
136
+ </head>
137
+ <body>
138
+ <h1>Enigmatic Webapp</h1>
139
+
140
+ <section>
141
+ <h2>Auth</h2>
142
+ <div class="row">
143
+ <span id="auth-status">Checking…</span>
144
+ <button onclick="window.login()">Login</button>
145
+ <button class="secondary" onclick="window.logout()">Logout</button>
146
+ </div>
147
+ </section>
148
+
149
+ <section>
150
+ <h2>Reactive state</h2>
151
+ <p>Edit the message; the custom element updates automatically.</p>
152
+ <div class="reactive-split">
153
+ <div class="reactive-demo">
154
+ <div class="row">
155
+ <input type="text" id="msg-input" placeholder="Message" value="World">
156
+ <button onclick="window.state.message = window.$('#msg-input').value">Update</button>
157
+ </div>
158
+ <p style="margin: 12px 0 0; font-size: 14px;"><hello-world data="message"></hello-world></p>
159
+ </div>
160
+ <div class="reactive-snippet">
161
+ <pre>&lt;!-- HTML: data="message" binds to state.message --&gt;
162
+ &lt;hello-world data="message"&gt;&lt;/hello-world&gt;
163
+
164
+ // JS: updating state re-renders the element
165
+ window.state.message = "World";</pre>
166
+ </div>
167
+ </div>
168
+ </section>
169
+
170
+ <section>
171
+ <h2>KV storage</h2>
172
+ <div class="row">
173
+ <input type="text" id="kv-key" placeholder="Key" value="greeting">
174
+ <input type="text" id="kv-value" placeholder="Value" value="Hello from KV">
175
+ </div>
176
+ <div class="row">
177
+ <button onclick="window.get(window.$('#kv-key').value).then(r => { window.$('#kv-result').textContent = JSON.stringify(r); window.$('#kv-result').classList.remove('error'); }).catch(e => { window.$('#kv-result').textContent = e.message; window.$('#kv-result').classList.add('error'); })">Get</button>
178
+ <button onclick="window.set(window.$('#kv-key').value, window.$('#kv-value').value).then(r => { window.$('#kv-result').textContent = JSON.stringify(r); window.$('#kv-result').classList.remove('error'); }).catch(e => { window.$('#kv-result').textContent = e.message; window.$('#kv-result').classList.add('error'); })">Set</button>
179
+ <button onclick="window.delete(window.$('#kv-key').value).then(r => { window.$('#kv-result').textContent = JSON.stringify(r); window.$('#kv-result').classList.remove('error'); }).catch(e => { window.$('#kv-result').textContent = e.message; window.$('#kv-result').classList.add('error'); })">Delete</button>
180
+ </div>
181
+ <div id="kv-result"></div>
182
+ </section>
183
+
184
+ <section>
185
+ <h2>Files</h2>
186
+ <file-widget></file-widget>
187
+ </section>
188
+
189
+ <script src="client.js"></script>
190
+ <script src="custom.js"></script>
191
+
192
+ <script>
193
+ window.state.message = window.$('#msg-input').value;
194
+
195
+ window.me().then(function(u) {
196
+ window.$('#auth-status').textContent = u ? ('Logged in as ' + (u.email || u.sub)) : 'Not logged in';
197
+ }).catch(function() {
198
+ window.$('#auth-status').textContent = 'Not logged in';
199
+ });
200
+ </script>
201
+ </body>
202
+ </html>
@@ -0,0 +1,106 @@
1
+ const ren = async (el, v) => {
2
+ const f = window.custom?.[el.tagName.toLowerCase()];
3
+ if (f) {
4
+ const dataAttr = el.getAttribute('data');
5
+ const val = v !== undefined ? v : (dataAttr ? window.state[dataAttr] : undefined);
6
+ try {
7
+ if (f.render) {
8
+ el.innerHTML = await f.render.call(f, val);
9
+ } else if (typeof f === 'function') {
10
+ el.innerHTML = await f(val);
11
+ }
12
+ } catch (e) {
13
+ console.error(e);
14
+ }
15
+ }
16
+ };
17
+
18
+ window.custom = {};
19
+
20
+ // 2. State proxy
21
+ const sProx = new Proxy({}, {
22
+ set(o, p, v) {
23
+ o[p] = v;
24
+ window.$$(`[data="${p}"]`).forEach(el => ren(el, v));
25
+ return true;
26
+ }
27
+ });
28
+
29
+ // 4. API helpers
30
+ const req = (method, key, body) =>
31
+ fetch(`${window.api_url}/${key ? encodeURIComponent(key) : ''}`, {
32
+ method,
33
+ body: body instanceof Blob || typeof body === 'string' ? body : JSON.stringify(body),
34
+ credentials: 'include',
35
+ });
36
+
37
+ const toJson = (r) => {
38
+ const ct = (r.headers.get('content-type') || '').toLowerCase();
39
+ if (!ct.includes('application/json')) {
40
+ return r.text().then((t) => { throw new Error('Server returned non-JSON (HTML?): ' + (t.slice(0, 60) || r.status)); });
41
+ }
42
+ return r.json();
43
+ };
44
+
45
+ Object.assign(window, {
46
+ $: (s) => document.querySelector(s),
47
+ $$: (s) => document.querySelectorAll(s),
48
+ $c: (s) => $0.closest(s),
49
+ state: sProx,
50
+ get: (k) => req('GET', k).then(toJson),
51
+ set: (k, v) => req('POST', k, v).then(toJson),
52
+ put: (k, v) => req('PUT', k, v).then(toJson),
53
+ delete: (k) => req('DELETE', k).then(toJson),
54
+ purge: (k) => req('PURGE', k).then(toJson),
55
+ list: () => req('PROPFIND').then(toJson),
56
+ me: () => fetch(`${window.api_url}/me`, { credentials: 'include' }).then((r) => (r.ok ? r.json() : null)),
57
+ login: () => window.location.href = `${window.api_url}/login`,
58
+ logout: () => window.location.href = `${window.api_url}/logout`,
59
+ download: async (k) => {
60
+ const r = await req('PATCH', k);
61
+ if (!r.ok) throw new Error('Download failed');
62
+ const a = document.createElement('a');
63
+ a.href = URL.createObjectURL(await r.blob());
64
+ a.download = k;
65
+ a.click();
66
+ URL.revokeObjectURL(a.href);
67
+ },
68
+ initCustomElements: () => {
69
+ if (!document.body) return;
70
+ Object.keys(window.custom || {}).forEach((t) => {
71
+ const elements = window.$$(t);
72
+ if (elements.length > 0) {
73
+ elements.forEach(el => ren(el));
74
+ }
75
+ });
76
+ }
77
+ });
78
+
79
+ // 5. Boot
80
+ const boot = () => {
81
+ if (window.initCustomElements) {
82
+ window.initCustomElements();
83
+ setTimeout(() => window.initCustomElements(), 10);
84
+ }
85
+ if (document.body) {
86
+ new MutationObserver((mutations) => {
87
+ mutations.forEach((m) => {
88
+ m.addedNodes.forEach((node) => {
89
+ if (node.nodeType === 1) {
90
+ const tag = node.tagName?.toLowerCase();
91
+ if (tag && window.custom?.[tag]) ren(node);
92
+ node.querySelectorAll && Array.from(node.querySelectorAll('*')).forEach((child) => {
93
+ const childTag = child.tagName?.toLowerCase();
94
+ if (childTag && window.custom?.[childTag]) ren(child);
95
+ });
96
+ }
97
+ });
98
+ });
99
+ }).observe(document.body, { childList: true, subtree: true });
100
+ }
101
+ };
102
+ if (document.readyState === 'loading') {
103
+ document.addEventListener('DOMContentLoaded', boot);
104
+ } else {
105
+ setTimeout(boot, 0);
106
+ }