clay-server 2.8.0 → 2.8.2
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/bin/cli.js +29 -16
- package/lib/public/app.js +10 -13
- package/lib/public/css/base.css +8 -1
- package/lib/public/index.html +2 -3
- package/lib/public/modules/ascii-logo.js +84 -31
- package/lib/public/modules/filebrowser.js +1 -2
- package/lib/public/modules/markdown.js +5 -105
- package/lib/public/modules/project-settings.js +1 -4
- package/lib/public/modules/sidebar.js +2 -5
- package/lib/public/modules/sticky-notes.js +0 -2
- package/lib/public/modules/tools.js +1 -2
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -121,7 +121,7 @@ for (var i = 0; i < args.length; i++) {
|
|
|
121
121
|
console.log(" --list List all registered projects");
|
|
122
122
|
console.log(" --headless Start daemon and exit immediately (implies --yes)");
|
|
123
123
|
console.log(" --dangerously-skip-permissions");
|
|
124
|
-
console.log(" Bypass all permission prompts
|
|
124
|
+
console.log(" Bypass all permission prompts");
|
|
125
125
|
process.exit(0);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
@@ -1243,7 +1243,30 @@ function setup(callback) {
|
|
|
1243
1243
|
port = p;
|
|
1244
1244
|
log(sym.bar);
|
|
1245
1245
|
|
|
1246
|
-
|
|
1246
|
+
function askPin() {
|
|
1247
|
+
promptPin(function (pin) {
|
|
1248
|
+
if (dangerouslySkipPermissions && !pin) {
|
|
1249
|
+
log(sym.bar);
|
|
1250
|
+
log(sym.warn + " " + a.yellow + "WARNING: No PIN + skip permissions = anyone with the URL" + a.reset);
|
|
1251
|
+
log(sym.bar + " " + a.yellow + "can execute any command without approval." + a.reset);
|
|
1252
|
+
log(sym.bar);
|
|
1253
|
+
promptToggle("Continue without PIN?", null, false, function (confirmed) {
|
|
1254
|
+
if (!confirmed) {
|
|
1255
|
+
clearUp(6);
|
|
1256
|
+
log(sym.done + " PIN protection " + a.dim + "·" + a.reset + " " + a.yellow + "Required for skip permissions" + a.reset);
|
|
1257
|
+
log(sym.bar);
|
|
1258
|
+
askPin();
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
afterPin(pin);
|
|
1262
|
+
});
|
|
1263
|
+
} else {
|
|
1264
|
+
afterPin(pin);
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function afterPin(pin) {
|
|
1247
1270
|
if (process.platform === "darwin") {
|
|
1248
1271
|
promptToggle("Keep awake", "Prevent system sleep while relay is running", false, function (keepAwake) {
|
|
1249
1272
|
callback(pin, keepAwake);
|
|
@@ -1251,7 +1274,9 @@ function setup(callback) {
|
|
|
1251
1274
|
} else {
|
|
1252
1275
|
callback(pin, false);
|
|
1253
1276
|
}
|
|
1254
|
-
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
askPin();
|
|
1255
1280
|
});
|
|
1256
1281
|
});
|
|
1257
1282
|
}
|
|
@@ -2398,15 +2423,10 @@ var currentVersion = require("../package.json").version;
|
|
|
2398
2423
|
// No daemon running — first-time setup
|
|
2399
2424
|
if (autoYes) {
|
|
2400
2425
|
var pin = cliPin || null;
|
|
2401
|
-
if (dangerouslySkipPermissions && !pin) {
|
|
2402
|
-
console.error(" " + sym.warn + " " + a.red + "--dangerously-skip-permissions requires --pin <pin>" + a.reset);
|
|
2403
|
-
process.exit(1);
|
|
2404
|
-
return;
|
|
2405
|
-
}
|
|
2406
2426
|
console.log(" " + sym.done + " Auto-accepted disclaimer");
|
|
2407
2427
|
console.log(" " + sym.done + " PIN: " + (pin ? "Enabled" : "Skipped"));
|
|
2408
2428
|
if (dangerouslySkipPermissions) {
|
|
2409
|
-
console.log(" " + sym.warn + " " + a.yellow + "Skip permissions mode enabled" + a.reset);
|
|
2429
|
+
console.log(" " + sym.warn + " " + a.yellow + "Skip permissions mode enabled" + (pin ? "" : " (no PIN)") + a.reset);
|
|
2410
2430
|
}
|
|
2411
2431
|
var autoRc = loadClayrc();
|
|
2412
2432
|
var autoRestorable = (autoRc.recentProjects || []).filter(function (p) {
|
|
@@ -2423,13 +2443,6 @@ var currentVersion = require("../package.json").version;
|
|
|
2423
2443
|
await forkDaemon(pin, false, autoRestorable.length > 0 ? autoRestorable : undefined, addCwd);
|
|
2424
2444
|
} else {
|
|
2425
2445
|
setup(function (pin, keepAwake) {
|
|
2426
|
-
if (dangerouslySkipPermissions && !pin) {
|
|
2427
|
-
log(sym.warn + " " + a.red + "--dangerously-skip-permissions requires a PIN." + a.reset);
|
|
2428
|
-
log(a.dim + " Please set a PIN to use skip permissions mode." + a.reset);
|
|
2429
|
-
process.exit(1);
|
|
2430
|
-
return;
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
2446
|
// Check ~/.clayrc for previous projects to restore
|
|
2434
2447
|
var rc = loadClayrc();
|
|
2435
2448
|
var restorable = (rc.recentProjects || []).filter(function (p) {
|
package/lib/public/app.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { showToast, copyToClipboard, escapeHtml } from './modules/utils.js';
|
|
2
2
|
import { refreshIcons, iconHtml, randomThinkingVerb } from './modules/icons.js';
|
|
3
|
-
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidModal
|
|
3
|
+
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidModal } from './modules/markdown.js';
|
|
4
4
|
import { initSidebar, renderSessionList, handleSearchResults, updatePageTitle, getActiveSearchQuery, buildSearchTimeline, removeSearchTimeline, populateCliSessionList, renderIconStrip, initIconStrip, getEmojiCategories } from './modules/sidebar.js';
|
|
5
5
|
import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton } from './modules/rewind.js';
|
|
6
6
|
import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './modules/notifications.js';
|
|
@@ -181,7 +181,7 @@ import { initProfile } from './modules/profile.js';
|
|
|
181
181
|
}
|
|
182
182
|
// Normal update (no animation)
|
|
183
183
|
greetEl.textContent = getGreeting();
|
|
184
|
-
|
|
184
|
+
|
|
185
185
|
applyWeatherTooltip(greetEl);
|
|
186
186
|
}
|
|
187
187
|
|
|
@@ -213,13 +213,13 @@ import { initProfile } from './modules/profile.js';
|
|
|
213
213
|
if (step < totalSteps) {
|
|
214
214
|
var idx = (startIdx + step) % SLOT_EMOJIS.length;
|
|
215
215
|
greetEl.textContent = prefix + " " + SLOT_EMOJIS[idx];
|
|
216
|
-
|
|
216
|
+
|
|
217
217
|
step++;
|
|
218
218
|
setTimeout(tick, intervals[step - 1]);
|
|
219
219
|
} else {
|
|
220
220
|
// Final: land on actual weather
|
|
221
221
|
greetEl.textContent = prefix + " " + weatherEmoji;
|
|
222
|
-
|
|
222
|
+
|
|
223
223
|
applyWeatherTooltip(greetEl);
|
|
224
224
|
}
|
|
225
225
|
}
|
|
@@ -366,7 +366,7 @@ import { initProfile } from './modules/profile.js';
|
|
|
366
366
|
})(projects[p]);
|
|
367
367
|
}
|
|
368
368
|
// Render emoji icons
|
|
369
|
-
|
|
369
|
+
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
// --- Week strip ---
|
|
@@ -451,7 +451,7 @@ import { initProfile } from './modules/profile.js';
|
|
|
451
451
|
pbGrid.appendChild(card);
|
|
452
452
|
})(pbs[pi]);
|
|
453
453
|
}
|
|
454
|
-
|
|
454
|
+
|
|
455
455
|
}
|
|
456
456
|
}
|
|
457
457
|
|
|
@@ -498,7 +498,7 @@ import { initProfile } from './modules/profile.js';
|
|
|
498
498
|
}
|
|
499
499
|
|
|
500
500
|
// Render twemoji for all emoji in the hub
|
|
501
|
-
|
|
501
|
+
|
|
502
502
|
}
|
|
503
503
|
|
|
504
504
|
function handleHubSchedules(msg) {
|
|
@@ -617,7 +617,7 @@ import { initProfile } from './modules/profile.js';
|
|
|
617
617
|
var pIcon = cachedProjects[pi].icon || null;
|
|
618
618
|
if (pIcon) {
|
|
619
619
|
tbIcon.textContent = pIcon;
|
|
620
|
-
|
|
620
|
+
|
|
621
621
|
tbIcon.classList.add("has-icon");
|
|
622
622
|
try { localStorage.setItem("clay-project-icon-" + (currentSlug || "default"), pIcon); } catch (e) {}
|
|
623
623
|
} else {
|
|
@@ -736,7 +736,7 @@ import { initProfile } from './modules/profile.js';
|
|
|
736
736
|
var _tbi = $("title-bar-project-icon");
|
|
737
737
|
if (_tbi) {
|
|
738
738
|
_tbi.textContent = _cachedProjectIcon;
|
|
739
|
-
|
|
739
|
+
|
|
740
740
|
_tbi.classList.add("has-icon");
|
|
741
741
|
}
|
|
742
742
|
}
|
|
@@ -1848,7 +1848,7 @@ import { initProfile } from './modules/profile.js';
|
|
|
1848
1848
|
bubble.appendChild(textEl);
|
|
1849
1849
|
}
|
|
1850
1850
|
|
|
1851
|
-
|
|
1851
|
+
|
|
1852
1852
|
div.appendChild(bubble);
|
|
1853
1853
|
|
|
1854
1854
|
// Action bar below bubble (icons visible on hover)
|
|
@@ -1975,7 +1975,6 @@ import { initProfile } from './modules/profile.js';
|
|
|
1975
1975
|
if (highlightTimer) clearTimeout(highlightTimer);
|
|
1976
1976
|
highlightTimer = setTimeout(function () {
|
|
1977
1977
|
highlightCodeBlocks(contentEl);
|
|
1978
|
-
parseEmojis(contentEl);
|
|
1979
1978
|
}, 150);
|
|
1980
1979
|
|
|
1981
1980
|
scrollToBottom();
|
|
@@ -1996,7 +1995,6 @@ import { initProfile } from './modules/profile.js';
|
|
|
1996
1995
|
if (contentEl) {
|
|
1997
1996
|
contentEl.innerHTML = renderMarkdown(currentFullText);
|
|
1998
1997
|
highlightCodeBlocks(contentEl);
|
|
1999
|
-
parseEmojis(contentEl);
|
|
2000
1998
|
}
|
|
2001
1999
|
}
|
|
2002
2000
|
}
|
|
@@ -2008,7 +2006,6 @@ import { initProfile } from './modules/profile.js';
|
|
|
2008
2006
|
if (contentEl) {
|
|
2009
2007
|
highlightCodeBlocks(contentEl);
|
|
2010
2008
|
renderMermaidBlocks(contentEl);
|
|
2011
|
-
parseEmojis(contentEl);
|
|
2012
2009
|
}
|
|
2013
2010
|
if (currentFullText) {
|
|
2014
2011
|
addCopyHandler(currentMsgEl, currentFullText);
|
package/lib/public/css/base.css
CHANGED
|
@@ -58,6 +58,13 @@
|
|
|
58
58
|
font-display: swap;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/* --- Twemoji COLR Font (cross-platform consistent emoji via font, no JS parsing) --- */
|
|
62
|
+
@font-face {
|
|
63
|
+
font-family: 'Twemoji';
|
|
64
|
+
src: url('https://cdn.jsdelivr.net/npm/twemoji-colr-font@15.0.3/twemoji.woff2') format('woff2');
|
|
65
|
+
font-display: swap;
|
|
66
|
+
}
|
|
67
|
+
|
|
61
68
|
/* --- Reset & Variables --- */
|
|
62
69
|
* {
|
|
63
70
|
margin: 0;
|
|
@@ -159,7 +166,7 @@ html, body {
|
|
|
159
166
|
height: 100%;
|
|
160
167
|
background: var(--bg);
|
|
161
168
|
color: var(--text);
|
|
162
|
-
font-family: "Pretendard", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
169
|
+
font-family: "Pretendard", "Twemoji", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
163
170
|
font-size: 15px;
|
|
164
171
|
line-height: 1.6;
|
|
165
172
|
overflow: hidden;
|
package/lib/public/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<title>Clay</title>
|
|
13
13
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
14
14
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
15
|
-
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500&family=Nunito:wght@700;800&display=swap" rel="stylesheet">
|
|
15
|
+
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500;700&family=Nunito:wght@700;800&display=swap" rel="stylesheet">
|
|
16
16
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5/css/xterm.min.css">
|
|
17
17
|
<script>
|
|
18
18
|
(function(){try{var k="clay-theme-vars",v=localStorage.getItem(k),r=document.documentElement;if(v){var o=JSON.parse(v),p;for(p in o)r.style.setProperty(p,o[p]);var vt=localStorage.getItem(k.replace("-vars","-variant"));if(vt==="light"){r.classList.add("light-theme");r.classList.remove("dark-theme")}else{r.classList.add("dark-theme");r.classList.remove("light-theme")}var m=document.querySelector('meta[name="theme-color"]');if(m&&o["--bg"])m.setAttribute("content",o["--bg"])}else{var sl=window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches;if(sl){r.classList.add("light-theme");r.classList.remove("dark-theme")}}}catch(e){}})();
|
|
@@ -219,7 +219,7 @@
|
|
|
219
219
|
<div id="app">
|
|
220
220
|
<div id="connect-overlay">
|
|
221
221
|
<canvas id="ascii-logo-canvas"></canvas>
|
|
222
|
-
<p id="connect-overlay-msg">
|
|
222
|
+
<p id="connect-overlay-msg">Reconnecting to server…</p>
|
|
223
223
|
</div>
|
|
224
224
|
<div id="sticky-notes-container" class="hidden"></div>
|
|
225
225
|
<div id="messages"></div>
|
|
@@ -1361,7 +1361,6 @@
|
|
|
1361
1361
|
<script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"></script>
|
|
1362
1362
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5/lib/xterm.min.js"></script>
|
|
1363
1363
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0/lib/addon-fit.min.js"></script>
|
|
1364
|
-
<script src="https://cdn.jsdelivr.net/npm/twemoji@14.0.2/dist/twemoji.min.js"></script>
|
|
1365
1364
|
<script type="module" src="app.js"></script>
|
|
1366
1365
|
</body>
|
|
1367
1366
|
</html>
|
|
@@ -13,7 +13,7 @@ var ASCII_LINES = [
|
|
|
13
13
|
" _______\\/////////__\\///////////////__\\///________\\///________\\///_______",
|
|
14
14
|
];
|
|
15
15
|
|
|
16
|
-
// Tri-accent gradient stops: Green → Indigo → Terracotta
|
|
16
|
+
// Tri-accent gradient stops: Green → Indigo → Terracotta (matches CLI)
|
|
17
17
|
var GRADIENT_STOPS = [
|
|
18
18
|
[9, 229, 163],
|
|
19
19
|
[88, 87, 252],
|
|
@@ -42,6 +42,7 @@ var offsetY = 0;
|
|
|
42
42
|
var centerX = 0;
|
|
43
43
|
var centerY = 0;
|
|
44
44
|
var maxCol = 0;
|
|
45
|
+
var glyphCache = {}; // key: char → offscreen canvas (white glyph)
|
|
45
46
|
var running = false;
|
|
46
47
|
|
|
47
48
|
function getGradientColor(row, totalRows) {
|
|
@@ -61,9 +62,10 @@ function getGradientColor(row, totalRows) {
|
|
|
61
62
|
return [r, g, b];
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
function easeOutBack(t) {
|
|
66
|
+
var s = 1.4;
|
|
67
|
+
var t1 = t - 1;
|
|
68
|
+
return t1 * t1 * ((s + 1) * t1 + s) + 1;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
function easeOutCubic(t) {
|
|
@@ -82,7 +84,7 @@ function buildParticles() {
|
|
|
82
84
|
var line = ASCII_LINES[row];
|
|
83
85
|
for (var col = 0; col < line.length; col++) {
|
|
84
86
|
var ch = line[col];
|
|
85
|
-
if (ch === " "
|
|
87
|
+
if (ch === " ") continue;
|
|
86
88
|
if (col > maxCol) maxCol = col;
|
|
87
89
|
var gc = getGradientColor(row, ASCII_LINES.length);
|
|
88
90
|
particles.push({
|
|
@@ -101,9 +103,9 @@ function buildParticles() {
|
|
|
101
103
|
finalR: gc[0],
|
|
102
104
|
finalG: gc[1],
|
|
103
105
|
finalB: gc[2],
|
|
104
|
-
r:
|
|
105
|
-
g:
|
|
106
|
-
b:
|
|
106
|
+
r: 220,
|
|
107
|
+
g: 220,
|
|
108
|
+
b: 220,
|
|
107
109
|
scatterX: 0,
|
|
108
110
|
scatterY: 0,
|
|
109
111
|
});
|
|
@@ -133,12 +135,12 @@ function computeLayout() {
|
|
|
133
135
|
}
|
|
134
136
|
|
|
135
137
|
// Responsive font size
|
|
136
|
-
fontSize = Math.floor(w / maxLen * 1.
|
|
137
|
-
fontSize = Math.max(
|
|
138
|
+
fontSize = Math.floor(w / maxLen * 1.3);
|
|
139
|
+
fontSize = Math.max(8, Math.min(fontSize, 28));
|
|
138
140
|
|
|
139
|
-
ctx.font = fontSize + "px Menlo, Monaco, Consolas,
|
|
141
|
+
ctx.font = "bold " + fontSize + "px 'Roboto Mono', Menlo, Monaco, Consolas, monospace";
|
|
140
142
|
charWidth = ctx.measureText("M").width;
|
|
141
|
-
lineHeight = fontSize * 1.
|
|
143
|
+
lineHeight = fontSize * 1.2;
|
|
142
144
|
|
|
143
145
|
var totalW = maxLen * charWidth;
|
|
144
146
|
var totalH = ASCII_LINES.length * lineHeight;
|
|
@@ -153,6 +155,30 @@ function computeLayout() {
|
|
|
153
155
|
p.targetX = offsetX + p.col * charWidth;
|
|
154
156
|
p.targetY = offsetY + p.row * lineHeight + lineHeight * 0.8;
|
|
155
157
|
}
|
|
158
|
+
|
|
159
|
+
// Build glyph cache — pre-render each unique char as white on offscreen canvas
|
|
160
|
+
glyphCache = {};
|
|
161
|
+
var dpr2 = window.devicePixelRatio || 1;
|
|
162
|
+
var pad = Math.ceil(fontSize * 0.3);
|
|
163
|
+
var gw = Math.ceil(charWidth * dpr2) + pad * 2;
|
|
164
|
+
var gh = Math.ceil(fontSize * 1.6 * dpr2) + pad * 2;
|
|
165
|
+
var fontStr = "bold " + fontSize + "px 'Roboto Mono', Menlo, Monaco, Consolas, monospace";
|
|
166
|
+
var chars = {};
|
|
167
|
+
for (var i = 0; i < particles.length; i++) chars[particles[i].char] = 1;
|
|
168
|
+
var keys = Object.keys(chars);
|
|
169
|
+
for (var i = 0; i < keys.length; i++) {
|
|
170
|
+
var ch = keys[i];
|
|
171
|
+
var oc = document.createElement("canvas");
|
|
172
|
+
oc.width = gw;
|
|
173
|
+
oc.height = gh;
|
|
174
|
+
var ox = oc.getContext("2d");
|
|
175
|
+
ox.scale(dpr2, dpr2);
|
|
176
|
+
ox.font = fontStr;
|
|
177
|
+
ox.textBaseline = "alphabetic";
|
|
178
|
+
ox.fillStyle = "#fff";
|
|
179
|
+
ox.fillText(ch, pad / dpr2, (gh - pad) / dpr2);
|
|
180
|
+
glyphCache[ch] = oc;
|
|
181
|
+
}
|
|
156
182
|
}
|
|
157
183
|
|
|
158
184
|
function randomScatter(p) {
|
|
@@ -167,9 +193,9 @@ function randomScatter(p) {
|
|
|
167
193
|
p.vx = 0;
|
|
168
194
|
p.vy = 0;
|
|
169
195
|
p.vr = 0;
|
|
170
|
-
p.r =
|
|
171
|
-
p.g =
|
|
172
|
-
p.b =
|
|
196
|
+
p.r = 220;
|
|
197
|
+
p.g = 220;
|
|
198
|
+
p.b = 220;
|
|
173
199
|
}
|
|
174
200
|
|
|
175
201
|
function setPhase(newPhase) {
|
|
@@ -212,7 +238,7 @@ function updateParticles(dt) {
|
|
|
212
238
|
var delay = (p.col / (maxCol || 1)) * 0.6;
|
|
213
239
|
var localT = Math.max(0, phaseTime - delay) / (PHASE_SCATTER_IN - 0.6);
|
|
214
240
|
localT = Math.min(localT, 1);
|
|
215
|
-
var ease =
|
|
241
|
+
var ease = easeOutBack(localT);
|
|
216
242
|
|
|
217
243
|
p.x = lerp(p.scatterX, p.targetX, ease);
|
|
218
244
|
p.y = lerp(p.scatterY, p.targetY, ease);
|
|
@@ -238,9 +264,9 @@ function updateParticles(dt) {
|
|
|
238
264
|
var localT = (sweepProgress - colNorm * 0.6) / 0.4;
|
|
239
265
|
localT = Math.max(0, Math.min(1, localT));
|
|
240
266
|
var ease = easeOutCubic(localT);
|
|
241
|
-
p.r = Math.round(lerp(
|
|
242
|
-
p.g = Math.round(lerp(
|
|
243
|
-
p.b = Math.round(lerp(
|
|
267
|
+
p.r = Math.round(lerp(220, p.finalR, ease));
|
|
268
|
+
p.g = Math.round(lerp(220, p.finalG, ease));
|
|
269
|
+
p.b = Math.round(lerp(220, p.finalB, ease));
|
|
244
270
|
}
|
|
245
271
|
if (phaseTime >= PHASE_COLOR_SWEEP) {
|
|
246
272
|
for (var i = 0; i < particles.length; i++) {
|
|
@@ -252,10 +278,10 @@ function updateParticles(dt) {
|
|
|
252
278
|
setPhase("hold");
|
|
253
279
|
}
|
|
254
280
|
} else if (phase === "hold") {
|
|
255
|
-
var breath = Math.sin(phaseTime * Math.PI * 0.8) * 0.
|
|
281
|
+
var breath = Math.sin(phaseTime * Math.PI * 0.8) * 0.04;
|
|
256
282
|
for (var i = 0; i < particles.length; i++) {
|
|
257
283
|
var p = particles[i];
|
|
258
|
-
p.opacity = 0.
|
|
284
|
+
p.opacity = 0.96 + breath;
|
|
259
285
|
// Subtle micro-jitter
|
|
260
286
|
p.x = p.targetX + Math.sin(phaseTime * 2.3 + i * 0.7) * 0.4;
|
|
261
287
|
p.y = p.targetY + Math.cos(phaseTime * 1.9 + i * 0.5) * 0.3;
|
|
@@ -297,7 +323,7 @@ function updateParticles(dt) {
|
|
|
297
323
|
var delay = ((maxCol - p.col) / (maxCol || 1)) * 0.5;
|
|
298
324
|
var localT = Math.max(0, phaseTime - delay) / (PHASE_REASSEMBLE - 0.5);
|
|
299
325
|
localT = Math.min(localT, 1);
|
|
300
|
-
var ease =
|
|
326
|
+
var ease = easeOutBack(localT);
|
|
301
327
|
|
|
302
328
|
p.x = lerp(p.scatterX, p.targetX, ease);
|
|
303
329
|
p.y = lerp(p.scatterY, p.targetY, ease);
|
|
@@ -319,29 +345,56 @@ function updateParticles(dt) {
|
|
|
319
345
|
|
|
320
346
|
function drawFrame() {
|
|
321
347
|
if (!ctx) return;
|
|
322
|
-
var
|
|
323
|
-
var
|
|
348
|
+
var dpr = window.devicePixelRatio || 1;
|
|
349
|
+
var w = canvas.width / dpr;
|
|
350
|
+
var h = canvas.height / dpr;
|
|
324
351
|
ctx.clearRect(0, 0, w, h);
|
|
325
|
-
|
|
326
|
-
|
|
352
|
+
|
|
353
|
+
var pad = Math.ceil(fontSize * 0.3);
|
|
354
|
+
var drawW = (glyphCache._w || (Math.ceil(charWidth * dpr) + pad * 2)) / dpr;
|
|
355
|
+
var drawH = (glyphCache._h || (Math.ceil(fontSize * 1.6 * dpr) + pad * 2)) / dpr;
|
|
356
|
+
var padCSS = pad / dpr;
|
|
357
|
+
|
|
358
|
+
// Use globalCompositeOperation to tint white glyphs
|
|
359
|
+
// 1) Draw glyphs as white (source-over)
|
|
360
|
+
// 2) Multiply with color overlay
|
|
361
|
+
// Instead, use simpler approach: drawImage with globalAlpha, then tint via composite
|
|
327
362
|
|
|
328
363
|
for (var i = 0; i < particles.length; i++) {
|
|
329
364
|
var p = particles[i];
|
|
330
365
|
if (p.opacity <= 0.01) continue;
|
|
331
366
|
|
|
332
|
-
|
|
367
|
+
var gc = glyphCache[p.char];
|
|
368
|
+
if (!gc) continue;
|
|
369
|
+
|
|
370
|
+
var dx = p.x - padCSS;
|
|
371
|
+
var dy = p.y - drawH + padCSS;
|
|
372
|
+
|
|
333
373
|
ctx.globalAlpha = p.opacity;
|
|
334
|
-
ctx.fillStyle = "rgb(" + p.r + "," + p.g + "," + p.b + ")";
|
|
335
374
|
|
|
336
375
|
if (Math.abs(p.rotation) > 0.01) {
|
|
376
|
+
ctx.save();
|
|
337
377
|
ctx.translate(p.x + charWidth / 2, p.y - fontSize / 3);
|
|
338
378
|
ctx.rotate(p.rotation);
|
|
339
|
-
ctx.
|
|
379
|
+
ctx.drawImage(gc, -drawW / 2, -drawH / 2, drawW, drawH);
|
|
380
|
+
ctx.restore();
|
|
340
381
|
} else {
|
|
341
|
-
ctx.
|
|
382
|
+
ctx.drawImage(gc, dx, dy, drawW, drawH);
|
|
342
383
|
}
|
|
343
|
-
ctx.restore();
|
|
344
384
|
}
|
|
385
|
+
|
|
386
|
+
// Tint pass — multiply white glyphs with particle colors
|
|
387
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
388
|
+
for (var i = 0; i < particles.length; i++) {
|
|
389
|
+
var p = particles[i];
|
|
390
|
+
if (p.opacity <= 0.01) continue;
|
|
391
|
+
ctx.globalAlpha = 1;
|
|
392
|
+
ctx.fillStyle = "rgb(" + p.r + "," + p.g + "," + p.b + ")";
|
|
393
|
+
var dx = p.x - padCSS;
|
|
394
|
+
var dy = p.y - drawH + padCSS;
|
|
395
|
+
ctx.fillRect(dx, dy, drawW, drawH);
|
|
396
|
+
}
|
|
397
|
+
ctx.globalCompositeOperation = "source-over";
|
|
345
398
|
}
|
|
346
399
|
|
|
347
400
|
function tick(now) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
2
2
|
import { escapeHtml, copyToClipboard } from './utils.js';
|
|
3
|
-
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, exportMarkdownAsPdf
|
|
3
|
+
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, exportMarkdownAsPdf } from './markdown.js';
|
|
4
4
|
import { closeSidebar } from './sidebar.js';
|
|
5
5
|
import { renderUnifiedDiff, renderSplitDiff } from './diff.js';
|
|
6
6
|
import { initFileIcons, getFileIconSvg, getFolderIconSvg } from './fileicons.js';
|
|
@@ -198,7 +198,6 @@ function renderBody() {
|
|
|
198
198
|
}
|
|
199
199
|
highlightCodeBlocks(bodyEl);
|
|
200
200
|
renderMermaidBlocks(bodyEl);
|
|
201
|
-
parseEmojis(bodyEl);
|
|
202
201
|
renderBtn.classList.add("active");
|
|
203
202
|
renderBtn.title = "Show raw";
|
|
204
203
|
pdfBtn.classList.remove("hidden");
|
|
@@ -27,112 +27,12 @@ export function renderMarkdown(text) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* parseEmojis — no-op stub.
|
|
31
|
+
* Emoji rendering is now handled by the Twemoji COLR font via CSS @font-face.
|
|
32
|
+
* No DOM manipulation needed — the font renders emoji glyphs directly.
|
|
33
|
+
* This stub is kept so existing callers don't break.
|
|
33
34
|
*/
|
|
34
|
-
export function parseEmojis(
|
|
35
|
-
if (typeof twemoji === "undefined" || !el) return;
|
|
36
|
-
twemoji.parse(el, { folder: "svg", ext: ".svg" });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Global Twemoji auto-parser.
|
|
41
|
-
* Observes all DOM mutations and automatically converts Unicode emojis to
|
|
42
|
-
* Twemoji SVGs. This eliminates the need to call parseEmojis() manually
|
|
43
|
-
* after every DOM update.
|
|
44
|
-
*/
|
|
45
|
-
var twemojiOpts = { folder: "svg", ext: ".svg" };
|
|
46
|
-
var SKIP_TAGS = { PRE: 1, CODE: 1, SCRIPT: 1, STYLE: 1, TEXTAREA: 1, INPUT: 1 };
|
|
47
|
-
var emojiRegex = /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{27BF}\u{2700}-\u{27BF}\u{FE00}-\u{FE0F}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{200D}\u{20E3}\u{E0020}-\u{E007F}]/u;
|
|
48
|
-
var pendingNodes = [];
|
|
49
|
-
var flushScheduled = false;
|
|
50
|
-
|
|
51
|
-
function shouldSkip(node) {
|
|
52
|
-
var el = node.nodeType === 3 ? node.parentElement : node;
|
|
53
|
-
while (el) {
|
|
54
|
-
if (SKIP_TAGS[el.tagName]) return true;
|
|
55
|
-
// Already parsed — img.emoji is what twemoji produces
|
|
56
|
-
if (el.tagName === "IMG" && el.classList && el.classList.contains("emoji")) return true;
|
|
57
|
-
el = el.parentElement;
|
|
58
|
-
}
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function flushTwemoji() {
|
|
63
|
-
flushScheduled = false;
|
|
64
|
-
if (typeof twemoji === "undefined") return;
|
|
65
|
-
var nodes = pendingNodes;
|
|
66
|
-
pendingNodes = [];
|
|
67
|
-
for (var i = 0; i < nodes.length; i++) {
|
|
68
|
-
var node = nodes[i];
|
|
69
|
-
if (!node.isConnected) continue;
|
|
70
|
-
if (shouldSkip(node)) continue;
|
|
71
|
-
// For text nodes, parse the parent element
|
|
72
|
-
var target = node.nodeType === 3 ? node.parentElement : node;
|
|
73
|
-
if (target) twemoji.parse(target, twemojiOpts);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function queueTwemoji(node) {
|
|
78
|
-
pendingNodes.push(node);
|
|
79
|
-
if (!flushScheduled) {
|
|
80
|
-
flushScheduled = true;
|
|
81
|
-
requestAnimationFrame(flushTwemoji);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Start observing once DOM is ready
|
|
86
|
-
(function initTwemojiObserver() {
|
|
87
|
-
if (typeof twemoji === "undefined") return;
|
|
88
|
-
if (typeof MutationObserver === "undefined") return;
|
|
89
|
-
|
|
90
|
-
var observer = new MutationObserver(function (mutations) {
|
|
91
|
-
for (var m = 0; m < mutations.length; m++) {
|
|
92
|
-
var mut = mutations[m];
|
|
93
|
-
// New nodes added
|
|
94
|
-
if (mut.type === "childList") {
|
|
95
|
-
for (var n = 0; n < mut.addedNodes.length; n++) {
|
|
96
|
-
var added = mut.addedNodes[n];
|
|
97
|
-
if (added.nodeType === 1) {
|
|
98
|
-
// Element — check if it contains emoji text
|
|
99
|
-
if (emojiRegex.test(added.textContent || "")) {
|
|
100
|
-
queueTwemoji(added);
|
|
101
|
-
}
|
|
102
|
-
} else if (added.nodeType === 3) {
|
|
103
|
-
// Text node
|
|
104
|
-
if (emojiRegex.test(added.data || "")) {
|
|
105
|
-
queueTwemoji(added);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
// Text content changed
|
|
111
|
-
if (mut.type === "characterData") {
|
|
112
|
-
if (emojiRegex.test(mut.target.data || "")) {
|
|
113
|
-
queueTwemoji(mut.target);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
function start() {
|
|
120
|
-
// Parse everything already in the page
|
|
121
|
-
twemoji.parse(document.body, twemojiOpts);
|
|
122
|
-
// Watch for future changes
|
|
123
|
-
observer.observe(document.body, {
|
|
124
|
-
childList: true,
|
|
125
|
-
subtree: true,
|
|
126
|
-
characterData: true,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (document.readyState === "loading") {
|
|
131
|
-
document.addEventListener("DOMContentLoaded", start);
|
|
132
|
-
} else {
|
|
133
|
-
start();
|
|
134
|
-
}
|
|
135
|
-
})();
|
|
35
|
+
export function parseEmojis() {}
|
|
136
36
|
|
|
137
37
|
var langAliases = { jsonl: "json", dotenv: "bash" };
|
|
138
38
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// project-settings.js — Project settings panel (profile, defaults, instructions, env)
|
|
2
2
|
import { refreshIcons } from './icons.js';
|
|
3
3
|
import { showToast } from './utils.js';
|
|
4
|
-
import { parseEmojis } from './markdown.js';
|
|
5
4
|
|
|
6
5
|
var ctx = null;
|
|
7
6
|
var panelEl = null;
|
|
@@ -253,7 +252,6 @@ function updateIconPreview(icon) {
|
|
|
253
252
|
var removeBtn = document.getElementById("ps-icon-remove-btn");
|
|
254
253
|
if (preview) {
|
|
255
254
|
preview.textContent = icon || "";
|
|
256
|
-
if (icon) parseEmojis(preview);
|
|
257
255
|
}
|
|
258
256
|
if (removeBtn) {
|
|
259
257
|
removeBtn.classList.toggle("hidden", !icon);
|
|
@@ -338,7 +336,7 @@ function showPsEmojiPicker() {
|
|
|
338
336
|
grid.appendChild(btn);
|
|
339
337
|
})(emojis[i]);
|
|
340
338
|
}
|
|
341
|
-
|
|
339
|
+
|
|
342
340
|
scrollArea.scrollTop = 0;
|
|
343
341
|
}
|
|
344
342
|
|
|
@@ -350,7 +348,6 @@ function showPsEmojiPicker() {
|
|
|
350
348
|
}
|
|
351
349
|
|
|
352
350
|
buildGrid(EMOJI_CATEGORIES[0].emojis);
|
|
353
|
-
parseEmojis(tabBar);
|
|
354
351
|
|
|
355
352
|
anchor.innerHTML = "";
|
|
356
353
|
anchor.appendChild(picker);
|
|
@@ -2,7 +2,6 @@ import { escapeHtml, copyToClipboard } from './utils.js';
|
|
|
2
2
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
3
|
import { openProjectSettings } from './project-settings.js';
|
|
4
4
|
import { triggerShare } from './qrcode.js';
|
|
5
|
-
import { parseEmojis } from './markdown.js';
|
|
6
5
|
|
|
7
6
|
var ctx;
|
|
8
7
|
|
|
@@ -1714,7 +1713,7 @@ function showEmojiPicker(slug, anchorEl) {
|
|
|
1714
1713
|
grid.appendChild(btn);
|
|
1715
1714
|
})(emojis[i]);
|
|
1716
1715
|
}
|
|
1717
|
-
|
|
1716
|
+
|
|
1718
1717
|
scrollArea.scrollTop = 0;
|
|
1719
1718
|
}
|
|
1720
1719
|
|
|
@@ -1728,8 +1727,7 @@ function showEmojiPicker(slug, anchorEl) {
|
|
|
1728
1727
|
// Start with first category (Frequent)
|
|
1729
1728
|
buildGrid(EMOJI_CATEGORIES[0].emojis);
|
|
1730
1729
|
|
|
1731
|
-
|
|
1732
|
-
parseEmojis(tabBar);
|
|
1730
|
+
|
|
1733
1731
|
|
|
1734
1732
|
document.body.appendChild(picker);
|
|
1735
1733
|
emojiPickerEl = picker;
|
|
@@ -1993,7 +1991,6 @@ export function renderIconStrip(projects, currentSlug) {
|
|
|
1993
1991
|
var emojiSpan = document.createElement("span");
|
|
1994
1992
|
emojiSpan.className = "project-emoji";
|
|
1995
1993
|
emojiSpan.textContent = p.icon;
|
|
1996
|
-
parseEmojis(emojiSpan);
|
|
1997
1994
|
el.appendChild(emojiSpan);
|
|
1998
1995
|
} else {
|
|
1999
1996
|
el.appendChild(document.createTextNode(getProjectAbbrev(p.name)));
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
2
|
-
import { parseEmojis } from './markdown.js';
|
|
3
2
|
|
|
4
3
|
var ctx;
|
|
5
4
|
var notes = new Map(); // id -> { data, el }
|
|
@@ -1124,7 +1123,6 @@ function renderArchiveCards() {
|
|
|
1124
1123
|
var bodyLines = (noteData.data.text || "").split("\n").slice(1).join("\n").trim();
|
|
1125
1124
|
if (bodyLines) {
|
|
1126
1125
|
body.innerHTML = renderMiniMarkdown("_\n" + bodyLines).replace('<div class="sn-title">_</div>', "");
|
|
1127
|
-
parseEmojis(body);
|
|
1128
1126
|
}
|
|
1129
1127
|
card.appendChild(body);
|
|
1130
1128
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { escapeHtml, copyToClipboard } from './utils.js';
|
|
2
2
|
import { iconHtml, refreshIcons, randomThinkingVerb } from './icons.js';
|
|
3
|
-
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks
|
|
3
|
+
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
|
|
4
4
|
import { renderUnifiedDiff, renderSplitDiff, renderPatchDiff } from './diff.js';
|
|
5
5
|
import { openFile } from './filebrowser.js';
|
|
6
6
|
|
|
@@ -744,7 +744,6 @@ export function renderPlanCard(content) {
|
|
|
744
744
|
body.innerHTML = renderMarkdown(content);
|
|
745
745
|
highlightCodeBlocks(body);
|
|
746
746
|
renderMermaidBlocks(body);
|
|
747
|
-
parseEmojis(body);
|
|
748
747
|
|
|
749
748
|
var copyBtn = header.querySelector(".plan-card-copy");
|
|
750
749
|
if (copyBtn) {
|