我们来详细解释一下这段代码。
这是一段为 JSBox 应用编写的 剪贴板历史管理器 脚本。JSBox 是一个在 iOS 上运行 JavaScript 代码的强大工具,可以通过脚本实现各种自动化功能。
核心功能概览
这个脚本的核心目标是:
-
自动记录:在后台静默监控系统剪贴板,每当有新的内容被复制时,自动保存下来。
-
持久化存储:将剪贴板历史记录安全地存储在 iOS 的钥匙串(Keychain)中,即使关闭脚本或重启手机,数据也不会丢失。
-
管理与交互:提供一个用户界面,让你可以浏览、搜索、复制、置顶、编辑和删除历史记录。
代码分段详解
我们将代码分成几个逻辑部分来理解。
1. 全局设置 (Settings)
const KC_KEY = "CLIPBOARD_HISTORY"; // 在钥匙串中存储数据用的“钥匙”
// Settings
const MAX_ITEMS = 200; // 最多保存200条历史记录
const POLL_INTERVAL = 1.5; // 每1.5秒检查一次剪贴板
const MIN_TEXT_LEN = 1; // 文本长度至少为1才记录
const IGNORE_SAME_CONSEC = true; // 如果连续复制同样的内容,则忽略
这部分定义了脚本的基本行为:
-
KC_KEY: 一个唯一的字符串,用作在钥匙串中存取数据的标识符。 -
MAX_ITEMS: 为了防止历史记录无限增长占用过多空间,这里限制最多只保存200条。当超过时,最旧的记录会被删除。 -
POLL_INTERVAL: 轮询间隔,即脚本每隔多久检查一次剪贴板有没有新内容。1.5秒是一个比较均衡的设置。 -
MIN_TEXT_LEN: 太短的内容(比如一个空格)通常没有意义,所以不记录。 -
IGNORE_SAME_CONSEC: 这是一个优化。如果你反复按Cmd+C复制同一个东西,脚本只会记录一次。
2. 数据存储与管理函数 (Utilities)
这部分函数负责处理数据的读取、保存和清理,它们都与钥匙串($keychain)交互。
-
loadHistory(): 从钥匙串中读取历史记录。它使用JSON.parse将存储的字符串转换回 JavaScript 数组。如果读取失败或没有数据,返回一个空数组。 -
saveHistory(list): 将历史记录数组(list)保存到钥匙串。在保存前,它会确保记录不超过MAX_ITEMS的限制,并使用JSON.stringify将数组转换为字符串。 -
clearAll(): 清空所有历史记录,通过从钥匙串中移除对应的KC_KEY实现。
3. 核心数据操作函数
这部分函数是业务逻辑的核心,负责增、删、改、查历史记录。
-
normalizeText(t): 一个辅助函数,用于“标准化”文本。它会统一换行符(\r\n->\n)并去除首尾的空格,这样可以确保比较文本时的一致性。 -
upsertItem(list, text, opts): 这是最核心的函数之一,实现了 "Update or Insert"(更新或插入) 的逻辑。-
如果
text在历史记录中 已存在,它会更新这条记录的时间戳,并将其移动到列表顶部(如果记录被置顶,则移动到所有置顶项的顶部)。 -
如果
text是 新内容,它会创建一个新的记录对象(包含文本、当前时间戳、是否置顶),并将其插入到列表顶部(同样,置顶项优先)。 -
这个函数保证了最新使用或复制的内容总是在最前面,非常符合使用习惯。
-
-
pinToggle(list, index, filtered): 切换指定记录的 置顶 状态。它会修改记录的pinned属性,然后对整个列表重新排序,确保所有置顶项都在非置顶项之前,最后保存。 -
deleteItem(list, index, filtered): 删除指定的记录。
4. UI 界面与状态管理
这部分代码负责构建用户界面并处理界面的数据更新。
-
state: 一个全局状态对象,存储了当前所有的历史记录 (list)、经过搜索过滤后的记录 (filtered)、搜索关键词 (q) 等。UI 的显示完全依赖于这个state对象。 -
filterList(): 根据搜索框中的关键词 (state.q) 过滤历史记录。它支持多个关键词搜索,并会对搜索结果进行排序,让匹配度更高的结果排在前面。 -
reloadListView(): 刷新列表视图。它会先调用filterList()获取最新的显示数据,然后将数据格式化(比如处理标题、副标题、时间戳、置顶标记📌),最后更新到界面上的列表($("list"))中。 -
copyItem(index): 当用户点击列表中的某一项时触发。它会将该项的文本复制到系统剪贴板,并调用upsertItem将其更新到列表顶部。 -
editSheet(index): 当用户选择编辑某一项时,弹出一个新的页面,其中包含一个文本框,允许用户修改内容并保存。
5. 剪贴板自动监控 (Polling)
-
startPolling(): 启动一个定时器 ($timer.schedule),按照前面设定的POLL_INTERVAL间隔,周期性地执行一个任务。 -
定时器任务:检查当前剪贴板的内容,如果内容是新的(与上次复制的不同,也与列表顶部的不同),就调用
upsertItem将其添加到历史记录中,并刷新界面。 -
stopPolling(): 停止定时器。这很重要,当脚本界面关闭时,需要调用它来停止后台检查,以节省系统资源。
6. 主界面定义 ($ui.render)
这是脚本的入口,定义了整个应用的界面布局和事件处理。
-
views数组:-
Toolbar (工具栏): 位于顶部的视图,包含了“导入”、“导出”、“清空”三个按钮和一个“搜索框”。
-
导入: 手动将当前剪贴板的内容添加到历史记录。
-
导出: 将所有历史记录格式化成纯文本,并弹出系统分享菜单。
-
清空: 弹出确认框,防止误操作,确认后清空所有历史。
-
搜索框: 输入文字时会实时调用
reloadListView筛选列表。
-
-
List (列表): 显示剪贴板历史的主体部分。
-
template: 定义了列表中每一行的样式(一个标题标签和一个副标题标签)。 -
actions: 定义了列表项 左滑 时出现的按钮(“置顶/取消”和“删除”)。 -
events: 定义了列表的交互事件。-
didSelect: 单击 列表项,触发copyItem复制内容。 -
didLongPress: 长按 列表项,弹出一个菜单,提供复制、置顶、编辑、删除、分享等更多操作。
-
-
-
-
events对象:-
appeared: 当脚本界面 出现 时触发。在这里,它会加载历史记录,并启动startPolling开始监控剪贴板。 -
disappeared: 当脚本界面 消失(比如切换到其他应用或关闭)时触发。在这里,它会调用stopPolling停止监控,节省电量。
-
总结
总而言之,这是一个设计精良、功能完备的剪贴板历史管理工具。它利用 JSBox 提供的原生 UI 和系统能力($clipboard, $keychain, $timer),实现了自动记录、安全存储、高效管理和友好交互等一系列功能。代码结构清晰,通过 state 对象管理状态,通过函数分离不同职责,是一个很好的 JSBox 脚本范例。