在移动 Safari 上编辑维基百科源代码听起来只是比普通输入多敲几个字符实际遇到的最大麻烦却来自一个双关语capital issue。从字面上看它是“大写字母问题”从体验上看它又真的是“严重问题”。当用户拿着手机在维基百科的原生编辑页里输入|image、ref、{{cite web}}这类 wikitext 片段时iOS 键盘会自作主张地把句首字母改成大写把ref这类单词改成别的拼写最后保存进去的内容和用户看到的完全不一样轻则模板参数失效重则页面展示异常。这篇文章就从这样一个真实场景出发先讲清楚 iOS 文本输入框的自动大写、自动更正和拼写检查链路再用一个最小页面复现问题随后给出两个可运行的解决方案一个独立运行的移动端 wikitext 编辑器 Web App以及一个能直接修复维基百科原生编辑页的书签小工具。无论你是移动 Web 开发者、MediaWiki 脚本维护者还是经常在手机上写技术内容的用户这套分析思路都能复用。1. 先从 Mobile Safari 的 capital issue 理解问题1.1 谁会在移动 Safari 上编辑维基百科过去提到维基百科编辑默认场景总是电脑浏览器。但在真实的内容贡献人群里有相当一部分人会在通勤路上、外出时用手机登录维基百科处理小修小补的编辑比如补充引用来源、修正错别字、调整段落顺序。这些人里不少使用 iPhone 自带的 Safari也就是移动 Safari。编辑维基百科有两种入口可视化编辑器以及编辑源代码。可视化编辑器在移动端虽然有但对复杂模板支持有限。真正要修复模板参数、处理分类、检查ref标签时用户仍然需要切到“编辑源代码”视图。源代码视图的核心操作区域就是一个textarea用户在这个文本域里输入 wikitext。问题恰恰集中在这个textarea上。移动 Safari 为了照顾普通输入场景默认对textarea启用句子首字母大写、自动改正、拼写检查等行为。这些行为对聊天和发邮件很有帮助但面对大小写敏感的模板参数、固定拼写的标签和魔术字时就成了帮倒忙。标题里那个 capital issue 并不是少数人的个例。只要在手机上完整编辑一次含模板的条目几乎都会遇到|param被改成|Param或者ref被改成Ref的情况。前者会导致模板参数无法匹配后者会让引用标签功能异常因为 MediaWiki 的 XML 标签名大小写并不总是宽松处理。1.2 iOS 键盘的自动大写、自动更正和预测输入做了什么移动 Safari 中输入行为的关键控制层有两层一层是页面本身的 HTML 属性另一层是 iOS 系统键盘的智能输入引擎。默认情况下iOS 键盘会对文本输入框应用以下行为第一是自动大写。键盘默认在句首开启大写状态用户输入一个小写字母时系统可能直接替换为大写字母。对普通文本来说这是符合习惯的但 wikitext 中很多标记恰恰必须保持小写比如模板参数|url、|title一旦变成|Url、|Title模板解析就会出现问题。第二是自动更正。iOS 内置词典会把常见拼写错误替换成它认为正确的词同时还会统一大小写。比如速度较快时输入ref再输入空格iOS 可能把ref识别成某个常见单词并替换拼写或者把整个词首字母大写。很多编辑者保存后打开预览才发现引用标签已经损坏。第三是拼写检查。系统会给它认为拼写错误的单词画上红色波浪线并在长按菜单里给出“替换”建议。wikitext 里大量片段都不是标准英文单词所以几乎每个模板名都会被标记为拼写错误误导用户去“更正”。第四是预测输入和快捷短语。键盘上方会出现候选词用户一旦误点就会把一段 wikitext 替换成普通短语。快捷短语虽然在系统设置里可以添加但无法解决textarea本身不接受禁用属性的问题。这些行为叠加起来对移动端 wikitext 编辑者就是灾难。换句话说普通用户看到的是“手机帮我修正”编辑者看到的是“手机帮我改坏”。1.3 为什么直接改 Safari 设置解决不了不少人的第一反应是去 iOS 设置里关闭自动大写。但很遗憾iOS 没有提供一个全局且稳定的开关能让所有网页输入框都不再自动大写。系统设置中的“自动大写”和“自动更正”开关确实会影响键盘行为但网页里的textarea可能被浏览器内部策略覆盖而且第三方键盘和系统英文键盘的响应方式并不一致。更关键的是维基百科原生编辑页并不是你写的页面你无法修改它的 textarea 属性。即使用户关闭了系统设置里的自动修正也不能保证原生编辑页的每个输入框都符合预期一旦切换键盘或者升级 iOS行为还可能变化。所以正确的解决思路不在系统设置层而是在页面输入控件层。HTML 标准提供了autocapitalize、autocorrect、autocomplete、spellcheck这些属性维基百科原生页面如果没有设置它们我们就在自己的工具里设置或者用一段脚本临时给原生页面的 textarea 打补丁。这比期待用户手动改系统设置更可控也更容易复现和测试。2. 用最小页面复现问题定位根因2.1 建立一个可复现的 HTML 页面要确认 capital issue 到底出在哪里第一步不是改代码而是做一个最小复现页面。在本地创建一个capital-test.html内容如下!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleiOS Capital Issue Test/title /head body h3默认 textarea/h3 textarea iddefaultBox rows4 stylewidth:100%/textarea h3关闭纠正属性的 textarea/h3 textarea idfixedBox rows4 stylewidth:100% autocapitalizeoff autocorrectoff autocompleteoff spellcheckfalse /textarea /body /html页面里有前后两个 textarea。第一个保持默认第二个设置autocapitalizeoff、autocorrectoff、autocompleteoff和spellcheckfalse。用 HTTP 服务器启动这个页面python3 -m http.server 8080然后使用 iPhone 的 Safari 打开http://你电脑的局域网IP:8080/capital-test.html分别在两个框中输入|image和ref这类内容观察键盘行为。预期结果是默认 textarea 在输入|image时很可能把首字母变成|Image设置了纠正属性的 textarea 则能保持小写。这个对比实验能快速确认属性是否生效也能帮判断是系统键盘问题还是页面属性问题。2.2 从 DOM 属性和键盘提示判断谁在起作用最小页面复现之后还需要知道当前属性是否真的应用到了输入控件上。这里推荐使用 Safari 的 Web Inspector 对 iPhone 页面进行远程调试。在 Mac 上给 iPhone 插上数据线在 Safari 菜单栏选择“开发”然后在子菜单里选择对应的 iPhone 页面打开 Web Inspector。选中 textarea 后在右侧 Attributes 面板里检查有没有autocapitalizeoff、autocorrectoff和spellcheckfalse。如果属性仍然存在但键盘行为没有变化就要考虑两个方向一是当前页面是否缓存了旧版本二是当前使用的键盘是否忽略这些属性。系统英文键盘通常遵循属性但部分第三方输入法有自己的策略。遇到这种情况时可以切换系统英文键盘再测试。如果想进一步确认自动更正发生在哪一步可以在页面中加入事件监听const box document.getElementById(fixedBox); box.addEventListener(input, function () { console.log(input value:, JSON.stringify(box.value)); }); box.addEventListener(keydown, function (e) { console.log(keydown key:, e.key); });在输入之前keydown记录的是按键本身input事件触发时value已经是系统处理后的结果。如果keydown里按下的是r但input后 value 变成了大写R就说明自动大写发生在系统输入管线的底层普通 JS 事件无法拦截只能通过属性去关闭。2.3 结论解铃还须在文本输入层处理通过最小页面和事件日志可以得出一个结论对于移动 Safari 的文本输入根因并不在应用层而在浏览器和键盘共同处理的输入管线。autocapitalize、autocorrect、spellcheck这类属性是页面层唯一能明确告知系统“这里不要给我做智能处理”的入口。因此后续两条技术路线都非常直接。要么自己开发一个设置了这些属性的 textarea要么在不改动维基百科源码的前提下用一段脚本遍历页面里的 textarea 并补上这些属性。两条路线都围绕同一个根因展开只是工程形态不同。3. 方案一独立 wikitext 编辑器 Web App3.1 技术选型为什么不用原生 App既然要解决维基百科编辑问题第一反应可能是做一个原生 App。但原生 App 要对接维基百科的登录、OAuth、编辑 API开发成本明显更高。而且维基百科用户通常已经在 Safari 里登录如果用原生 App就要重新走一遍授权流程体验并不比网页更好。更轻的方案是做一个独立 Web App。它使用手机的浏览器环境可以继承现有登录 Cookie不需要处理用户名密码。最终项目结构大致如下wikipedia-mobile-editor/ ├── index.html ├── app.css ├── app.js └── config.js其中config.js用来配置 API 端点和站点地址index.html负责结构app.js处理读取、编辑和保存逻辑。3.2 关闭 iOS 文本纠正的核心 HTML 配置核心输入区放在index.html中textarea 上必须显式声明属性textarea idwikitext autocapitalizeoff autocorrectoff autocompleteoff spellcheckfalse placeholder在此输入 wikitext /textarea这几个属性的作用可以分得很清楚autocapitalizeoff关闭句子首字母大写旧写法autocapitalizenone在部分 iOS 版本上也兼容但新写法更推荐。autocorrectoff关闭自动更正避免ref被替换成其他单词。autocompleteoff关闭历史输入提示避免粘贴候选干扰光标位置。spellcheckfalse关闭拼写检查红波浪线。还有一个容易被忽略的 CSS 设置。iOS Safari 在输入框获得焦点后如果字体小于 16px页面会自动放大影响工具栏点击。可以在样式里把基准字号定大并禁用自动缩放textarea { font-size: 16px; -webkit-text-size-adjust: 100%; }这里的-webkit-text-size-adjust不是标准用法但在移动 Safari 中常用来避免字号自动调整。3.3 用 MediaWiki API 完成读取、编辑与保存独立 Web App 不直接操作维基百科的数据库而是调用 MediaWiki API。以中文维基百科为例API 地址通常是https://zh.wikipedia.org/w/api.php。由于页面和维基百科不同源需要开启跨域支持具体是否允许取决于站点配置。如果是做本地开发可以先通过反向代理或者浏览器的跨域调试模式验证逻辑生产环境再到同一域名下部署。核心能力有两块读取当前页面内容以及保存修改后的内容。读取页面可以调用actionqueryasync function readPage(title) { const url new URL(/w/api.php, API_ORIGIN); const params new URLSearchParams({ action: query, prop: revisions, rvprop: content, rvslots: main, titles: title, formatversion: 2, format: json }); url.search params.toString(); const resp await fetch(url, { credentials: include }); const data await resp.json(); const pages data.query.pages || []; const page pages[0]; if (page page.revisions page.revisions[0]) { return page.revisions[0].slots.main.content; } return ; }保存前需要获取 CSRF token。MediaWiki 的编辑操作要求携带用户自己的编辑 token用于防跨站请求伪造。获取方式如下async function getCsrfToken() { const url new URL(/w/api.php, API_ORIGIN); const params new URLSearchParams({ action: query, meta: tokens, type: csrf, format: json }); url.search params.toString(); const resp await fetch(url, { credentials: include }); const data await resp.json(); return data.query.tokens.csrftoken; }拿到 token 后再提交编辑async function savePage(title, wikitext) { const token await getCsrfToken(); const body new URLSearchParams({ action: edit, title: title, text: wikitext, token: token, summary: Fix case issues via mobile editor, format: json }); const resp await fetch(/w/api.php, { method: POST, body, credentials: include }); const data await resp.json(); if (data.error) { throw new Error(data.error.code : data.error.info); } return data; }这个流程里最关键的一点是token必须来自当前登录会话不能用硬编码字符串。如果用户没有登录API 会返回notoken或权限错误。3.4 增加移动端常用符号工具栏有了能输入、能保存的 textarea 之后还要解决一个操作效率问题。用户手打ref、{{}}、[[ ]]这些符号时即使关闭了自动大写也仍然容易出错。更稳妥的做法是提供工具栏按钮让用户点一下按钮就插入完整片段。工具栏按钮可以放在 textarea 上方点击后读取光标位置并插入指定文本function insertAtCursor(textarea, text) { const start textarea.selectionStart; const end textarea.selectionEnd; const value textarea.value; textarea.value value.slice(0, start) text value.slice(end); textarea.focus(); const newPos start text.length; textarea.setSelectionRange(newPos, newPos); }按钮示例button onclickinsertAtCursor(document.getElementById(wikitext), lt;refgt;lt;/refgt;)ref/button button onclickinsertAtCursor(document.getElementById(wikitext), {{){{/button button onclickinsertAtCursor(document.getElementById(wikitext), [[ ]])[[ ]]/button点击ref按钮时虽然 HTML 里写的是lt;refgt;但最终插入到 textarea 的字符串是ref/ref。这样用户不需要手动切换键盘符号页也减少了自动更正介入的机会。移动端工具栏值得优先放的符号包括 小节 、ref、/ref、{{、}}、[[、]]、|、*、#。这些是 wikitext 编辑里最常用、也最容易在手机上输入出错的符号。3.5 验证流程真机输入、保存、回读完成 Web App 后不能只看页面能打开必须做一轮真机验证。验证清单如下检查项预期结果异常排查textarea 属性元素上包含autocapitalizeoff检查缓存或是否加载了旧文件输入 image首字母没有被自动大写输入ref没有自动更正为其他拼写检查autocorrectoff是否生效点击工具栏插入文本插入位置正确检查selectionStart是否受按钮聚焦影响读取页面能显示 wikitext 内容检查登录态和跨域配置保存内容返回result: Success查看 token 是否过期或页面是否被保护这里要特别注意按钮聚焦问题。在 iOS 上点击按钮会先让按钮获得焦点可能导致 textarea 丢失选区。因此每次插入后必须重新调用textarea.focus()再使用setSelectionRange定位光标。如果不做这步插入结果往往会出现在文本末尾。4. 方案二一个 bookmarket 快速修复原生编辑页4.1 原理在现成文本域上覆盖根因属性独立 Web App 适合长期使用但不是每个人都愿意切换编辑入口。反过来看维基百科原生编辑页里已经有一个好的文本域只是它缺少关闭自动大写的属性。这种情况下可以用一段脚本在原生页面上运行时打补丁。书签工具bookmarklet就是一段可以保存到浏览器书签里的 JavaScript。点击它之后脚本会把当前页面所有适合输入的控件遍历一遍并设置我们需要的那几个属性。这段脚本不会改变页面任何内容也不修改维基百科的保存逻辑只是覆盖输入控件属性因此风险很低。4.2 代码实现与安装方式首先给出便于阅读的版本javascript:(function () { var selectors textarea, input[typetext], input[typesearch], input[typeurl]; var nodes document.querySelectorAll(selectors); nodes.forEach(function (el) { el.setAttribute(autocapitalize, off); el.setAttribute(autocorrect, off); el.setAttribute(autocomplete, off); el.setAttribute(spellcheck, false); }); alert(done: nodes.length field(s) patched); })();实际存为书签时需要把这段代码压缩成一行并处理 URL 编码。因为浏览器书签的 URL 只能是一行所以完整可用的形式是javascript:(function(){var stextarea, input[typetext], input[typesearch], input[typeurl];document.querySelectorAll(s).forEach(function(el){el.setAttribute(autocapitalize,off);el.setAttribute(autocorrect,off);el.setAttribute(autocomplete,off);el.setAttribute(spellcheck,false);});alert(done);})();在 iPhone Safari 中添加这个书签时可以先收藏任意一个普通页面然后编辑书签名称并把 URL 替换为上面这串代码。进入维基百科编辑源代码页面后点击这个书签页面上的 textarea 就会被补上属性。此时再在文本框里输入|param或refiOS 键盘不再自动纠正。4.3 为什么只能作为应急补充书签工具的优势是零依赖、不部署服务器但它的缺点是每次打开新编辑页都要重新点击一次。这是因为新页面加载后之前注入的属性不会保留。另外书签工具只对当前文档里的已有元素生效。如果维基百科编辑器是异步渲染的脚本执行时 textarea 还没有出现在 DOM 里就需要稍等再点击或者改成定时器重试的版本。从工程角度看书签工具适合作为应急方案或者给少量用户使用不适合作为团队内部的标准工具。稳定使用还是建议走独立 Web App因为可以统一控制版本、缓存、错误上报和用户引导。5. 常见坑与排查链路5.1autocapitalizeoff在部分 iOS 上不生效现象代码里明明设置了autocapitalizeoff但在真机上输入句首字母时仍然被大写。可能原因有很多。第一属性值写错了比如写成autocapitalizefalse并不会关闭自动大写标准值应该是off或none。第二元素不是 textarea而是一个contenteditable的 divautocapitalize属性在部分版本中只对表单控件生效。第三用户使用的是第三方键盘第三方键盘可能不完全遵守网页属性。排查步骤打开 Web Inspector确认元素上存在autocapitalizeoff。切换到系统英文键盘再测试。在设置中检查 iOS 的“自动大写”开关是否被全局禁用或启用。把输入框从contenteditable改成textarea或反过来做对照实验。如果确认属性已经生效但仍被大写建议放弃依赖键盘行为的思路改用工具栏插入完整符号避免用户手打容易被大写的片段。5.2 自动更正替换了ref或模板参数现象设置了autocorrectoff但输入ref后仍被改成了其他拼写或大小写。这通常有两个原因一是属性没有真正追加到元素上比如书签工具执行时 textarea 尚未渲染二是 iOS 键盘的“自动更正”机制同时受系统词典和用户自定义短语影响有时即使关闭属性部分快速输入场景仍会触发替换。解决方式是双重保障。第一在 textarea 上同时设置autocorrectoff和spellcheckfalse不要只设其中一个。第二在保存前对内容做一次简单校验重点检查ref、 /ref、模板参数|xxx这些常见片段是否被改坏。如果检测到疑似被改写的标签可以提示用户检查。5.3 中文键盘和第三方键盘不可控现象使用九宫格中文键盘或第三方输入法时前面设置的属性全部失效句首仍然自动大写甚至出现了不希望看到的拼音联想。原因在于 iOS 系统英文键盘和中文键盘、第三方键盘对输入属性的实现并不一致。第三方键盘有权忽略部分 HTML 属性这样做的本意是保持输入习惯统一但对代码输入场景来说反而成了问题。排查时首先要确认键盘类型。在工具页面里可以加入提示让用户使用系统英文键盘编辑 wikitext。另外工具栏插入的方式能绕过大部分键盘自动行为因为点击按钮后文本是 JS 直接写入 value不经过键盘的自动更正管线。5.4 保存失败CSRF token、跨域和登录态现象点击保存按钮后接口返回notoken、badtoken或 403。建议按顺序排查当前浏览器是否已经登录维基百科。可以通过打开https://zh.wikipedia.org/wiki/Special:Watchlist来确认。actionquerymetatokenstypecsrf接口是否返回了csrftoken。如果没有说明会话不完整。fetch 请求是否带上了 Cookie。同源请求默认会带跨域请求则取决于credentials和 CORS 配置。检查保存的页面标题是否存在、是否被全保护或半保护。检查 token 是否已经过期重新获取后再试。可以用下面的表格快速对照错误信息常见原因处理方式notoken请求中没有携带 token先成功调用 tokens 接口badtokentoken 过期或与当前会话不匹配重新获取 token 再提交permissiondenied当前用户无编辑权限确认登录和页面保护状态readonly维基站点处于只读状态稍后重试保存逻辑中应加入错误提示不要只把接口返回 JSON 打印到 console而是显示在页面上方便移动端用户知道下一步该做什么。6. 从“能用”到“可维护”的工程化建议6.1 区分学习环境和生产环境在本地跑python3 -m http.server只适合学习真正给编辑者日常使用还需要考虑部署环境。独立 Web App 如果部署在和维基百科不同的域名下必须处理 API 跨域或者由后端代理转发请求。部署时尽量使用 HTTPS避免登录态被明文抓取。生产环境还需要处理这些细节配置多站点。zh.wikipedia.org 和 en.wikipedia.org 的 API 地址不同逻辑应抽成配置项。增加编辑冲突提示。用户打开页面后如果其他编辑者已经修改过同一页面直接保存会覆盖内容。保存前要重新读取版本并做差异提示。增加操作日志。在编辑器前端记录读取时间、保存时间、页面标题和操作结果便于排查问题。增加版本号。每次发布前端资源时在 URL 上带版本号避免手机浏览器缓存旧脚本。学习环境可以忽略这些问题但一旦有其他人使用就必须逐项补上。6.2 可复用清单移动端 wikitext 编辑器发布前检查清单以下清单可以直接用于上线前验证在真机 iPhone Safari 中打开页面检查 textarea 上autocapitalizeoff和autocorrectoff是否存在。用系统英文键盘输入|image、ref、{{cite web}}确认不会自动大写或替换。用中文键盘和第三方键盘输入同一组内容记录哪些行为不受控。点击工具栏按钮插入引用标签确认插入位置在当前光标位置而不是文本末尾。获取 CSRF token并确认请求带上了登录 Cookie。保存一个测试修改后在普通浏览器中打开页面确认内容没有被破坏。在弱网环境下测试保存按钮的重复点击防止重复提交。6.3 安全和合规注意开发这类工具时最需要守住的安全底线是不保存用户密码、不硬编码任何 token、不绕过维基百科的权限校验。获取编辑权限时优先复用浏览器里已有的登录态而不是把用户导向第三方登录页。如果未来要做独立站点只申请最小权限的 OAuth 授权尽量不申请管理权限。保存编辑内容时通过summary参数写清楚编辑摘要不要用机器人身份批量提交。每个用户都应以自己的账号完成编辑这样内容可追溯、也符合站点的编辑规范。6.4 下一步扩展方向这个项目的核心思路可以迁移到很多场景。比如在移动端写 Markdown、LaTeX 或 SQL同样会遇到自动大写和自动更正问题。只要你有一个面向代码输入的 textarea就值得检查是否设置了autocapitalizeoff、autocorrectoff、autocompleteoff和spellcheckfalse。更进一步的扩展方向包括把方案二升级为 Safari Web Extension不需要用户每次都手动点击书签。在 Web App 中集成 wikitext 语法高亮预览让编辑者保存前就发现标签闭合问题。增加离线草稿功能利用 localStorage 自动保存防止页面刷新导致内容丢失。接入更完整的配置界面让用户选择要编辑的语言版本和常用摘要。回到最开始那个 capital issue真正值得记住的技术判断是移动端代码输入问题不能只靠编辑器逻辑兜底而要先在输入控件层明确告诉系统“这里是代码不要自动纠正”。先用最小页面复现再根据自己的场景选择独立编辑器或书签工具这样的处理路径既稳又简单。
