在移动 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 lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>iOS Capital Issue Test</title> </head> <body> <h3>默认 textarea</h3> <textarea id="defaultBox" rows="4" style="width:100%"></textarea> <h3>关闭纠正属性的 textarea</h3> <textarea id="fixedBox" rows="4" style="width:100%" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" ></textarea> </body> </html>页面里有前后两个 textarea。第一个保持默认,第二个设置autocapitalize="off"、autocorrect="off"、autocomplete="off"和spellcheck="false"。
用 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 面板里检查有没有autocapitalize="off"、autocorrect="off"和spellcheck="false"。
如果属性仍然存在,但键盘行为没有变化,就要考虑两个方向:一是当前页面是否缓存了旧版本,二是当前使用的键盘是否忽略这些属性。系统英文键盘通常遵循属性,但部分第三方输入法有自己的策略。遇到这种情况时,可以切换系统英文键盘再测试。
如果想进一步确认自动更正发生在哪一步,可以在页面中加入事件监听:
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 App
3.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 id="wikitext" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" placeholder="在此输入 wikitext" ></textarea>这几个属性的作用可以分得很清楚:
autocapitalize="off"关闭句子首字母大写,旧写法autocapitalize="none"在部分 iOS 版本上也兼容,但新写法更推荐。autocorrect="off"关闭自动更正,避免ref被替换成其他单词。autocomplete="off"关闭历史输入提示,避免粘贴候选干扰光标位置。spellcheck="false"关闭拼写检查红波浪线。
还有一个容易被忽略的 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。由于页面和维基百科不同源,需要开启跨域支持,具体是否允许取决于站点配置。如果是做本地开发,可以先通过反向代理或者浏览器的跨域调试模式验证逻辑,生产环境再到同一域名下部署。
核心能力有两块:读取当前页面内容,以及保存修改后的内容。
读取页面可以调用action=query:
async 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 onclick="insertAtCursor(document.getElementById('wikitext'), '<ref></ref>')">ref</button> <button onclick="insertAtCursor(document.getElementById('wikitext'), '{{')">{{</button> <button onclick="insertAtCursor(document.getElementById('wikitext'), '[[ ]]')">[[ ]]</button>点击ref按钮时,虽然 HTML 里写的是<ref>,但最终插入到 textarea 的字符串是<ref></ref>。这样用户不需要手动切换键盘符号页,也减少了自动更正介入的机会。
移动端工具栏值得优先放的符号包括:== 小节 ==、<ref>、</ref>、{{、}}、[[、]]、|、*、#。这些是 wikitext 编辑里最常用、也最容易在手机上输入出错的符号。
3.5 验证流程:真机输入、保存、回读
完成 Web App 后,不能只看页面能打开,必须做一轮真机验证。验证清单如下:
| 检查项 | 预期结果 | 异常排查 |
|---|---|---|
| textarea 属性 | 元素上包含autocapitalize="off" | 检查缓存或是否加载了旧文件 |
| 输入 ` | image=` | 首字母没有被自动大写 |
输入<ref> | 没有自动更正为其他拼写 | 检查autocorrect="off"是否生效 |
| 点击工具栏插入 | 文本插入位置正确 | 检查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[type="text"], input[type="search"], input[type="url"]'; 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 s='textarea, input[type="text"], input[type="search"], input[type="url"]';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=或<ref>,iOS 键盘不再自动纠正。
4.3 为什么只能作为应急补充
书签工具的优势是零依赖、不部署服务器,但它的缺点是每次打开新编辑页都要重新点击一次。这是因为新页面加载后,之前注入的属性不会保留。
另外,书签工具只对当前文档里的已有元素生效。如果维基百科编辑器是异步渲染的,脚本执行时 textarea 还没有出现在 DOM 里,就需要稍等再点击,或者改成定时器重试的版本。
从工程角度看,书签工具适合作为应急方案或者给少量用户使用,不适合作为团队内部的标准工具。稳定使用还是建议走独立 Web App,因为可以统一控制版本、缓存、错误上报和用户引导。
5. 常见坑与排查链路
5.1autocapitalize="off"在部分 iOS 上不生效
现象:代码里明明设置了autocapitalize="off",但在真机上输入句首字母时仍然被大写。
可能原因有很多。第一,属性值写错了,比如写成autocapitalize="false"并不会关闭自动大写,标准值应该是off或none。第二,元素不是 textarea,而是一个contenteditable的 div,autocapitalize属性在部分版本中只对表单控件生效。第三,用户使用的是第三方键盘,第三方键盘可能不完全遵守网页属性。
排查步骤:
- 打开 Web Inspector,确认元素上存在
autocapitalize="off"。 - 切换到系统英文键盘再测试。
- 在设置中检查 iOS 的“自动大写”开关是否被全局禁用或启用。
- 把输入框从
contenteditable改成textarea或反过来做对照实验。
如果确认属性已经生效但仍被大写,建议放弃依赖键盘行为的思路,改用工具栏插入完整符号,避免用户手打容易被大写的片段。
5.2 自动更正替换了<ref>或模板参数
现象:设置了autocorrect="off",但输入<ref>后仍被改成了其他拼写或大小写。
这通常有两个原因:一是属性没有真正追加到元素上,比如书签工具执行时 textarea 尚未渲染;二是 iOS 键盘的“自动更正”机制同时受系统词典和用户自定义短语影响,有时即使关闭属性,部分快速输入场景仍会触发替换。
解决方式是双重保障。第一,在 textarea 上同时设置autocorrect="off"和spellcheck="false",不要只设其中一个。第二,在保存前对内容做一次简单校验,重点检查<ref>、< /ref>、模板参数|xxx=这些常见片段是否被改坏。如果检测到疑似被改写的标签,可以提示用户检查。
5.3 中文键盘和第三方键盘不可控
现象:使用九宫格中文键盘或第三方输入法时,前面设置的属性全部失效,句首仍然自动大写,甚至出现了不希望看到的拼音联想。
原因在于 iOS 系统英文键盘和中文键盘、第三方键盘对输入属性的实现并不一致。第三方键盘有权忽略部分 HTML 属性,这样做的本意是保持输入习惯统一,但对代码输入场景来说反而成了问题。
排查时首先要确认键盘类型。在工具页面里可以加入提示,让用户使用系统英文键盘编辑 wikitext。另外,工具栏插入的方式能绕过大部分键盘自动行为,因为点击按钮后文本是 JS 直接写入 value,不经过键盘的自动更正管线。
5.4 保存失败:CSRF token、跨域和登录态
现象:点击保存按钮后,接口返回notoken、badtoken或 403。
建议按顺序排查:
- 当前浏览器是否已经登录维基百科。可以通过打开
https://zh.wikipedia.org/wiki/Special:Watchlist来确认。 action=query&meta=tokens&type=csrf接口是否返回了csrftoken。如果没有,说明会话不完整。- fetch 请求是否带上了 Cookie。同源请求默认会带,跨域请求则取决于
credentials和 CORS 配置。 - 检查保存的页面标题是否存在、是否被全保护或半保护。
- 检查 token 是否已经过期,重新获取后再试。
可以用下面的表格快速对照:
| 错误信息 | 常见原因 | 处理方式 |
|---|---|---|
notoken | 请求中没有携带 token | 先成功调用 tokens 接口 |
badtoken | token 过期或与当前会话不匹配 | 重新获取 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 上
autocapitalize="off"和autocorrect="off"是否存在。 - 用系统英文键盘输入
|image=、<ref>、{{cite web}},确认不会自动大写或替换。 - 用中文键盘和第三方键盘输入同一组内容,记录哪些行为不受控。
- 点击工具栏按钮插入引用标签,确认插入位置在当前光标位置而不是文本末尾。
- 获取 CSRF token,并确认请求带上了登录 Cookie。
- 保存一个测试修改后,在普通浏览器中打开页面确认内容没有被破坏。
- 在弱网环境下测试保存按钮的重复点击,防止重复提交。
6.3 安全和合规注意
开发这类工具时,最需要守住的安全底线是不保存用户密码、不硬编码任何 token、不绕过维基百科的权限校验。
获取编辑权限时,优先复用浏览器里已有的登录态,而不是把用户导向第三方登录页。如果未来要做独立站点,只申请最小权限的 OAuth 授权,尽量不申请管理权限。
保存编辑内容时,通过summary参数写清楚编辑摘要,不要用机器人身份批量提交。每个用户都应以自己的账号完成编辑,这样内容可追溯、也符合站点的编辑规范。
6.4 下一步扩展方向
这个项目的核心思路可以迁移到很多场景。比如在移动端写 Markdown、LaTeX 或 SQL,同样会遇到自动大写和自动更正问题。只要你有一个面向代码输入的 textarea,就值得检查是否设置了autocapitalize="off"、autocorrect="off"、autocomplete="off"和spellcheck="false"。
更进一步的扩展方向包括:
- 把方案二升级为 Safari Web Extension,不需要用户每次都手动点击书签。
- 在 Web App 中集成 wikitext 语法高亮预览,让编辑者保存前就发现标签闭合问题。
- 增加离线草稿功能,利用 localStorage 自动保存,防止页面刷新导致内容丢失。
- 接入更完整的配置界面,让用户选择要编辑的语言版本和常用摘要。
回到最开始那个 capital issue,真正值得记住的技术判断是:移动端代码输入问题不能只靠编辑器逻辑兜底,而要先在输入控件层明确告诉系统“这里是代码,不要自动纠正”。先用最小页面复现,再根据自己的场景选择独立编辑器或书签工具,这样的处理路径既稳又简单。