266 lines
7.9 KiB
HTML
266 lines
7.9 KiB
HTML
<!doctype html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
<title>IME Test</title>
|
|
<style>
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
background: #f8fafc;
|
|
color: #0f172a;
|
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
}
|
|
|
|
main {
|
|
display: flex;
|
|
min-height: 100dvh;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom));
|
|
}
|
|
|
|
h1 {
|
|
margin: 0;
|
|
font-size: 20px;
|
|
}
|
|
|
|
p {
|
|
margin: 0;
|
|
color: #475569;
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
label {
|
|
display: grid;
|
|
gap: 6px;
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
color: #334155;
|
|
}
|
|
|
|
input,
|
|
textarea,
|
|
[contenteditable] {
|
|
width: 100%;
|
|
border: 1px solid #cbd5e1;
|
|
border-radius: 8px;
|
|
background: #fff;
|
|
color: #0f172a;
|
|
font: inherit;
|
|
font-size: 18px;
|
|
line-height: 1.6;
|
|
outline: none;
|
|
padding: 12px;
|
|
}
|
|
|
|
textarea {
|
|
min-height: 150px;
|
|
resize: vertical;
|
|
}
|
|
|
|
[contenteditable] {
|
|
min-height: 110px;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.buttons {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
}
|
|
|
|
button {
|
|
border: 1px solid #94a3b8;
|
|
border-radius: 999px;
|
|
background: #fff;
|
|
color: #0f172a;
|
|
font: inherit;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
padding: 9px 13px;
|
|
}
|
|
|
|
button.primary {
|
|
border-color: #10b981;
|
|
background: #d1fae5;
|
|
color: #065f46;
|
|
}
|
|
|
|
pre {
|
|
min-height: 180px;
|
|
max-height: 38dvh;
|
|
overflow: auto;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
background: #fff;
|
|
color: #0f172a;
|
|
font-size: 12px;
|
|
line-height: 1.5;
|
|
margin: 0;
|
|
padding: 10px;
|
|
white-space: pre-wrap;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<h1>IME Test</h1>
|
|
<p>这是一个不加载 React、不加载编辑器的原生输入测试页。依次点下面三个输入区,用豆包输入法按键,然后点“复制日志”。</p>
|
|
|
|
<label>
|
|
Input
|
|
<input id="plain-input" autocomplete="on" autocorrect="on" inputmode="text" placeholder="点这里输入" />
|
|
</label>
|
|
|
|
<label>
|
|
Textarea
|
|
<textarea id="plain-textarea" autocomplete="on" autocorrect="on" inputmode="text" placeholder="点这里输入"></textarea>
|
|
</label>
|
|
|
|
<label>
|
|
Contenteditable
|
|
<div id="plain-contenteditable" contenteditable="true" role="textbox" aria-multiline="true"></div>
|
|
</label>
|
|
|
|
<div class="buttons">
|
|
<button class="primary" id="focus-input" type="button">聚焦 Input</button>
|
|
<button class="primary" id="focus-textarea" type="button">聚焦 Textarea</button>
|
|
<button class="primary" id="focus-contenteditable" type="button">聚焦 Contenteditable</button>
|
|
<button id="prompt-input" type="button">Prompt</button>
|
|
<button id="read-clipboard" type="button">读剪贴板</button>
|
|
<button id="copy-log" type="button">复制日志</button>
|
|
<button id="clear-log" type="button">清空日志</button>
|
|
</div>
|
|
|
|
<pre id="log"></pre>
|
|
</main>
|
|
|
|
<script>
|
|
const input = document.getElementById("plain-input");
|
|
const textarea = document.getElementById("plain-textarea");
|
|
const contenteditable = document.getElementById("plain-contenteditable");
|
|
const logNode = document.getElementById("log");
|
|
const events = [];
|
|
|
|
const activeLabel = () => {
|
|
const element = document.activeElement;
|
|
if (!element) return "none";
|
|
const id = element.id ? `#${element.id}` : "";
|
|
return `${element.tagName.toLowerCase()}${id}`;
|
|
};
|
|
|
|
const valueOf = (element) => {
|
|
if (!element) return "";
|
|
return "value" in element ? element.value : element.innerText;
|
|
};
|
|
|
|
const writeLog = (eventName, event) => {
|
|
const target = event?.target;
|
|
const time = new Date().toLocaleTimeString("zh-CN", { hour12: false });
|
|
const inputType = event && "inputType" in event ? ` type=${event.inputType}` : "";
|
|
const data = event && "data" in event && event.data ? ` data=${JSON.stringify(event.data)}` : "";
|
|
const key = event && "key" in event ? ` key=${event.key}` : "";
|
|
const composing = event && "isComposing" in event ? ` composing=${event.isComposing}` : "";
|
|
events.unshift(
|
|
`${time} ${eventName} active=${activeLabel()} target=${target?.id || target?.tagName || "none"} len=${valueOf(target).length}${inputType}${data}${key}${composing}`
|
|
);
|
|
events.length = Math.min(events.length, 80);
|
|
renderLog();
|
|
};
|
|
|
|
const renderLog = () => {
|
|
const header = [
|
|
`url=${location.href}`,
|
|
`userAgent=${navigator.userAgent}`,
|
|
`active=${activeLabel()}`,
|
|
`inputLen=${input.value.length}`,
|
|
`textareaLen=${textarea.value.length}`,
|
|
`contenteditableLen=${contenteditable.innerText.length}`,
|
|
"",
|
|
];
|
|
logNode.textContent = header.concat(events).join("\n");
|
|
};
|
|
|
|
for (const element of [input, textarea, contenteditable]) {
|
|
for (const eventName of [
|
|
"focus",
|
|
"blur",
|
|
"click",
|
|
"keydown",
|
|
"keyup",
|
|
"beforeinput",
|
|
"input",
|
|
"compositionstart",
|
|
"compositionupdate",
|
|
"compositionend",
|
|
"paste",
|
|
]) {
|
|
element.addEventListener(eventName, (event) => writeLog(eventName, event));
|
|
}
|
|
}
|
|
|
|
document.getElementById("focus-input").addEventListener("click", () => {
|
|
input.focus();
|
|
writeLog("button-focus-input");
|
|
});
|
|
|
|
document.getElementById("focus-textarea").addEventListener("click", () => {
|
|
textarea.focus();
|
|
writeLog("button-focus-textarea");
|
|
});
|
|
|
|
document.getElementById("focus-contenteditable").addEventListener("click", () => {
|
|
contenteditable.focus();
|
|
writeLog("button-focus-contenteditable");
|
|
});
|
|
|
|
document.getElementById("prompt-input").addEventListener("click", () => {
|
|
const text = window.prompt("Prompt input");
|
|
writeLog(text ? "prompt-ok" : "prompt-empty");
|
|
if (text) {
|
|
textarea.value += `${textarea.value ? "\\n" : ""}${text}`;
|
|
textarea.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertText", data: text }));
|
|
}
|
|
});
|
|
|
|
document.getElementById("read-clipboard").addEventListener("click", async () => {
|
|
try {
|
|
const text = await navigator.clipboard.readText();
|
|
writeLog(text ? "clipboard-ok" : "clipboard-empty");
|
|
if (text) {
|
|
textarea.value += `${textarea.value ? "\\n" : ""}${text}`;
|
|
textarea.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertFromPaste", data: text }));
|
|
}
|
|
} catch (error) {
|
|
writeLog(`clipboard-error-${error.name || "unknown"}`);
|
|
}
|
|
});
|
|
|
|
document.getElementById("copy-log").addEventListener("click", async () => {
|
|
renderLog();
|
|
try {
|
|
await navigator.clipboard.writeText(logNode.textContent);
|
|
} catch {
|
|
window.prompt("Copy log", logNode.textContent);
|
|
}
|
|
});
|
|
|
|
document.getElementById("clear-log").addEventListener("click", () => {
|
|
events.length = 0;
|
|
input.value = "";
|
|
textarea.value = "";
|
|
contenteditable.innerText = "";
|
|
renderLog();
|
|
});
|
|
|
|
writeLog("page-ready");
|
|
</script>
|
|
</body>
|
|
</html>
|