就掻ãã©ãŒã èªåå ¥åã®Chromeæ¡åŒµãããŒã«ã«å®çµã§äœã
Claude CodeãšCodexã忥ããã話ããç¶ããå人éçºãéç£ããããã®èªåååºç€ãã·ãªãŒãºã§ããä»åã¯ã就掻ã®ãšã³ããªãŒãã©ãŒã ãã¯ã³ã¯ãªãã¯ã§åãã Chrome æ¡åŒµæ©èœãããµãŒããŒã«äžåããŒã¿ãéããããŒã«ã«å®çµã§äœã£ãè©±ãæžããŸããMV3ã»ãã¥ãŒãªã¹ãã£ãã¯ãªãã£ãŒã«ãæšå®ã»çå¹Žææ¥3åå²å¯Ÿå¿ãªã©ãå®éã«æžããã³ãŒãã«æ²¿ã£ãŠè§£èª¬ããŸãã
ð å®éã«äœ¿ããŸã: 就掻ãã©ãŒã èªåå ¥åïŒChrome ãŠã§ãã¹ãã¢ïŒ
ãªãäœã£ãã
就掻ãå§ããŠããæ°ã¥ãã®ãããšã³ããªãŒãã©ãŒã ã®ãããããããããªå埩ã§ããã©ã®äŒç€Ÿãæ°åã»äœæã»åŠæŽãåãããã«èããŠããŸããLastPass ã Chrome ã®çµã¿èŸŒã¿ãªãŒããã£ã«ã¯ãåããµã€ãã®åããã©ãŒã ãã«ã¯åŒ·ãã§ãããå瀟ãã©ãã©ã®ã·ã¹ãã ïŒãã€ããã»ãªã¯ããç³»ã»å瀟ç¬èªã·ã¹ãã ïŒã«ãŸããã就掻ãã©ãŒã ã«ã¯æ¯ãç«ã¡ãŸããããã£ãŒã«ãã® name 屿§ããã©ãã©ã§ãfield_001 ã®ãããªé£çªã®ãã®ãå°ãªããããŸããã
æ¢è£œã®èªåå ¥åããŒã«ã¯å€éšãµãŒããŒã«ãããã£ãŒã«ãéä¿¡ããã¿ã€ããå€ããæ°åã»äœæã»çå¹Žææ¥ã第äžè ã«æž¡ãããšã«ãªããŸãã就掻æ å ±ã¯ç¹ã«æ éã«æ±ãããã®ã§ãããŒã¿ã¯ãã©ãŠã¶ã®ããŒã«ã«ã¹ãã¬ãŒãžã«ã®ã¿ä¿åããå€ã«äžååºããªãèšèšã«ããããšã«ããŸããã
å šäœæ§æ
Manifest V3 ã§äœã£ãŠããŸãããã¡ã€ã«æ§æã¯ã·ã³ãã«ã§ãã
jobform-autofill/
âââ manifest.json
âââ src/
â âââ util.js # 倿ãŠãŒãã£ãªãã£ã»ãããã£ãŒã«é
ç®å®çŸ©
â âââ matcher.js # ãã£ãŒã«ãæèã®æšå®ããžãã¯
â âââ filler.js # DOM ãžã®å
¥åããªããã£ã
â âââ content.js # ãã©ãŒã èµ°æ» â åé¡ â å
¥åã®çµ±å
âââ popup/ # æ¡åŒµã¢ã€ã³ã³ãã¯ãªãã¯ããŠéããããã£ãŒã«å
¥åç»é¢
âââ test/
âââ matcher.test.js # classifyField ã®åäœãã¹ãïŒjsdom äžèŠïŒ
âââ e2e.test.js # table/div æ··åšãã©ãŒã ã®çµ±åãã¹ã
âââ e2r.test.js # e2r ç³»ãã©ãŒã ã®ååž°ãã¹ãïŒæµ·å€æ³šèšä»ãïŒ
âââ button-gate.test.js # ãã¿ã³è¡šç€ºå€å®ã®ãã¹ã
manifest.json ã®ãã¡éèŠãªéšåã¯ãããªã£ãŠããŸãã
{
"manifest_version": 3,
"name": "就掻ãã©ãŒã èªåå
¥å",
"permissions": ["storage"],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["src/util.js", "src/matcher.js", "src/filler.js", "src/content.js"],
"run_at": "document_idle",
"all_frames": true
}
]
}
"permissions": ["storage"] ã ãã§ããactiveTab ã scripting ãèŠããŸãããã³ã³ãã³ãã¹ã¯ãªãããšããŠå
š URL ã«ã€ã³ãžã§ã¯ããããããcontent_scripts ã® matches ã <all_urls> ã«ããŠããŸããall_frames: true ã¯ããã©ãŒã ã <iframe> å
ã«åã蟌ãŸããŠãããµã€ãïŒäžéšã®æ¡çšã·ã¹ãã ã iframe ã䜿ããŸãïŒã«ã察å¿ããããã§ãã
èšèšã®æ žå¿ïŒãã£ãŒã«ãã®ãã¥ãŒãªã¹ãã£ãã¯æšå®
èªåå
¥åããŒã«ã®èã¯ããã®å
¥åæ¬ãæ°åãªã®ããéµäŸ¿çªå·ãªã®ããã倿ããéšåã§ããçæ³ã¯ autocomplete 屿§ãèŠãã ãã§æžãããšã§ããã就掻ãã©ãŒã ã§ autocomplete ãæ£ããèšå®ãããŠããããšã¯ã»ãŒãããŸããã
ããã§ ããã£ãŒã«ãã®åšèŸºã«ããæååãããéããŠæšå®ããã ã¢ãããŒããåããŸãããåŠç㯠matcher.js ã«éçŽãããŠããŸãã
buildContextïŒåšèŸºã©ãã«ãåéãã
function buildContext(el) {
const parts = [];
const seen = new Set();
const push = (v) => {
if (v == null) return;
const t = stripExample(String(v).replace(/\s+/g, " ").trim()).replace(/\s+/g, " ").trim();
if (t && t.length < 80 && !seen.has(t)) { seen.add(t); parts.push(t); }
};
// 屿§ãã
["name", "id", "placeholder", "aria-label", "autocomplete", "title", "data-label"]
.forEach((a) => push(el.getAttribute(a)));
// aria-labelledby / label[for]
const lb = el.getAttribute("aria-labelledby");
if (lb) lb.split(/\s+/).forEach((id) => {
const e = document.getElementById(id); if (e) push(e.textContent);
});
if (el.id) document.querySelectorAll(`label[for="${CSS.escape(el.id)}"]`)
.forEach((l) => push(l.textContent));
// ç¥å
ãé¡ã£ãŠçŽåã®ã©ãã«åè£ãæŸãïŒæå€§5éå±€ïŒ
let node = el;
for (let depth = 0; depth < 5 && node && node.tagName !== "BODY"; depth++) {
if (node.tagName === "TD" || node.tagName === "TH") {
const row = node.closest("tr");
if (row) {
const h = row.querySelector("th") || row.querySelector("td");
if (h && h !== node) push(h.textContent);
}
}
if (node.tagName === "DD" && node.previousElementSibling?.tagName === "DT") {
push(node.previousElementSibling.textContent);
}
// åã®å
åŒèŠçŽ ããã©ãŒã éšåãå«ãŸãªãã確èªããŠããæŸã
let sib = node.previousElementSibling, hops = 0;
while (sib && hops < 3) {
const isControl = sib.matches?.("input, select, textarea, button");
const hasControl = sib.querySelectorAll?.("input, select, textarea").length > 0;
if (!isControl && !hasControl) {
const t = sib.textContent?.trim();
if (t) { push(t); break; }
}
sib = sib.previousElementSibling; hops++;
}
node = node.parentElement;
}
return normalizeContext(parts.join(" | "));
}
ãã€ã³ãã¯ã<table> ã® <th>ã»<dl> ã® <dt>ã»<div> ã®åå
åŒèŠçŽ ããæšªæçã«æŸããèšèšã§ãã就掻ãã©ãŒã 㯠HTML ã®æžãæ¹ããµã€ãããšã«ãã©ãã©ã§ãtable ã¬ã€ã¢ãŠãã»div ã¬ã€ã¢ãŠãã»dl ã¬ã€ã¢ãŠããæ··åšããŸããããããã¹ãŠã§åãããã«ããã«ã¯ãDOM ã®ç¥å
ãäžå®æ·±ããŸã§é¡ããªããããã©ãŒã éšåãæããªãèŠçŽ ã®ããã¹ãããæãåºãå¿
èŠããããŸããã
ãŸããstripExample ãšããååŠçãå
¥ããŠããŸãããäŸïŒå§ïŒããã·ã¿ãåïŒã¿ããŠïŒãã®ãããªãã¬ãŒã¹ãã«ããŒçãªæ³šèšãé€å»ããããã§ãããããç¡ããšããå§ãã®æ¬ã®äŸç€ºããã¹ãã®äžã«ãåããå«ãŸããããšã§ãåïŒfirst nameïŒããšèª€åé¡ãããåé¡ãèµ·ããŸãã
classifyFieldïŒæèæååãåé¡ãã
function classifyField(ctx) {
const has = (re) => re.test(ctx);
// ã¹ããããã¹ãæ¬ïŒæåªå
ïŒ
if (has(/é æå|ã€ãã·ã£ã«|äžæå|1æå/)) return null;
if (has(/ãã®ä».*詳现|詳现ãå
¥å|系統ãã®ä»|åºåãã®ä»/)) return null;
// ã¡ãŒã«ïŒã¡ãŒã«ã¢ãã¬ã¹2 ãšæ¬äœ/確èªçšãåºå¥ïŒ
if (has(/ã¡ãŒã«|e-?mail|mail/)) {
const e2 = ctx.replace(/[2ïŒ][\sã]*(床|å|ç®|é)/g, " ");
if (/(ã¡ãŒã«ã¢ãã¬ã¹|ã¡ãŒã«)[\sã]*[2ïŒ]|ãµã|äºå|ã»ã«ã³ã|secondary/.test(e2))
return "email2";
return "email";
}
if (has(/éµäŸ¿|ã|zip|postal/)) return "postalCode";
if (has(/æºåž¯|ãããã|mobile|cell/)) return "phoneMobile";
if (has(/èªå®
|åºå®é»è©±|home.?phone/)) return "phoneHome";
if (has(/é»è©±|tel(?!l)|phone/)) return "phone";
// 髿 ¡ïŒå€§åŠããå
ã«å€å®ïŒ
if (has(/髿 ¡|é«çåŠæ ¡|åºèº«é«/)) {
if (has(/忥|ä¿®äº/)) return "highSchoolGradYear";
if (has(/å
¥åŠ/)) return "highSchoolAdmYear";
return "highSchool";
}
if (has(/忥|ä¿®äº|graduat/)) return "graddate";
if (has(/çå¹Žææ¥|èªçæ¥|date.?of.?birth/)) return "birthdate";
// æ°åïŒè€åèªã®ãåãã誀èªããªãããé€å»ããŠããå€å®ïŒ
const stripped = ctx.replace(/æ°å|ãåå|åå|ããªã¬ã|ãµãããª|ã«ãæ°å|fullname|name|kana/g, " ");
const COMPOUND = /(å°å|眲å|èšå|ä»¶å|åå|åç§°|å矩|äŒç€Ÿå|åŠæ ¡å|倧åŠå)/;
const isKana = has(/ããªã¬ã|ãµãããª|ã«ã|ïŸ
|kana|furigana/);
const isLast = /å§|ãã|ã»ã€|èå|åå|last|family/.test(stripped);
const isFirst = /ãã|ã¡ã€|first|given/.test(stripped)
|| (/å/.test(stripped) && !COMPOUND.test(ctx));
const hasFull = has(/æ°å|ãåå|åå|fullname|\bname\b/);
if (isKana) {
if (isLast) return "lastNameKana";
if (isFirst) return "firstNameKana";
return "fullNameKana";
}
if (isLast) return "lastName";
if (isFirst) return "firstName";
if (hasFull) return "fullName";
// æµ·å€å°çšæ¬ïŒæåŸã«å€å®ãã¹ãããïŒ
if (has(/æµ·å€åšäœ|æ¥æ¬åœå€|overseas/)) return null;
return null;
}
ãã®é¢æ°ã¯çŽç²é¢æ°ã§ããDOM åç §ãäžåãªããæååãåãåã£ãŠåé¡ããŒãè¿ãã ããªã®ã§ãNode.js äžã§ jsdom ãªãã«çŽæ¥ãã¹ãã§ããŸããã³ãŒãã®éèŠãªèšèšå€æã®ã²ãšã€ã§ãã
çå¹Žææ¥ã»åæ¥å¹Žæã®3åå²å¯Ÿå¿
就掻ãã©ãŒã ã§ç¹ã«åä»ãªã®ãæ¥ä»ãã£ãŒã«ãã§ãããå¹Žã»æã»æ¥ããå¥ã
ã® <select> ã«åãããŠãããã¿ãŒã³ãå€ããããã <option> ã® value ã 2003 ã ã£ãã '03 ã ã£ãã 2003幎 ã ã£ãããšãµã€ãã«ãã£ãŠç°ãªããŸãã
filler.js ã«ãã detectDateRole ã¯ãselect ã® option ã®å€ã¬ã³ãžãããããã¯å¹Žãæãæ¥ãããæšå®ããŸãã
function detectDateRole(el) {
const nums = Array.from(el.options)
.map((o) => parseInt((o.value || o.textContent).replace(/[^0-9]/g, ""), 10))
.filter((x) => !isNaN(x));
if (!nums.length) return null;
const max = Math.max(...nums);
if (max >= 1900) return "year";
if (max <= 12) return "month";
if (max <= 31) return "day";
return null;
}
æå€§å€ã1900以äžãªãã幎ãã12以äžãªããæãã31以äžãªããæ¥ãã§ããåçŽã§ããã就掻ãã©ãŒã ã§èŠãããã»ãŒãã¹ãŠã®ãã¿ãŒã³ã«å¯Ÿå¿ã§ããŠããŸãã
éžæã¯ selectNumber ãæ
ããŸãã'5'ã»'05'ã»'5æ'ã»'5æ¥' ãšãã£ã衚èšãããåžåããŸãã
function selectNumber(el, num) {
const n = parseInt(num, 10);
if (isNaN(n)) return false;
return selectOption(el, [
String(n), String(n).padStart(2, "0"),
`${n}æ`, `${n}æ¥`, `${n}幎`, `å¹³æ${n}`
]);
}
React / Vue ãã©ãŒã ãžã®å¯Ÿå¿
åçŽã« el.value = "..." ãšæžãã ãã§ã¯ React ã Vue ãã倿Žãæ€ç¥ããªããåé¡ããããŸãããããã®ãã¬ãŒã ã¯ãŒã¯ã¯ä»®æ³ DOM ã§ value ã管çããŠãããããçŽæ¥ DOM ãæžãæããŠã state ãæŽæ°ããããéä¿¡æã«ç©ºæ¬ãšããŠæ±ãããããšããããŸãã
filler.js ã® setNativeValue ã¯ãã®åé¡ãåé¿ããŠããŸãã
function setNativeValue(el, value) {
const proto =
el.tagName === "TEXTAREA" ? HTMLTextAreaElement.prototype :
el.tagName === "SELECT" ? HTMLSelectElement.prototype :
HTMLInputElement.prototype;
const desc = Object.getOwnPropertyDescriptor(proto, "value");
if (desc && desc.set) desc.set.call(el, value);
else el.value = value;
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
}
ãã€ã³ã㯠Object.getOwnPropertyDescriptor ã§ãã€ãã£ãã® setter ãåŒãåºããŠããåŒã¶éšåã§ããReact 㯠HTMLInputElement.prototype ã® value setter ãäžæžããã圢ã§å€æŽæ€ç¥ã仿ããŠãããããã€ã³ã¹ã¿ã³ã¹ã§ã¯ãªããããã¿ã€ããã setter ãåŒãåºããŠåŒã¶ããšã§ãã®æ€ç¥ããããæããããŸããå ã㊠input ãš change ã€ãã³ããçºç«ããããšã§ãVue ã® v-model ã远éããŸãã
é»è©±ã»éµäŸ¿çªå·ã®åå²ããã¯ã¹å¯Ÿå¿
é»è©±çªå·ãã090 | 1717 | 0135ãã®ããã«3ã€ã«åãããŠãããéµäŸ¿çªå·ãã171 | 0031ãã«åãããŠãããã¿ãŒã³ãå€ãã§ãã
fillSplitNumber ããããåŠçããŸããæ¡æ°ããå®çªãã¿ãŒã³ã§åå²ããåããã¯ã¹ã«æµã蟌ã¿ãŸãã
function fillSplitNumber(els, value, kind) {
const digits = String(value || "").replace(/\D/g, "");
if (!digits) return 0;
let pattern;
if (kind === "postal") {
pattern = digits.length === 7 ? [3, 4] : null;
} else if (kind === "phone") {
if (els.length === 3) {
if (digits.length === 11) pattern = [3, 4, 4]; // æºåž¯ 090-XXXX-XXXX
else if (digits.length === 10)
pattern = digits.startsWith("0") ? [2, 4, 4] : [3, 3, 4]; // åºå® 03-XXXX-XXXX
}
}
// maxlength ãä¿¡é Œã§ãããšãã¯ããã䜿ã
if (!pattern) {
const lens = els.map((e) => {
const m = parseInt(e.getAttribute("maxlength"), 10);
return m > 0 && m < 20 ? m : null;
});
pattern = lens.every((l) => l) ? lens : null;
}
const parts = pattern ? sliceByLens(digits, pattern) : [digits];
let n = 0;
els.forEach((el, i) => {
if (parts[i] != null) {
setNativeValue(el, parts[i]);
el.dispatchEvent(new Event("blur", { bubbles: true }));
n++;
}
});
return n ? 1 : 0;
}
ã«ã倿ïŒå šè§ã»ã²ãããªã»åè§ã«ããäžæ¬å¯Ÿå¿
ããªã¬ãæ¬ã¯ãµã€ãã«ãã£ãŠãå šè§ã«ã¿ã«ãã§å ¥åããã²ãããªã§å ¥åããåè§ã«ãã§å ¥åããšèŠæ±ããã©ãã©ã§ãããããã£ãŒã«ã«ã¯å šè§ã«ã¿ã«ãã§ä¿åããŠããããã£ãŒã«ãã®æèãã³ãã«å¿ããŠå€æããŸãã
function adaptKana(value, hint) {
if (/åè§|ïŸïŸïœ¶ïœž|hankaku|åè§ã«ã/.test(hint)) return fullToHalfKana(value);
if (/ã²ãããª|ãµãããª|hiragana/.test(hint)) return kataToHira(value);
return value; // æ¢å®ã¯å
šè§ã«ã¿ã«ã
}
buildContext ããåè§ã«ãã§ããã²ãããªã§å
¥åããªã©ã®ã©ãã«ããã¹ãããŸãšããŠåéããŠããã®ã§ãããã§ãã®ãã³ãã䜿ããŸãã
ãã¿ã³è¡šç€ºã®å€å®ïŒãšã³ããªãŒãã©ãŒã ãã©ãããå€å¥ãã
ã³ã³ãã³ãã¹ã¯ãªãã㯠<all_urls> ã«æ¿å
¥ãããŸããæ±äººæ€çŽ¢ããŒãžãäŒæ¥ãããããŒãžã«ãŸã§ãèªåå
¥åããã¿ã³ãåºãã®ã¯éªéãªã®ã§ããæ¬ç©ã®ãšã³ããªãŒãã©ãŒã ãã©ããããå€å®ããŠãããã¿ã³ã衚瀺ããŸãã
const MIN_CLASSIFIED = 2;
function looksLikeForm() {
let n = 0;
for (const el of document.querySelectorAll(TEXT_FIELDS)) {
if (classifyField(buildContext(el)) && ++n >= MIN_CLASSIFIED) return true;
}
return false;
}
ããããã£ãŒã«é ç®ã«åé¡ã§ããæ¬ã2件以äžããããšããåºæºã§ããæ±äººçµã蟌ã¿ããŒãžã¯ãã§ãã¯ããã¯ã¹ã䞊ã¶ã ãã§ãæ°åã»äœæã»ã¡ãŒã«ã«ã¯åé¡ãããŸããããšã³ããªãŒãã©ãŒã ãªããããã容æã«2件以äžèŠã€ããã®ã§ãéŸå€2ã§åååŒå¥ã§ããŠããŸãã
SPAïŒããŒãžé·ç§»ãªãã§äžèЧâãã©ãŒã ãåãæ¿ããã¿ã€ãã®æ¡çšã·ã¹ãã ïŒã§ã¯ãDOM å€åã MutationObserver ã§ç£èŠããŠãã¿ã³ãåçã«åºãå ¥ãããŸãã
let evalTimer = null;
const scheduleEval = () => {
clearTimeout(evalTimer);
evalTimer = setTimeout(evaluateButton, 400);
};
new MutationObserver(scheduleEval)
.observe(document.body, { childList: true, subtree: true });
debounce ãå ¥ããŠããã®ã¯ããã¿ã³èªèº«ã®è¿œå ã§ã MutationObserver ãçºç«ããããã§ãã
ãã¹ãïŒjsdom ã§å šããžãã¯ããã©ãŠã¶ãªãã«æ€èšŒ
ã³ãŒããæžãã«ããã£ãŠäžçªæ°ã«ããã®ã¯ããã©ãŠã¶ãèµ·åããªããšãã¹ãã§ããªããç¶æ³ãé¿ããããšã§ãããChrome æ¡åŒµã®ã³ã³ãã³ãã¹ã¯ãªããã¯ãã©ãŠã¶äžã§åããã®ã§ãããããžãã¯ãçŽç²é¢æ°ã«åãåºãããšã§ Node.js äžã§ãã¹ãã§ããããã«ããŠããŸãã
ãã¹ãã¯4ãã¡ã€ã«ã«åãããŠããŸãã
matcher.test.jsïŒjsdom äžèŠïŒïŒclassifyField ã®åäœãã¹ããå®ãã©ãŒã ã§èžãã 誀åé¡ããã®ãŸãŸååž°ãã¹ããšããŠè¿œå ããŠããŸãã
const cases = [
["æ°å", "fullName"],
["ãåå", "fullName"],
["å§", "lastName"],
["å", "firstName"],
// ... å®ãã©ãŒã ã§èžãã çœ ã®ååž°ãã¹ã
["çŸäœæåžåºé¡ã»å°å", "city"], // ãå°åããå(first)ãšèª€èªããªã
["åŠæ ¡åã®é æå", null], // é æåæ¬ã¯ã¹ããã
["äŒç€Ÿå", null], // è€åèªã®ãåããå(first)ã«ããªã
["æ°å å§", "lastName"], // è¡ã®ãæ°åãã§æœ°ãããå§ãšå€å®
["æ°å å", "firstName"], // åäžãåãšå€å®
["æµ·å€åšäœã®æ¹ã¯ãã¡ã", null], // æµ·å€å°çšæ¬ã¯ã¹ããã
// ...
];
e2e.test.jsïŒsample-form.htmlïŒtable/div äž¡ã¬ã€ã¢ãŠããå«ãïŒã« jsdom ã䜿ã£ãŠ content.js ãå®è¡ããåãã£ãŒã«ããæ£ããåãŸãããæ€èšŒããŸãã
e2r.test.jsïŒe2r ç³»ãã©ãŒã ã®ååž°ãã¹ãããåœå çšã®åå²ããã¯ã¹ããšãæµ·å€çšã®åç¬ããã¯ã¹ããåäžè¡ã«äžŠã¶ç¹æ®ã¬ã€ã¢ãŠãã§ãæµ·å€ããã¯ã¹ã空ã®ãŸãŸãåœå ããã¯ã¹ã ãæ£ããåãŸãããšã確èªããŸãã
button-gate.test.jsïŒlooksLikeForm ã®å€å®ãã¹ããæ±äººæ€çŽ¢ããŒãžã§ãã¿ã³ãåºããªãããšã³ããªãŒãã©ãŒã ã§ã¯åºããã®2ã±ãŒã¹ã確èªããŸãã
çŸæç¹ã§ã®ãã¹ãçµæã¯ä»¥äžã®éãã§ãã
matcher.test.js : 63/63 å
šéé
e2e.test.js : 34/34 å
šéé
e2r.test.js : 20/20 å
šéé
button-gate.test.js: 3/3 å
šéé
åèš: 120/120 å
šéé
èžãã èœãšã穎
1. äŸïŒå§ïŒããã·ã¿ãåïŒã¿ããŠïŒ ãåé¡ãæ±æãã
ã©ãã«ã«é£æ¥ãããå
¥åäŸãããã¹ãã buildContext ã«æ··å
¥ãããšããå§ãã®æ¬ã§ããåããšããæååãåºãŠããŠèª€åé¡ããŸããstripExample ã§é€å»ããããã«ããŠããè§£æ¶ããŸããã
2. ãäŒç€ŸåããåŠæ ¡åãã®ãåãã first name ã«åŒã£ããã
å ãšããæåãå«ãŸãããš firstName ã«åé¡ãããŠããŸãåé¡ã§ããè€åèªãªã¹ã COMPOUND ãäœãããååã»ä»¶åã»å€§åŠåã»äŒç€Ÿåã»åŠæ ¡åããªã©ã¯é€å€ããããã«ããŸããã
3. ãæ°å å§ããšããè¡ã©ãã«ïŒåããããŒã®è€åãã¿ãŒã³
table ã¬ã€ã¢ãŠãã§è¡ããããŒã«ãæ°åããåããããŒã«ãå§ããæ¥ãã±ãŒã¹ããããŸãããbuildContext ãäž¡æ¹ãæŸãã®ã§æèã¯ãæ°å å§ãã«ãªããŸãããã®å Žå hasFullïŒæ°åããïŒãš isLastïŒå§ããïŒãäž¡æ¹ true ã«ãªããŸããå
ã« isLast ãå€å®ããã° lastName ã«èœã¡ããããå€å®ã®é åºã§è§£æ±ºããŸããã
4. e2r ç³»ã®ãåœå ããã¯ã¹ããšãæµ·å€ããã¯ã¹ãã®æ··åš
äžéšã®æ¡çšã·ã¹ãã ïŒe2rïŒã¯ãåœå
çšã®å°ããªåå²ããã¯ã¹3ã€ããšãæµ·å€çšã®åç¬å€§ããã¯ã¹1ã€ããåãè¡ã«äžŠã¹ãŸãããã€ãŒãã«ãåãè¡ã® phone æ¬ãããŸãšãããšæµ·å€ããã¯ã¹ã«å
šæ¡ãå
¥ã£ãŠããŸããŸããsplitCluster ã§ maxlength ãå°ããïŒ1ã6æ¡ïŒããã¯ã¹ã ããåå²ã¯ã©ã¹ã¿ãšããŠåãåºãã倧ã㪠maxlength ã®ããã¯ã¹ãé€å€ããããšã§è§£æ±ºããŸããã
5. ã¡ãŒã«ã¢ãã¬ã¹ã2床ãèšå
¥ãã ãã ããã¡ãŒã«2ããšèª€èª
ã2床ããšãã衚çŸã®ã2ããã¡ãŒã«åºæ°ã®ã2ããšæ··åãããŸããã[2ïŒ][\sã]*(床|å|ç®|é) ãšãããã¿ãŒã³ãå
ã«é€å»ããŠããåºæ°ãã§ãã¯ããããã«ããŸããã
6. React ãã©ãŒã ã§ value ãéä¿¡æã«ç©ºã«ãªã
el.value = "..." ã§èŠãç®ã¯åãŸã£ãŠããReact ã state ãæŽæ°ããŠããªããã submit æã«ç©ºã«ãªãåé¡ã§ããsetNativeValue ã§ prototype ã® setter çµç±ã§æžã蟌ã¿ïŒã€ãã³ãçºç«ããããšã§å¯Ÿå¿ããŸããã
æ¬çªãã©ãŒã ã§ã®åäœã«ã€ããŠ
æ£çŽã«æžããšãæ¬çªã®å®éã®æ¡çšãã©ãŒã ã§ã®ç¶²çŸ çãªæ€èšŒã¯ãŸã ã§ããŠããŸããã
jsdom ããŒã¹ã®ãã¹ãã¯ããžãã¯ã®æ£ããã確èªãããã®ã§ããããå®éã®æ¡çšã·ã¹ãã ã§æ£ããå ¥åã§ããããã¯å¥ã®åé¡ã§ãããã©ãŒã ã® HTML æ§é ã»ã€ãã³ããªã¹ããŒã®å®è£ ã»SPA ã®ãã¬ãŒã ã¯ãŒã¯ã¯ãµãŒãã¹ããšã«ç°ãªããŸããsample-form.html ãš sample-e2r.html ã¯å žåçãªãã¿ãŒã³ãã«ããŒããããäœããŸãããããã¹ãŠã®ã±ãŒã¹ãç¶²çŸ ããŠããããã§ã¯ãããŸããã
䜿ãéã¯å¿ ãå 容ã確èªããŠããéä¿¡ããŠãã ãããæ¡åŒµãå ¥ååŸããå 容ãå¿ ã確èªããŠããšããããŒã¹ãéç¥ãåºãããã«ããŠããŸããã責任ã¯å©çšè åŽã«ãããŸãã
ãŸãšã
- MV3 +
"permissions": ["storage"]ã®ã¿ïŒå€éšãšã®éä¿¡ãªãããããã£ãŒã«ã¯ãã©ãŠã¶ã®ããŒã«ã«ã«ä¿å buildContextïŒ<table>/<div>/<dl>ãæšªæããŠã©ãã«ãåéãç¥å 5éå±€ãŸã§é¡ãclassifyFieldïŒçŽç²é¢æ°ãæ£èŠè¡šçŸã®é åºãåé¡ã®åªå 床ã«ãªããstripExampleã§äŸç€ºããã¹ããå ã«é€å»detectDateRoleïŒoption ã®å€ã¬ã³ãžããå¹Žã»æã»æ¥ãæšå®ã3åå² select ã«å¯Ÿå¿setNativeValueïŒprototype ã® setter çµç± + ã€ãã³ãçºç«ã§ React/Vue ã«ã远ésplitClusterïŒmaxlength ã§åœå çšãšæµ·å€çšããã¯ã¹ãåºå¥- ãã¹ãïŒjsdom ã䜿ã£ãŠ Node.js äžã§ 120 æ¬ã®ãã¹ãããã©ãŠã¶ãªãã«å®è¡
- æ¬çªãã©ãŒã ã§ã®å®å šæ€èšŒã¯æªå®æœïŒäœ¿ãéã¯å 容確èªå¿ é
就掻ãã©ãŒã ã®ããªãšãŒã·ã§ã³ã¯æ¬åœã«å€ãããã®ã³ãŒããæ³å®å€ã®ã¬ã€ã¢ãŠãã§èª€åäœããããšã¯ååããåŸãŸããcontent.js ã® DEBUG = true ã«ãããš console.log ã§æªåé¡ãã£ãŒã«ãã®æèãåºãã®ã§ãåé¡ãããã° classifyField ã«æ¡ä»¶ãè¶³ã圢ã§ããããåœãŠãŠããŸãã
LilyïŒ@bokuwalilyïŒâ å人éçºè ãClaude Code ã§èªåååºç€ãçµã¿ãªãããiOSã¢ããªãWebãµãŒãã¹ãéç£ããŠããŸã
- äœã£ãã¢ããªã¯ ããŒããã©ãªãª ã«ãŸãšããŠããŸãð±
- æ°çã»éçºã®è£åŽã¯ X @bokuwalily ã§çºä¿¡ããŠããŸãð
- OSS: github.com/bokuwalily ð
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ