mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-07 16:09:43 +02:00
fix: CJK/Unicode wide character support (#13)
Set UTF-8 locale in all PTY spawn environments, add xterm-addon-unicode11 for proper double-width character measurement, and update LocalEchoOverlay helpers to respect CJK character visual width in positioning, line wrapping, and cursor placement. Based on PR #13 by @TeigenZhang, adapted to current codebase structure. Co-Authored-By: Tenggan Zhang <TeigenZhang@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Tenggan Zhang
Claude Opus 4.6
parent
5170c188c3
commit
3c29b1fa6e
Generated
+13
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "claudeman",
|
"name": "claudeman",
|
||||||
"version": "0.1600",
|
"version": "0.1618",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "claudeman",
|
"name": "claudeman",
|
||||||
"version": "0.1600",
|
"version": "0.1618",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
"xterm": "^5.3.0",
|
"xterm": "^5.3.0",
|
||||||
"xterm-addon-fit": "^0.8.0",
|
"xterm-addon-fit": "^0.8.0",
|
||||||
|
"xterm-addon-unicode11": "^0.6.0",
|
||||||
"xterm-addon-webgl": "^0.16.0",
|
"xterm-addon-webgl": "^0.16.0",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
@@ -4669,6 +4670,16 @@
|
|||||||
"xterm": "^5.0.0"
|
"xterm": "^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xterm-addon-unicode11": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/xterm-addon-unicode11/-/xterm-addon-unicode11-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-5pkb8YoS/deRtNqQRw8t640mu+Ga8B2MG3RXGQu0bwgcfr8XiXIRI880TWM49ICAHhTmnOLPzIIBIjEnCq7k2A==",
|
||||||
|
"deprecated": "This package is now deprecated. Move to @xterm/addon-unicode11 instead.",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"xterm": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xterm-addon-webgl": {
|
"node_modules/xterm-addon-webgl": {
|
||||||
"version": "0.16.0",
|
"version": "0.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0.tgz",
|
||||||
|
|||||||
+2
-1
@@ -10,7 +10,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "node scripts/postinstall.js",
|
"postinstall": "node scripts/postinstall.js",
|
||||||
"build": "tsc && chmod +x dist/index.js && mkdir -p dist/web dist/templates dist/web/public/vendor && cp -r src/web/public dist/web/ && cp src/templates/case-template.md dist/templates/ && cp node_modules/xterm/css/xterm.css dist/web/public/vendor/ && npx esbuild node_modules/xterm/lib/xterm.js --minify --outfile=dist/web/public/vendor/xterm.min.js && npx esbuild node_modules/xterm-addon-fit/lib/xterm-addon-fit.js --minify --outfile=dist/web/public/vendor/xterm-addon-fit.min.js && cp node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js dist/web/public/vendor/xterm-addon-webgl.min.js && npx esbuild dist/web/public/app.js --minify --drop:console --outfile=dist/web/public/app.js --allow-overwrite && npx esbuild dist/web/public/styles.css --minify --outfile=dist/web/public/styles.css --allow-overwrite && npx esbuild dist/web/public/mobile.css --minify --outfile=dist/web/public/mobile.css --allow-overwrite && for f in dist/web/public/*.js dist/web/public/*.css dist/web/public/*.html dist/web/public/vendor/*.js dist/web/public/vendor/*.css; do [ -f \"$f\" ] && gzip -9 -k -f \"$f\" && { brotli -9 -k -f \"$f\" 2>/dev/null || true; }; done",
|
"build": "tsc && chmod +x dist/index.js && mkdir -p dist/web dist/templates dist/web/public/vendor && cp -r src/web/public dist/web/ && cp src/templates/case-template.md dist/templates/ && cp node_modules/xterm/css/xterm.css dist/web/public/vendor/ && npx esbuild node_modules/xterm/lib/xterm.js --minify --outfile=dist/web/public/vendor/xterm.min.js && npx esbuild node_modules/xterm-addon-fit/lib/xterm-addon-fit.js --minify --outfile=dist/web/public/vendor/xterm-addon-fit.min.js && cp node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js dist/web/public/vendor/xterm-addon-webgl.min.js && npx esbuild node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js --minify --outfile=dist/web/public/vendor/xterm-addon-unicode11.min.js && npx esbuild dist/web/public/app.js --minify --drop:console --outfile=dist/web/public/app.js --allow-overwrite && npx esbuild dist/web/public/styles.css --minify --outfile=dist/web/public/styles.css --allow-overwrite && npx esbuild dist/web/public/mobile.css --minify --outfile=dist/web/public/mobile.css --allow-overwrite && for f in dist/web/public/*.js dist/web/public/*.css dist/web/public/*.html dist/web/public/vendor/*.js dist/web/public/vendor/*.css; do [ -f \"$f\" ] && gzip -9 -k -f \"$f\" && { brotli -9 -k -f \"$f\" 2>/dev/null || true; }; done",
|
||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
"dev": "tsx src/index.ts",
|
"dev": "tsx src/index.ts",
|
||||||
"web": "node dist/index.js web",
|
"web": "node dist/index.js web",
|
||||||
@@ -47,6 +47,7 @@
|
|||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
"xterm": "^5.3.0",
|
"xterm": "^5.3.0",
|
||||||
"xterm-addon-fit": "^0.8.0",
|
"xterm-addon-fit": "^0.8.0",
|
||||||
|
"xterm-addon-unicode11": "^0.6.0",
|
||||||
"xterm-addon-webgl": "^0.16.0",
|
"xterm-addon-webgl": "^0.16.0",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
|
|||||||
+8
-2
@@ -949,7 +949,7 @@ export class Session extends EventEmitter {
|
|||||||
cols: 120,
|
cols: 120,
|
||||||
rows: 40,
|
rows: 40,
|
||||||
cwd: this.workingDir,
|
cwd: this.workingDir,
|
||||||
env: { ...process.env, TERM: 'xterm-256color', COLORTERM: undefined, CLAUDECODE: undefined },
|
env: { ...process.env, LANG: 'en_US.UTF-8', LC_ALL: 'en_US.UTF-8', TERM: 'xterm-256color', COLORTERM: undefined, CLAUDECODE: undefined },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set claudeSessionId immediately since we passed --session-id to Claude
|
// Set claudeSessionId immediately since we passed --session-id to Claude
|
||||||
@@ -1016,6 +1016,8 @@ export class Session extends EventEmitter {
|
|||||||
cwd: this.workingDir,
|
cwd: this.workingDir,
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
|
LANG: 'en_US.UTF-8',
|
||||||
|
LC_ALL: 'en_US.UTF-8',
|
||||||
PATH: getAugmentedPath(),
|
PATH: getAugmentedPath(),
|
||||||
TERM: 'xterm-256color',
|
TERM: 'xterm-256color',
|
||||||
COLORTERM: undefined,
|
COLORTERM: undefined,
|
||||||
@@ -1295,7 +1297,7 @@ export class Session extends EventEmitter {
|
|||||||
cols: 120,
|
cols: 120,
|
||||||
rows: 40,
|
rows: 40,
|
||||||
cwd: this.workingDir,
|
cwd: this.workingDir,
|
||||||
env: { ...process.env, TERM: 'xterm-256color', COLORTERM: undefined, CLAUDECODE: undefined },
|
env: { ...process.env, LANG: 'en_US.UTF-8', LC_ALL: 'en_US.UTF-8', TERM: 'xterm-256color', COLORTERM: undefined, CLAUDECODE: undefined },
|
||||||
});
|
});
|
||||||
} catch (spawnErr) {
|
} catch (spawnErr) {
|
||||||
console.error('[Session] Failed to spawn PTY for shell mux attachment:', spawnErr);
|
console.error('[Session] Failed to spawn PTY for shell mux attachment:', spawnErr);
|
||||||
@@ -1330,6 +1332,8 @@ export class Session extends EventEmitter {
|
|||||||
cwd: this.workingDir,
|
cwd: this.workingDir,
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
|
LANG: 'en_US.UTF-8',
|
||||||
|
LC_ALL: 'en_US.UTF-8',
|
||||||
TERM: 'xterm-256color',
|
TERM: 'xterm-256color',
|
||||||
CLAUDEMAN_MUX: '1',
|
CLAUDEMAN_MUX: '1',
|
||||||
CLAUDEMAN_SESSION_ID: this.id,
|
CLAUDEMAN_SESSION_ID: this.id,
|
||||||
@@ -1458,6 +1462,8 @@ export class Session extends EventEmitter {
|
|||||||
cwd: this.workingDir,
|
cwd: this.workingDir,
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
|
LANG: 'en_US.UTF-8',
|
||||||
|
LC_ALL: 'en_US.UTF-8',
|
||||||
PATH: getAugmentedPath(),
|
PATH: getAugmentedPath(),
|
||||||
TERM: 'xterm-256color',
|
TERM: 'xterm-256color',
|
||||||
COLORTERM: undefined,
|
COLORTERM: undefined,
|
||||||
|
|||||||
@@ -265,6 +265,8 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
|||||||
const pathExport = claudeDir ? `export PATH="${claudeDir}:$PATH" && ` : '';
|
const pathExport = claudeDir ? `export PATH="${claudeDir}:$PATH" && ` : '';
|
||||||
|
|
||||||
const envExports = [
|
const envExports = [
|
||||||
|
'export LANG=en_US.UTF-8',
|
||||||
|
'export LC_ALL=en_US.UTF-8',
|
||||||
'unset CLAUDECODE',
|
'unset CLAUDECODE',
|
||||||
'unset COLORTERM',
|
'unset COLORTERM',
|
||||||
'export CLAUDEMAN_MUX=1',
|
'export CLAUDEMAN_MUX=1',
|
||||||
@@ -431,6 +433,8 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
|||||||
const claudeDir = findClaudeDir();
|
const claudeDir = findClaudeDir();
|
||||||
const pathExport = claudeDir ? `export PATH="${claudeDir}:$PATH" && ` : '';
|
const pathExport = claudeDir ? `export PATH="${claudeDir}:$PATH" && ` : '';
|
||||||
const envExports = [
|
const envExports = [
|
||||||
|
'export LANG=en_US.UTF-8',
|
||||||
|
'export LC_ALL=en_US.UTF-8',
|
||||||
'unset CLAUDECODE',
|
'unset CLAUDECODE',
|
||||||
'unset COLORTERM',
|
'unset COLORTERM',
|
||||||
'export CLAUDEMAN_MUX=1',
|
'export CLAUDEMAN_MUX=1',
|
||||||
|
|||||||
+66
-12
@@ -933,8 +933,39 @@ function _zl_readTextAfterPrompt(terminal, prompt, offset) {
|
|||||||
} catch { return ''; }
|
} catch { return ''; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get visual cell width of a character (CJK wide chars = 2, others = 1)
|
||||||
|
function _zl_charCellWidth(ch, terminal) {
|
||||||
|
if (terminal?.unicode?.getStringCellWidth) {
|
||||||
|
return terminal.unicode.getStringCellWidth(ch);
|
||||||
|
}
|
||||||
|
const code = ch.codePointAt(0);
|
||||||
|
if (code >= 0x1100 && (
|
||||||
|
(code <= 0x115F) || // Hangul Jamo
|
||||||
|
(code >= 0x2E80 && code <= 0x303E) || // CJK Radicals, Kangxi, Ideographic
|
||||||
|
(code >= 0x3040 && code <= 0x33BF) || // Hiragana, Katakana, Bopomofo, CJK Compat
|
||||||
|
(code >= 0x3400 && code <= 0x4DBF) || // CJK Unified Ext A
|
||||||
|
(code >= 0x4E00 && code <= 0xA4CF) || // CJK Unified, Yi
|
||||||
|
(code >= 0xA960 && code <= 0xA97C) || // Hangul Jamo Extended-A
|
||||||
|
(code >= 0xAC00 && code <= 0xD7A3) || // Hangul Syllables
|
||||||
|
(code >= 0xF900 && code <= 0xFAFF) || // CJK Compat Ideographs
|
||||||
|
(code >= 0xFE30 && code <= 0xFE6F) || // CJK Compat Forms
|
||||||
|
(code >= 0xFF01 && code <= 0xFF60) || // Fullwidth Forms
|
||||||
|
(code >= 0xFFE0 && code <= 0xFFE6) || // Fullwidth Signs
|
||||||
|
(code >= 0x1F000 && code <= 0x1FBFF) || // Mahjong, Domino, Emoji
|
||||||
|
(code >= 0x20000 && code <= 0x2FFFF) || // CJK Unified Ext B-F
|
||||||
|
(code >= 0x30000 && code <= 0x3FFFF) // CJK Unified Ext G+
|
||||||
|
)) return 2;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _zl_stringCellWidth(str, terminal) {
|
||||||
|
let w = 0;
|
||||||
|
for (const ch of str) w += _zl_charCellWidth(ch, terminal);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
function _zl_renderOverlay(container, params) {
|
function _zl_renderOverlay(container, params) {
|
||||||
const { lines, startCol, totalCols, cellW, cellH, promptRow, font, showCursor, cursorColor } = params;
|
const { lines, startCol, totalCols, cellW, cellH, promptRow, font, showCursor, cursorColor, terminal } = params;
|
||||||
container.style.left = '0px';
|
container.style.left = '0px';
|
||||||
container.style.top = (promptRow * cellH) + 'px';
|
container.style.top = (promptRow * cellH) + 'px';
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
@@ -943,13 +974,13 @@ function _zl_renderOverlay(container, params) {
|
|||||||
const leftPx = i === 0 ? startCol * cellW : 0;
|
const leftPx = i === 0 ? startCol * cellW : 0;
|
||||||
const widthPx = i === 0 ? (fullWidthPx - leftPx) : fullWidthPx;
|
const widthPx = i === 0 ? (fullWidthPx - leftPx) : fullWidthPx;
|
||||||
const topPx = i * cellH;
|
const topPx = i * cellH;
|
||||||
const lineEl = _zl_makeLine(lines[i], leftPx, topPx, widthPx, cellH, cellW, font);
|
const lineEl = _zl_makeLine(lines[i], leftPx, topPx, widthPx, cellH, cellW, font, terminal);
|
||||||
container.appendChild(lineEl);
|
container.appendChild(lineEl);
|
||||||
}
|
}
|
||||||
if (showCursor) {
|
if (showCursor) {
|
||||||
const lastLine = lines[lines.length - 1];
|
const lastLine = lines[lines.length - 1];
|
||||||
const lastLineLeft = lines.length === 1 ? startCol : 0;
|
const lastLineLeft = lines.length === 1 ? startCol : 0;
|
||||||
const cursorCol = lastLineLeft + lastLine.length;
|
const cursorCol = lastLineLeft + _zl_stringCellWidth(lastLine, terminal);
|
||||||
if (cursorCol < totalCols) {
|
if (cursorCol < totalCols) {
|
||||||
const cursor = document.createElement('span');
|
const cursor = document.createElement('span');
|
||||||
cursor.style.cssText = 'position:absolute;display:inline-block';
|
cursor.style.cssText = 'position:absolute;display:inline-block';
|
||||||
@@ -964,7 +995,7 @@ function _zl_renderOverlay(container, params) {
|
|||||||
container.style.display = '';
|
container.style.display = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function _zl_makeLine(text, leftPx, topPx, widthPx, cellH, cellW, font) {
|
function _zl_makeLine(text, leftPx, topPx, widthPx, cellH, cellW, font, terminal) {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
el.style.cssText = 'position:absolute;pointer-events:none';
|
el.style.cssText = 'position:absolute;pointer-events:none';
|
||||||
el.style.backgroundColor = font.backgroundColor;
|
el.style.backgroundColor = font.backgroundColor;
|
||||||
@@ -973,18 +1004,21 @@ function _zl_makeLine(text, leftPx, topPx, widthPx, cellH, cellW, font) {
|
|||||||
el.style.width = widthPx + 'px';
|
el.style.width = widthPx + 'px';
|
||||||
el.style.height = (cellH + 1) + 'px';
|
el.style.height = (cellH + 1) + 'px';
|
||||||
el.style.lineHeight = cellH + 'px';
|
el.style.lineHeight = cellH + 'px';
|
||||||
for (let i = 0; i < text.length; i++) {
|
let colOffset = 0;
|
||||||
|
for (const ch of text) {
|
||||||
|
const charWidth = _zl_charCellWidth(ch, terminal);
|
||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
span.style.cssText = "position:absolute;display:inline-block;text-align:center;pointer-events:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:geometricPrecision;font-feature-settings:'liga' 0,'calt' 0";
|
span.style.cssText = "position:absolute;display:inline-block;text-align:center;pointer-events:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:geometricPrecision;font-feature-settings:'liga' 0,'calt' 0";
|
||||||
span.style.left = (i * cellW) + 'px';
|
span.style.left = (colOffset * cellW) + 'px';
|
||||||
span.style.width = cellW + 'px';
|
span.style.width = (charWidth * cellW) + 'px';
|
||||||
span.style.fontFamily = font.fontFamily;
|
span.style.fontFamily = font.fontFamily;
|
||||||
span.style.fontSize = font.fontSize;
|
span.style.fontSize = font.fontSize;
|
||||||
span.style.fontWeight = font.fontWeight;
|
span.style.fontWeight = font.fontWeight;
|
||||||
span.style.color = font.color;
|
span.style.color = font.color;
|
||||||
if (font.letterSpacing) span.style.letterSpacing = font.letterSpacing;
|
if (font.letterSpacing) span.style.letterSpacing = font.letterSpacing;
|
||||||
span.textContent = text[i];
|
span.textContent = ch;
|
||||||
el.appendChild(span);
|
el.appendChild(span);
|
||||||
|
colOffset += charWidth;
|
||||||
}
|
}
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
@@ -1192,11 +1226,15 @@ class ZerolagInputAddon {
|
|||||||
if (renderKey === this._lastRenderKey && this._overlay.style.display !== 'none') return;
|
if (renderKey === this._lastRenderKey && this._overlay.style.display !== 'none') return;
|
||||||
this._lastRenderKey = renderKey;
|
this._lastRenderKey = renderKey;
|
||||||
const firstLineCols = Math.max(1, totalCols - startCol);
|
const firstLineCols = Math.max(1, totalCols - startCol);
|
||||||
const lines = []; let remaining = displayText;
|
const lines = []; const chars = [...displayText]; let ci = 0;
|
||||||
lines.push(remaining.slice(0, firstLineCols)); remaining = remaining.slice(firstLineCols);
|
{ let lineStr = '', lineCols = 0;
|
||||||
while (remaining.length > 0) { lines.push(remaining.slice(0, totalCols)); remaining = remaining.slice(totalCols); }
|
while (ci < chars.length) { const cw = _zl_charCellWidth(chars[ci], this._terminal); if (lineCols + cw > firstLineCols) break; lineStr += chars[ci]; lineCols += cw; ci++; }
|
||||||
|
lines.push(lineStr); }
|
||||||
|
while (ci < chars.length) { let lineStr = '', lineCols = 0;
|
||||||
|
while (ci < chars.length) { const cw = _zl_charCellWidth(chars[ci], this._terminal); if (lineCols + cw > totalCols) break; lineStr += chars[ci]; lineCols += cw; ci++; }
|
||||||
|
lines.push(lineStr); }
|
||||||
const cursorColor = this._options.cursorColor ?? this._terminal.options.theme?.cursor ?? _ZL_DEFAULT_CURSOR;
|
const cursorColor = this._options.cursorColor ?? this._terminal.options.theme?.cursor ?? _ZL_DEFAULT_CURSOR;
|
||||||
_zl_renderOverlay(this._overlay, { lines, startCol, totalCols, cellW, cellH, promptRow: activePrompt.row, font: this._font, showCursor: this._options.showCursor, cursorColor });
|
_zl_renderOverlay(this._overlay, { lines, startCol, totalCols, cellW, cellH, promptRow: activePrompt.row, font: this._font, showCursor: this._options.showCursor, cursorColor, terminal: this._terminal });
|
||||||
} catch {
|
} catch {
|
||||||
if (this._overlay) { this._overlay.innerHTML = ''; this._overlay.style.display = 'none'; }
|
if (this._overlay) { this._overlay.innerHTML = ''; this._overlay.style.display = 'none'; }
|
||||||
}
|
}
|
||||||
@@ -2010,6 +2048,14 @@ class ClaudemanApp {
|
|||||||
this.fitAddon = new FitAddon.FitAddon();
|
this.fitAddon = new FitAddon.FitAddon();
|
||||||
this.terminal.loadAddon(this.fitAddon);
|
this.terminal.loadAddon(this.fitAddon);
|
||||||
|
|
||||||
|
if (typeof Unicode11Addon !== 'undefined') {
|
||||||
|
try {
|
||||||
|
const unicode11Addon = new Unicode11Addon.Unicode11Addon();
|
||||||
|
this.terminal.loadAddon(unicode11Addon);
|
||||||
|
this.terminal.unicode.activeVersion = '11';
|
||||||
|
} catch (_e) { /* Unicode11 addon failed — default Unicode handling used */ }
|
||||||
|
}
|
||||||
|
|
||||||
const container = document.getElementById('terminalContainer');
|
const container = document.getElementById('terminalContainer');
|
||||||
this.terminal.open(container);
|
this.terminal.open(container);
|
||||||
|
|
||||||
@@ -14365,6 +14411,14 @@ class ClaudemanApp {
|
|||||||
const fitAddon = new FitAddon.FitAddon();
|
const fitAddon = new FitAddon.FitAddon();
|
||||||
terminal.loadAddon(fitAddon);
|
terminal.loadAddon(fitAddon);
|
||||||
|
|
||||||
|
if (typeof Unicode11Addon !== 'undefined') {
|
||||||
|
try {
|
||||||
|
const unicode11Addon = new Unicode11Addon.Unicode11Addon();
|
||||||
|
terminal.loadAddon(unicode11Addon);
|
||||||
|
terminal.unicode.activeVersion = '11';
|
||||||
|
} catch (_e) { /* Unicode11 addon failed */ }
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
terminal.open(body);
|
terminal.open(body);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
<script defer src="vendor/xterm.min.js"></script>
|
<script defer src="vendor/xterm.min.js"></script>
|
||||||
<script defer src="vendor/xterm-addon-fit.min.js"></script>
|
<script defer src="vendor/xterm-addon-fit.min.js"></script>
|
||||||
<script defer src="vendor/xterm-addon-webgl.min.js"></script>
|
<script defer src="vendor/xterm-addon-webgl.min.js"></script>
|
||||||
|
<script defer src="vendor/xterm-addon-unicode11.min.js"></script>
|
||||||
<!-- Synchronous mobile detection — runs before first paint to prevent panel flash -->
|
<!-- Synchronous mobile detection — runs before first paint to prevent panel flash -->
|
||||||
<script>if(window.innerWidth<768||(('ontouchstart' in window||navigator.maxTouchPoints>0)&&window.innerWidth<1024))document.documentElement.classList.add('mobile-init');</script>
|
<script>if(window.innerWidth<768||(('ontouchstart' in window||navigator.maxTouchPoints>0)&&window.innerWidth<1024))document.documentElement.classList.add('mobile-init');</script>
|
||||||
<!-- Inline critical CSS for instant skeleton paint (before styles.css loads) -->
|
<!-- Inline critical CSS for instant skeleton paint (before styles.css loads) -->
|
||||||
|
|||||||
Reference in New Issue
Block a user