@tensorix/metahub 0.5.4 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cli.js +666 -152
  2. package/dist/cli.js.map +23 -17
  3. package/dist/core/copy-views.d.ts +3 -0
  4. package/dist/core/copy-views.d.ts.map +1 -0
  5. package/dist/core/csv-columns.d.ts +6 -0
  6. package/dist/core/csv-columns.d.ts.map +1 -0
  7. package/dist/core/databases.d.ts.map +1 -1
  8. package/dist/core/sync/auth.d.ts.map +1 -1
  9. package/dist/core/sync/cf-oauth.d.ts.map +1 -1
  10. package/dist/core/sync/files.d.ts.map +1 -1
  11. package/dist/core/sync/page-messages.d.ts +5 -0
  12. package/dist/core/sync/page-messages.d.ts.map +1 -1
  13. package/dist/core/sync/remote-shares-cache.d.ts.map +1 -1
  14. package/dist/core/sync/room-serve.d.ts.map +1 -1
  15. package/dist/core/sync/share-actions.d.ts.map +1 -1
  16. package/dist/core/sync/share-copy.d.ts +1 -1
  17. package/dist/core/sync/share-copy.d.ts.map +1 -1
  18. package/dist/core/sync/share-export.d.ts +4 -2
  19. package/dist/core/sync/share-export.d.ts.map +1 -1
  20. package/dist/core/sync/share-file-styles.d.ts +2 -0
  21. package/dist/core/sync/share-file-styles.d.ts.map +1 -0
  22. package/dist/core/sync/share-link.d.ts +27 -0
  23. package/dist/core/sync/share-link.d.ts.map +1 -0
  24. package/dist/core/sync/share-serve.d.ts.map +1 -1
  25. package/dist/core/sync/share-short-links.d.ts +8 -0
  26. package/dist/core/sync/share-short-links.d.ts.map +1 -0
  27. package/dist/core/sync/sites-serve.d.ts.map +1 -1
  28. package/dist/core/ui-theme.d.ts +44 -0
  29. package/dist/core/ui-theme.d.ts.map +1 -0
  30. package/dist/db-worker.js +64 -64
  31. package/dist/edge-worker.js +137 -8
  32. package/dist/index.js +619 -128
  33. package/dist/index.js.map +20 -15
  34. package/dist/metahub-sdk.js +4 -4
  35. package/dist/mh-runtime.js +2 -2
  36. package/dist/sw.js +9 -6
  37. package/dist/webui/server/assets.d.ts.map +1 -1
  38. package/dist/webui.js +130 -130
  39. package/package.json +1 -1
package/dist/sw.js CHANGED
@@ -1,4 +1,7 @@
1
- function z(e){if(!e||e.length===0)return"zh-CN";for(let t of e){if(!t)continue;if(/^zh\b/i.test(t))return"zh-CN";if(/^en\b/i.test(t))return"en"}return"en"}function K(e){if(!e)return"zh-CN";return z(e.split(",").map((t)=>t.split(";")[0].trim()))}var l=(e,t,n)=>Number(e)===1?t:n;var ge={刚刚:"just now","{n} 分钟前":({n:e})=>`${e} ${l(e,"minute","minutes")} ago`,"{n} 小时前":({n:e})=>`${e} ${l(e,"hour","hours")} ago`,"{n} 天前":({n:e})=>`${e} ${l(e,"day","days")} ago`,今天:"Today",昨天:"Yesterday",本周:"This week","{n} 天后过期":({n:e})=>`Expires in ${e} ${l(e,"day","days")}`,"{n} 小时后过期":({n:e})=>`Expires in ${e} ${l(e,"hour","hours")}`,"{n} 分钟后过期":({n:e})=>`Expires in ${e} ${l(e,"minute","minutes")}`,"快照 {when}":"Snapshot {when}",还没有活动:"No activity yet","{ago}活跃":"Active {ago}","{day}后无活动":"Inactive since {day}",从未:"Never",无标题:"Untitled",语言:"Language",跟随系统:"System","更改语言后页面会重新加载。":"The page reloads after changing the language.",本地副本未就绪:"Local replica is not ready","请先撤销所有 Edge Room 再断开":"Revoke all Edge Rooms before disconnecting","此设备不驻留在线托管,目前仅支持通过 Edge 发布站点":"This device is not hosted online; sites can only be published through Edge for now","请先连接 Edge":"Connect Edge first","当前 Edge 仅支持 inbox,不支持 Room 托管":"This Edge only supports inbox, not Room hosting","此浏览器通过同步存储桶交换数据、不驻留在线;请在在线主节点管理公开发布":"This browser exchanges data through the sync bucket and is not hosted online; manage public publishing on the online primary node","当前离线,且此浏览器尚未启用本地副本。请先在线打开 Metahub,在设置中开启「离线副本」。":"You are offline and this browser has no local replica yet. Open Metahub online first and enable “Offline replica” in Settings.","离线副本中没有这个页面。":"This page is not in the offline replica.","此资源不是页面,无法离线打开。":"This resource is not a page and cannot be opened offline.","此页面以二进制块存储,无法离线打开。":"This page is stored as a binary blob and cannot be opened offline.","离线加载失败:{msg}":"Offline load failed: {msg}","正在从本地副本加载…":"Loading from the local replica…"};var fe={复制链接:"Copy link",复制内链:"Copy internal link","通过设备分享…":"Share via device…","导出 Markdown":"Export Markdown","导出 CSV":"Export CSV","保存中…":"Saving…",已保存:"Saved",分享:"Share",保存:"Save","保存到同步存储桶失败:{msg}":"Failed to save to the sync bucket: {msg}",保存到同步存储桶:"Save to sync bucket",仍有改动尚未保存到同步存储桶:"Some changes are not yet saved to the sync bucket","保存失败:{msg}":"Save failed: {msg}","按 ID 打开…":"Open by ID…",搜索:"Search",打开设置:"Open settings","折叠 / 展开侧栏":"Collapse / expand sidebar",后退:"Back",前进:"Forward","块 / 代码方式切换":"Toggle blocks / source view",宽屏模式:"Wide mode",版本历史:"Version history","复制 ID":"Copy ID",块方式显示:"Show as blocks",代码方式显示:"Show as source",创建副本:"Duplicate","{title} 副本":"{title} copy","重命名…":"Rename…",重命名文档:"Rename document",删除文档:"Delete document",删除:"Delete",最近动态:"Recent activity",重命名数据库:"Rename database",删除数据库:"Delete database","删除数据库?":"Delete database?","「{name}」及其所有记录将被永久删除。":"“{name}” and all of its records will be permanently deleted.",返回:"Back",展开侧栏:"Expand sidebar",站点配置:"Site settings",配置:"Settings",菜单:"Menu","搜索:“{q}”":"Search: “{q}”",设置:"Settings",站点管理:"Sites",站点:"Sites",发布与分享:"Publish & share",更多:"More","⚡ 离线 — 本地副本模式:可正常查看和编辑全部内容,恢复连接后自动同步":"⚡ Offline — local replica mode: everything stays readable and editable, syncs when the connection returns","⚡ 离线 — 可浏览已缓存的内容,修改会失败;恢复网络后自动恢复":"⚡ Offline — cached content is readable, edits will fail; resumes when the network returns","数据库不存在或已被删除。":"This database does not exist or was deleted.","加载中…":"Loading…",空空如也:"Nothing here yet","从左侧选择一个数据库或文档,或新建一个开始。":"Pick a database or document on the left, or create one to get started.","+ 新建文档":"+ New document",文档:"Documents",记录:"Record","正在从同步存储桶恢复本地副本…":"Restoring the local replica from the sync bucket…",数据表:"Databases",新建文档:"New document",新建数据库:"New database","侧栏:文档":"Sidebar: documents","侧栏:数据表":"Sidebar: databases","侧栏:站点":"Sidebar: sites",移出折叠组:"Unfold",折叠此数据库:"Fold this database",新建子页:"New subpage",移到顶层:"Move to top level",有可用更新:"Update available",收起侧栏:"Collapse sidebar",新建站点:"New site","搜索…":"Search…","已折叠 · {n}":"Folded · {n}",名称:"Name",状态:"Status",待办:"To do",进行中:"In progress",已完成:"Done",优先级:"Priority",高:"High",中:"Medium",低:"Low",截止:"Due",姓名:"Name",公司:"Company",分类:"Category",客户:"Customer",合作:"Partner",潜在:"Lead",邮箱:"Email",空白:"Blank","仅一个「名称」列":"Just a “Name” column",任务:"Tasks","状态·优先级·截止":"Status · Priority · Due",联系人:"Contacts","公司·分类·邮箱":"Company · Category · Email",未命名数据库:"Unnamed database","已创建数据库「{name}」":"Created database “{name}”","创建一个带属性列的结构化数据表。":"Create a structured table with property columns.",取消:"Cancel",创建:"Create","例如:项目、客户、库存…":"e.g. Projects, Customers, Inventory…",图标:"Icon",模板:"Template",导航:"Navigation",新建:"Create",编辑器:"Editor",快速笔记:"Quick Note",命令面板:"Command palette","关闭菜单 / 弹窗":"Close menu / dialog",保存到同步桶:"Save to sync bucket",文内查找:"Find in document",复制当前块:"Duplicate current block",列表缩进:"Indent list item",列表反缩进:"Outdent list item",新建笔记:"New note",上一条笔记:"Previous note",下一条笔记:"Next note",在主窗口中打开:"Open in main window",隐藏小窗:"Hide window",外观:"Appearance",快速小窗:"Quick windows",离线与缓存:"Offline & cache",数据与备份:"Data & backup","设备##page":"Devices",操作审计:"Audit log",站点与发布:"Sites & publishing",快捷键:"Shortcuts",关于:"About",失败:"Failed",重试:"Retry",移除:"Remove","拖动调整宽度,双击复原":"Drag to resize, double-click to reset",确定:"OK",数据库:"Database",未命名记录:"Unnamed record",打开:"Open","输入 ID、前缀或名称":"Enter an ID, prefix or name","搜索页面、输入命令或粘贴 ID":"Search pages, type a command or paste an ID",清空:"Clear","未找到 {ref},可能未同步或已删除":"{ref} not found — it may not be synced yet or was deleted",没有匹配的页面或命令:"No matching pages or commands",暂无可用命令:"No commands available",最近打开:"Recent",命令:"Commands",内容匹配:"Content matches",选择:"Select","可粘贴 ID 或链接":"You can paste an ID or link","文档##kind":"Document","没有找到“{q}”":"Nothing found for “{q}”","换个关键词试试,或用命令面板按标题、ID 直达。":"Try another keyword, or jump by title or ID from the command palette.",打开命令面板:"Open command palette",键盘快捷键:"Keyboard shortcuts","还没有文档,新建一个":"No documents yet — create one","还没有数据表,新建一个":"No databases yet — create one","还没有站点,新建一个":"No sites yet — create one","在全部内容中搜索 “{q}”":"Search all content for “{q}”","已删除「{title}」":"Deleted “{title}”","已删除「{title}」及 {n} 个子页":({title:e,n:t})=>`Deleted “${e}” and ${t} ${l(t,"subpage","subpages")}`,"撤销##undo":"Undo",已删除笔记:"Note deleted","下载中 · {n}":"Downloading · {n}","正在下载本地副本,已接收 {n} 条变更":"Downloading the local replica, {n} changes received",同步中:"Syncing",正在与服务器同步:"Syncing with the server",已更新:"Updated",已同步:"Synced",同步失败:"Sync failed","上次同步失败:{msg} · 本地读写不受影响,点击查看":"Last sync failed: {msg} · local reads and writes are unaffected, click for details",未知错误:"Unknown error",当前文档:"Current document","标注「仅桌面应用」的组合键被浏览器保留,只在桌面应用里可用。":"Shortcuts marked “Desktop app only” are reserved by the browser and only work in the desktop app.",仅桌面应用:"Desktop app only",正在加载:"Loading",链接已复制:"Link copied","内链已复制,可粘贴到任意文档":"Internal link copied — paste it into any document",已是最新:"Already up to date","已复制 ID":"ID copied",已创建副本:"Copy created"};var me={没有匹配的页面:"No matching pages",没有匹配的块:"No matching blocks",上一个:"Previous",下一个:"Next","查找:下一个":"Find: next","查找:上一个":"Find: previous",文件:"File",纯文本:"Plain text",文本:"Text",普通段落:"Plain paragraph","标题 1":"Heading 1",大号标题:"Large heading","标题 2":"Heading 2",中号标题:"Medium heading","标题 3":"Heading 3",小号标题:"Small heading",无序列表:"Bulleted list",项目符号:"Bullet points",有序列表:"Numbered list",编号列表:"Numbered items",待办清单:"To-do list",复选项:"Checkboxes",引用:"Quote",引用块:"Quote block",代码:"Code",代码块:"Code block",表格:"Table",插入表格:"Insert a table",分隔线:"Divider",水平分隔:"Horizontal rule",图片:"Image",上传或拖入图片:"Upload or drop an image",视频:"Video",上传视频文件:"Upload a video file",音频:"Audio",上传音频文件:"Upload an audio file",上传任意文件:"Upload any file","嵌入并渲染 HTML":"Embed and render HTML","保存失败:{msg}":"Save failed: {msg}",已合并其他设备的修改:"Merged changes from another device","加载中…":"Loading…","文档已被其他端修改,自动保存已暂停。":"This document was changed elsewhere; autosave is paused.","载入最新(弃本地改动)":"Load latest (discard local changes)",用本地版本覆盖:"Overwrite with local version","已分享 · 管理分享":"Shared · Manage sharing",已分享:"Shared",未知错误:"Unknown error",同步失败:"Sync failed","上次同步 {when}":"Last synced {when}",实时同步:"Live sync",对齐方式:"Alignment",左对齐:"Align left",居中:"Center",右对齐:"Align right",在左侧插入列:"Insert column left",在右侧插入列:"Insert column right",复制列:"Duplicate column",左移列:"Move column left",右移列:"Move column right",删除列:"Delete column",在上方插入行:"Insert row above",在下方插入行:"Insert row below",复制行:"Duplicate row",上移行:"Move row up",下移行:"Move row down",删除行:"Delete row",行选项:"Row options",列选项:"Column options",复制:"Copy",清空:"Clear",新增列:"Add column",新增行:"Add row",表头:"Header","输入代码…":"Type code…",关闭自动换行:"Turn off line wrap","自动换行(所有代码块)":"Line wrap (all code blocks)",换行:"Wrap","格式化代码 (Shift+Alt+F)":"Format code (Shift+Alt+F)","格式化中…":"Formatting…",已格式化:"Formatted",失败:"Failed",格式化:"Format",复制代码:"Copy code",已复制:"Copied","转换为({n} 行)":({n:e})=>`Turn into (${e} ${l(e,"line","lines")})`,转换为:"Turn into",复制块组:"Duplicate blocks",复制块:"Duplicate block",删除块组:"Delete blocks",删除块:"Delete block",在下方插入:"Insert below","拖动重排 / 点击菜单":"Drag to reorder / click for menu",媒体:"Media",字节未同步到本设备或加载失败:"Not synced to this device or failed to load",重试:"Retry","上传中…":"Uploading…","双击预览 / 标注":"Double-click to preview / annotate",预览:"Preview",下载文件:"Download file",矩形框选:"Rectangle",箭头:"Arrow",文字:"Text","撤销 (⌘Z)":"Undo (⌘Z)","重做 (⇧⌘Z)":"Redo (⇧⌘Z)",取消:"Cancel","保存中…":"Saving…",保存:"Save",关闭:"Close",在文档中查找:"Find in document",无结果:"No results",区分大小写:"Match case",全词匹配:"Whole word",全词:"Word",缩小:"Zoom out",重置:"Reset",放大:"Zoom in","标注 / 编辑":"Annotate / edit",加粗:"Bold",斜体:"Italic",删除线:"Strikethrough",行内代码:"Inline code",链接:"Link",插入链接:"Insert link",链接地址:"URL","{name} 超过 {mb}MB 上限":"{name} exceeds the {mb}MB limit","上传失败:{msg}":"Upload failed: {msg}","已选 {n} 字":"{n} words selected","{n} 字符":"{n} chars","{n} 字":"{n} words","约 {n} 分钟阅读":"~{n} min read","JSON 解析失败:{msg}":"JSON parse failed: {msg}","格式化组件加载失败(离线?):{msg}":"Formatter failed to load (offline?): {msg}",文档不存在或未同步:"Document does not exist or is not synced","⏳ 正在上传 {name}…":"⏳ Uploading {name}…",文档目录:"Table of contents",列1:"Column 1",列2:"Column 2",基础块:"Basic blocks",链接到:"Link to",'输入文本,"/" 唤出命令':'Type text, "/" for commands',"HTML 预览":"HTML preview",编辑源码:"Edit source",源码:"Source"};var ye={上一条:"Previous",下一条:"Next",复制失败:"Copy failed","{n} 列已隐藏":({n:e})=>`${e} ${l(e,"column","columns")} hidden`,"{n} 项":({n:e})=>`${e} ${l(e,"item","items")}`,"{name} 副本":"{name} copy","「{name}」的筛选、排序和隐藏列设置将一并删除,记录不受影响。":"The filters, sorts and hidden columns of “{name}” will be deleted. Records are not affected.",按属性筛选:"Filter by property",包含:"contains",包含全部:"contains all of",包含任一:"contains any of",本月:"This month",标题列始终显示:"The title column is always shown",不包含:"does not contain",不等于:"is not",不是:"is not",不晚于:"is on or before",不为空:"is not empty",不早于:"is on or after",等于:"is","调整或清除筛选条件后会再次显示记录。":"Adjust or clear the filters to see records again.",都不包含:"contains none of",非空:"Filled",复制视图:"Duplicate view","改为「{type}」会清空这一列的 {n} 个单元格,可在版本历史中回滚。":({type:e,n:t})=>`Changing to “${e}” clears ${t} ${l(t,"cell","cells")} in this column. You can roll back from version history.`,"更改属性类型?":"Change property type?",关闭搜索:"Close search","过去 30 天":"Past 30 days","过去 7 天":"Past 7 days",还没有选项:"No options yet","还有 {n} 个隐藏的属性":({n:e})=>`${e} more hidden ${l(e,"property","properties")}`,"换个关键词,或清除筛选条件。":"Try another keyword, or clear the filters.",换行文本:"Wrap text",计数:"Count",计算:"Calculate",满足全部:"Match all",满足任一:"Match any",没有符合条件的记录:"No records match",没有匹配的属性:"No matching properties",平均:"Average",求和:"Sum",全部显示:"Show all",筛选:"Filter",筛选此列:"Filter by this column",删除视图:"Delete view","删除视图?":"Delete view?",是:"is",收起隐藏的属性:"Hide hidden properties",属性:"Properties",数值:"Number",搜索属性:"Search properties",添加排序:"Add sort",晚于:"is after",唯一值:"Unique",为空:"is empty",未勾选:"Unchecked","未来 30 天":"Next 30 days","未来 7 天":"Next 7 days",未选择:"None selected",无:"None","显示 {m} / {n} 条记录":"Showing {m} of {n} records",显示:"Show","新建视图 · 选择布局":"New view · Choose a layout",新建视图:"New view","选择…":"Choose…",移除排序:"Remove sort",移除筛选:"Remove filter",已勾选:"Checked",隐藏:"Hide",隐藏全部:"Hide all",右侧插入列:"Insert column right",在表中搜索:"Search in table","在表中搜索…":"Search in table…",在范围内:"is within",早于:"is before",中位数:"Median",重命名视图:"Rename view",最大:"Max",最小:"Min",左侧插入列:"Insert column left",已撤销:"Undone",已重做:"Redone","{n} 个单元格因类型不匹配未写入":({n:e})=>`${e} ${l(e,"cell","cells")} skipped: value doesn't fit the column type`,设置属性:"Set property","设置属性…":"Set property…",勾选:"Check",取消勾选:"Uncheck",应用:"Apply",编辑单元格:"Edit cell",选中整行:"Select row",选中全部单元格:"Select all cells",复制单元格:"Copy cells",粘贴到单元格:"Paste into cells",清空单元格:"Clear cells",撤销单元格修改:"Undo cell edit",重做:"Redo",清除:"Clear","↑↓ 换日 · ↵ 选定":"↑↓ day · ↵ pick","看板视图需要一个「单选」或「多选」属性来分组。请在表格视图中添加一个单选列后再切换到看板。":"The board needs a select or multi-select property to group by. Add one in the table view, then switch back.",按属性分组:"Group by property","分组:{name}":"Group: {name}",多选分组不支持拖拽改值:"Dragging can't reassign a multi-select group",未分组:"Ungrouped",看板:"Board",日历:"Calendar",时间轴:"Timeline",在浮窗中打开这个数据库的看板:"Open this database's board in a floating window",记录不存在或已被删除:"Record does not exist or has been deleted",移动属性:"Move property",排序:"Sort",清除排序后可拖拽移动:"Clear the sort to drag rows",拖拽移动:"Drag to move",新建属性:"New property",新建记录:"New record","共 {n} 条记录":({n:e})=>`${e} ${l(e,"record","records")}`,"{n} 已选":"{n} selected","删除记录?":"Delete record?","确定删除选中的 {n} 条记录?":({n:e})=>`Delete the ${e} selected ${l(e,"record","records")}?`,"已删除 {n} 条记录":({n:e})=>`Deleted ${e} ${l(e,"record","records")}`,"{n} 个单元格":({n:e})=>`${e} ${l(e,"cell","cells")}`,填充:"Fill",移动记录:"Move record","创建选项失败:{msg}":"Failed to create option: {msg}",搜索或创建选项:"Search or create an option","创建记录失败:{msg}":"Failed to create record: {msg}",搜索或创建记录:"Search or create a record",该属性未设置关联目标:"This property has no relation target",无法加载目标数据表:"Could not load the target database",目标数据表暂无记录:"The target database has no records yet","还有 {n} 条,继续输入过滤":({n:e})=>`${e} more ${l(e,"match","matches")}, keep typing to filter`,"创建文档失败:{msg}":"Failed to create document: {msg}",搜索或创建文档:"Search or create a document","暂无文档,输入标题创建":"No documents yet; type a title to create one","更新属性失败:{msg}":"Failed to update property: {msg}","选项 1":"Option 1","选项 2":"Option 2",已存在同名选项:"An option with that name already exists","重命名选项失败:{msg}":"Failed to rename option: {msg}",至少保留一个选项:"Keep at least one option","删除选项?":"Delete option?","「{name}」将从所有使用它的记录中清除。":"“{name}” will be cleared from every record that uses it.","删除选项失败:{msg}":"Failed to delete option: {msg}",属性类型:"Property type",选项:"Options",确认:"Confirm",重命名选项:"Rename option",删除选项:"Delete option",添加选项:"Add option",关联目标:"Relation target",选择关联的数据表:"Choose the related database","新建属性失败:{msg}":"Failed to create property: {msg}",删除属性:"Delete property","删除属性?":"Delete property?","「{name}」及其所有单元格数据将被移除。":"“{name}” and all of its cell data will be removed.",搜索数据表:"Search databases",没有匹配的数据表:"No matching databases",还没有数据库:"No databases yet",没有匹配的记录:"No matching records",没有匹配的文档:"No matching documents",没有匹配的选项:"No matching options","还没有选项,输入名称创建":"No options yet — type a name to create one",升序:"Ascending",还没有记录:"No records yet","新建一条记录,或从 CLI 导入。":"Create a record, or import from the CLI.","没有符合筛选的动态。":"No activity matches the filters.",降序:"Descending",当前表:"Current table","新建属性 · 选择类型":"New property · Choose a type",排序依据:"Sort by",清除排序:"Clear sort",打开记录:"Open record",复制记录:"Duplicate record",删除记录:"Delete record",返回字段:"Back to fields",字段修改历史:"Field history",在主视图打开:"Open in main view",修复:"Repair",回滚:"Rollback",本设备:"This device","(空)":"(empty)","{n} 块修改":({n:e})=>`${e} ${l(e,"block","blocks")} changed`,"{n} 块删除":({n:e})=>`${e} ${l(e,"block","blocks")} deleted`,元数据:"Metadata",变更:"Changes",源码:"Source","展开 {n} 个未变更块":({n:e})=>`Show ${e} unchanged ${l(e,"block","blocks")}`,"展开 {n} 行未变更内容":({n:e})=>`Show ${e} unchanged ${l(e,"line","lines")}`,"恢复到此版本?":"Restore this version?","标题和正文将恢复到 {when} 的状态。此操作会作为一次新修订记录,任何版本都仍可从历史找回。":"The title and body will be restored to their state at {when}. This is recorded as a new revision; every version stays recoverable from history.",恢复:"Restore",已恢复:"Restored","文档刚被其他端修改,已刷新,请重试":"The document was just changed elsewhere and has been refreshed; please try again",当前:"Current",取消对比基准:"Unpin comparison base","设为对比基准(Shift+点击)":"Pin as comparison base (Shift+click)","{n} 次连续小修改":({n:e})=>`${e} consecutive small ${l(e,"edit","edits")}`,显示修复:"Show repairs","对比基准:{when}":"Comparing against: {when}",清除基准:"Clear base",此修订删除了文档:"This revision deleted the document",初始版本:"Initial version","更早的修订已被存储压缩合并,此处按整篇新增显示":"Earlier revisions were compacted in storage, so this one shows as a whole-document addition","两个版本相同。":"The two versions are identical.","(空文档)":"(empty document)","此修订未改动正文。":"This revision did not change the body.",恢复到此版本:"Restore this version","「{name}」字段历史":"History of “{name}”","该单元格的每一次写入,最新在前。":"Every write to this cell, newest first.","暂无写入记录。":"No writes yet.","(清空)":"(cleared)",已删除:"Deleted",调整排序:"Reordered",全部记录:"All records",全部设备:"All devices",清除筛选:"Clear filters","暂无动态。":"No activity yet.",只看这条记录:"Only this record","(已删字段)":"(deleted field)","…还有 {n} 项":({n:e})=>`…${e} more ${l(e,"item","items")}`,"记录字段将恢复到该修订时的值。此操作会作为一次新修订记录。":"The record's fields will be restored to their values at that revision. This is recorded as a new revision.",修改历史:"Edit history","暂无历史。":"No history yet.",查看此字段完整历史:"View this field's full history",字段:"Field",站点渠道:"Site channel",站点文件:"Site file",文档块:"Document block",附件策略:"Attachment policy","修改##audit":"Edited",段落:"Paragraph",撤销这组改动:"Undo this change set","会以一次新的正向修改还原到改动前的状态,本身也会记入审计,可以再撤销。之后被其他编辑覆盖过的字段会保留,不会被回退。":"Restores the pre-change state as a new forward edit, which is itself audited and can be undone. Fields overwritten by later edits are kept, not rolled back.",撤销改动:"Undo changes","已恢复 {n} 项,保留了 {kept} 项更晚的编辑":({n:e,kept:t})=>`Restored ${e} ${l(e,"item","items")}, kept ${t} later ${l(t,"edit","edits")}`,"已恢复 {n} 项":({n:e})=>`Restored ${e} ${l(e,"item","items")}`,"无需恢复:已是改动前的状态":"Nothing to restore: already in the pre-change state",全部:"All","AI 操作":"AI actions",这台设备:"This device",其他设备:"Other devices","工作区里每一次改动:谁、什么时候、改了什么。AI 通过命令行做的操作会标出来,可以逐条检查并撤销。":"Every change in the workspace: who, when, and what. Actions the AI made through the command line are marked so you can review and undo them one by one.","还没有 AI 操作的记录。":"No AI actions recorded yet.","暂无改动记录。":"No changes recorded yet.",以一次新的正向修改撤销这组改动:"Undo this change set with a new forward edit","撤销##audit":"Undo","早期版本的改动,仅可查看摘要。":"Change from an earlier version; only the summary is available.",",":", ",显示更早的记录:"Show earlier changes","已显示全部 · 只保留最近 90 天,更早的改动已合并且无法撤销":"That's everything · Only the last 90 days are kept; earlier changes were merged and cannot be undone","日历视图需要一个「日期」属性。请在表格视图中添加一个日期列后再切换到日历。":"The calendar view needs a Date property. Add a date column in the table view, then switch to the calendar.",按日期属性:"By date property",上个月:"Previous month",下个月:"Next month","{date} · {n} 条记录":({date:e,n:t})=>`${e} · ${t} ${l(t,"record","records")}`,"+{n} 更多":"+{n} more","时间轴视图需要一个「日期」属性作为开始日期。请在表格视图中添加一个日期列后再切换到时间轴。":"The timeline view needs a Date property as the start date. Add a date column in the table view, then switch to the timeline.",开始日期:"Start date","结束日期(可选)":"End date (optional)","无(里程碑)":"None (milestone)","(里程碑)":"(milestone)",点击空白处排期:"Click an empty spot to schedule","未排期 · 排到今天":"Unscheduled · Schedule for today",数字:"Number",单选:"Select",多选:"Multi-select",复选框:"Checkbox",日期:"Date",关联:"Relation"};var be={"Logo 底色":"Logo background","浅底 Logo":"Light logo","深底 Logo":"Dark logo","Logo 配色":"Logo colors","彩色(默认)":"Color (default)","蓝色单色":"Monochrome blue","应用内标识与浏览器图标即时切换,选择保存在当前设备。安装图标使用默认彩色版。":"App logos and the browser icon update instantly. Your choice is saved on this device. Installed icons use the default color version.",浅色:"Light",始终使用明亮界面:"Always use the light interface",深色:"Dark",始终使用暗色界面:"Always use the dark interface",随操作系统外观自动切换:"Follow the operating system appearance","重命名失败:{msg}":"Rename failed: {msg}",此设备:"This device",重命名本机:"Rename this device",工作区:"Workspace",颜色主题:"Color theme",字数统计:"Word count","在文档右下角显示字数,悬停查看字符数与预计阅读时间。":"Show the word count in the bottom-right corner of a document; hover for characters and estimated reading time.",入口验证成功并已保存:"Entry point verified and saved",已清除设备托管入口:"Device hosting entry point cleared","已连接 Edge;{notes}":"Connected to Edge; {notes}","已连接 Edge":"Connected to Edge","断开 Edge?":"Disconnect Edge?","只移除本机连接,不会删除 Cloudflare Worker 或 D1。存在活动 Room 时会拒绝断开。":"Only removes this device's connection; the Cloudflare Worker and D1 are kept. Disconnecting is refused while Rooms are active.",断开:"Disconnect","已断开 Edge":"Disconnected from Edge",不可达:"Unreachable",需升级:"Upgrade needed",设备托管入口:"Device hosting entry point",公网入口地址:"Public entry URL","已验证 · {scope}":"Verified · {scope}","公网 HTTPS":"Public HTTPS",局域网:"LAN",仅本机:"Local only","填写已由反向代理、隧道或公网 IP 转发到本节点的地址;保存时会访问 /health 并核对节点身份。":"Enter the address a reverse proxy, tunnel or public IP forwards to this node; saving fetches /health and checks the node identity.","验证中…":"Verifying…",验证并保存:"Verify and save","Desktop 内置 sidecar 只监听本机且关闭鉴权,不能直接暴露到公网。需要设备托管时请另行启动带鉴权的":"The desktop's built-in sidecar listens on localhost only with authentication off and cannot be exposed to the internet. For device hosting, run an authenticated",",或使用下方 Edge。":" separately, or use Edge below.","Edge 始终在线托管":"Edge always-on hosting","此设备把数据存放在云端存储桶、不常驻在线,站点需由 Edge 托管。你的设备离线期间,Edge 继续提供最后一次同步的版本。":"This device keeps its data in a cloud bucket and is not always online, so sites must be hosted by Edge. While your device is offline, Edge keeps serving the last synced version.","Edge Room 不依赖设备在线。可一键部署到你的 Cloudflare 账户,或连接已有兼容端点。":"Edge Rooms do not depend on your device being online. Deploy to your Cloudflare account in one click, or connect an existing compatible endpoint.",端点:"Endpoint","升级部署…":"Upgrade deployment…","{status} · 版本 {version} / {expected} · 活动 Room {rooms} · {note}":"{status} · version {version} / {expected} · active Rooms {rooms} · {note}",未知:"unknown","暂无 Room":"No Rooms yet","Room 同步存在异常":"Room sync has errors","Room 最近同步 {ago}":"Rooms last synced {ago}",等待首次同步:"Waiting for the first sync","部署到 Cloudflare":"Deploy to Cloudflare","在你自己的账户创建 Worker + D1(可顺带创建 R2 同步桶),断开时不会自动删除。":"Creates a Worker + D1 in your own account (optionally an R2 sync bucket too); nothing is deleted automatically when you disconnect.","一键部署…":"Deploy…","连接已有 Edge":"Connect an existing Edge","任意兼容 /v1/inbox 协议的端点。":"Any endpoint compatible with the /v1/inbox protocol.","断开 Edge":"Disconnect Edge","只移除本机连接,不会删除 Cloudflare Worker 或 D1;存在活动 Room 时会拒绝断开。":"Only removes this device's connection; the Cloudflare Worker and D1 are kept. Disconnecting is refused while Rooms are active.","已开始在此浏览器保存完整副本,正在下载数据…":"Started keeping a full replica in this browser; downloading data…","停止使用此同步存储桶?":"Stop using this sync bucket?",切回在线模式:"Switch back to online mode","此页面没有在线主节点可回退。停止后会返回「连接同步存储桶」;已下载的数据仍保留在此浏览器里,重新连接后可从断点继续。若要同时删除本地数据,请使用下方「删除此浏览器的副本」。":"This page has no online primary node to fall back to. Stopping returns you to “Connect a sync bucket”; downloaded data stays in this browser and resumes where it left off when you reconnect. To also delete the local data, use “Delete this browser's replica” below.","此浏览器将恢复纯在线模式。已下载的本地数据保留在此浏览器里,重新启用后从断点续传。":"This browser returns to online-only mode. Downloaded local data stays in this browser and resumes where it left off when re-enabled.",停止使用:"Stop using","已停止,请重新连接同步存储桶":"Stopped; reconnect a sync bucket",已切回在线模式:"Switched back to online mode",删除此浏览器的副本:"Delete this browser's replica","将删除此浏览器的副本并返回「连接同步存储桶」。\n① 已同步到存储桶的内容不会丢;\n② 尚未同步的本地编辑会丢失;\n③ 尚未上传的本地附件会丢失;\n④ 建议先确认上方显示“当前版本已确认”。":`Deletes this browser's replica and returns to “Connect a sync bucket”.
1
+ var me={bg:"#ffffff",paper:"#ffffff",surface:"#fafaf9","surface-2":"#f1f1ef",sidebar:"#f7f7f5",fg:"#37352f","fg-soft":"#595750",muted:"#6f6d66",line:"#eae9e6","line-strong":"#d8d7d3",accent:"#3867b2","accent-fg":"#ffffff","accent-soft":"#edf2f9",hover:"rgba(55,53,47,.035)","hover-2":"rgba(55,53,47,.07)","code-bg":"#f6f5f3","selected-bg":"#f7f9fc","selected-border":"#a8b8ce","selected-icon-bg":"#edf2f8","selected-icon-fg":"#4d678b","selected-muted":"#626b78","selected-hover-bg":"#f1f5fa","selected-hover-border":"#8199bb",danger:"#b6423a","danger-fg":"#ffffff","danger-soft":"#f9eeec",success:"#347653",warning:"#866022","warning-soft":"#faf3e5","brand-purple":"#79619d","brand-teal":"#397d76","brand-orange":"#a06436","shadow-sm":"0 1px 2px rgba(35,32,25,.035)","shadow-md":"0 4px 14px rgba(35,32,25,.08)","shadow-lg":"0 16px 48px rgba(35,32,25,.13)","brand-stripe":"linear-gradient(to right,#335cff 0% 25%,#8967ed 25% 50%,#18b7b6 50% 75%,#f28b55 75% 100%)"},K={bg:"#191919",paper:"#191919",surface:"#222222","surface-2":"#2c2c2b",sidebar:"#202020",fg:"#e8e7e4","fg-soft":"#c0beb8",muted:"#aaa8a2",line:"#30302f","line-strong":"#42423f",accent:"#8aaddb","accent-fg":"#172235","accent-soft":"#263242",hover:"rgba(255,255,255,.035)","hover-2":"rgba(255,255,255,.065)","code-bg":"#242423","selected-bg":"#242a32","selected-border":"#56657a","selected-icon-bg":"#2d3745","selected-icon-fg":"#afc5e2","selected-muted":"#b5bfcd","selected-hover-bg":"#2a313b","selected-hover-border":"#7287a3",danger:"#e89890","danger-fg":"#2b1716","danger-soft":"#3b2927",success:"#92bd9b",warning:"#d6b578","warning-soft":"#363024","brand-purple":"#baa6d1","brand-teal":"#8cbbb4","brand-orange":"#d4a37d","shadow-sm":"0 1px 2px rgba(0,0,0,.15)","shadow-md":"0 4px 14px rgba(0,0,0,.25)","shadow-lg":"0 16px 48px rgba(0,0,0,.35)","brand-stripe":"linear-gradient(to right,#335cff 0% 25%,#8967ed 25% 50%,#18b7b6 50% 75%,#f28b55 75% 100%)"},z=(e)=>Object.entries(e).map(([t,n])=>`--${t}:${n};`).join(""),ye=`:root{color-scheme:light;${z(me)}--card:var(--surface-2);--ok:var(--success)}
2
+ @media(prefers-color-scheme:dark){:root:not([data-resolved]){color-scheme:dark;${z(K)}}}
3
+ :root[data-resolved="dark"]{color-scheme:dark;${z(K)}}
4
+ ::selection{background:var(--accent-soft);color:var(--accent)}`,be=`(function(){var t;try{t=localStorage.getItem('mh-theme')}catch(e){}var d=t==='dark'||(t!=='light'&&matchMedia('(prefers-color-scheme: dark)').matches);document.documentElement.dataset.resolved=d?'dark':'light';var m=document.querySelector('meta[name="theme-color"]');if(m)m.content=d?'${K.bg}':'${me.bg}'})();`;function V(e){if(!e||e.length===0)return"zh-CN";for(let t of e){if(!t)continue;if(/^zh\b/i.test(t))return"zh-CN";if(/^en\b/i.test(t))return"en"}return"en"}function Y(e){if(!e)return"zh-CN";return V(e.split(",").map((t)=>t.split(";")[0].trim()))}var d=(e,t,n)=>Number(e)===1?t:n;var we={刚刚:"just now","{n} 分钟前":({n:e})=>`${e} ${d(e,"minute","minutes")} ago`,"{n} 小时前":({n:e})=>`${e} ${d(e,"hour","hours")} ago`,"{n} 天前":({n:e})=>`${e} ${d(e,"day","days")} ago`,今天:"Today",昨天:"Yesterday",本周:"This week","{n} 天后过期":({n:e})=>`Expires in ${e} ${d(e,"day","days")}`,"{n} 小时后过期":({n:e})=>`Expires in ${e} ${d(e,"hour","hours")}`,"{n} 分钟后过期":({n:e})=>`Expires in ${e} ${d(e,"minute","minutes")}`,"快照 {when}":"Snapshot {when}",还没有活动:"No activity yet","{ago}活跃":"Active {ago}","{day}后无活动":"Inactive since {day}",从未:"Never",无标题:"Untitled",语言:"Language",跟随系统:"System","更改语言后页面会重新加载。":"The page reloads after changing the language.",本地副本未就绪:"Local replica is not ready","请先撤销所有 Edge Room 再断开":"Revoke all Edge Rooms before disconnecting","此设备不驻留在线托管,目前仅支持通过 Edge 发布站点":"This device is not hosted online; sites can only be published through Edge for now","请先连接 Edge":"Connect Edge first","当前 Edge 仅支持 inbox,不支持 Room 托管":"This Edge only supports inbox, not Room hosting","此浏览器通过同步存储桶交换数据、不驻留在线;请在在线主节点管理公开发布":"This browser exchanges data through the sync bucket and is not hosted online; manage public publishing on the online primary node","当前离线,且此浏览器尚未启用本地副本。请先在线打开 Metahub,在设置中开启「离线副本」。":"You are offline and this browser has no local replica yet. Open Metahub online first and enable “Offline replica” in Settings.","离线副本中没有这个页面。":"This page is not in the offline replica.","此资源不是页面,无法离线打开。":"This resource is not a page and cannot be opened offline.","此页面以二进制块存储,无法离线打开。":"This page is stored as a binary blob and cannot be opened offline.","离线加载失败:{msg}":"Offline load failed: {msg}","正在从本地副本加载…":"Loading from the local replica…"};var ve={复制链接:"Copy link",复制内链:"Copy internal link","通过设备分享…":"Share via device…","导出 Markdown":"Export Markdown","导出 CSV":"Export CSV","保存中…":"Saving…",已保存:"Saved",分享:"Share",保存:"Save","保存到同步存储桶失败:{msg}":"Failed to save to the sync bucket: {msg}",保存到同步存储桶:"Save to sync bucket",仍有改动尚未保存到同步存储桶:"Some changes are not yet saved to the sync bucket","保存失败:{msg}":"Save failed: {msg}","按 ID 打开…":"Open by ID…",搜索:"Search",打开设置:"Open settings","折叠 / 展开侧栏":"Collapse / expand sidebar",后退:"Back",前进:"Forward","块 / 代码方式切换":"Toggle blocks / source view",宽屏模式:"Wide mode",版本历史:"Version history","复制 ID":"Copy ID",块方式显示:"Show as blocks",代码方式显示:"Show as source",创建副本:"Duplicate","{title} 副本":"{title} copy","重命名…":"Rename…",重命名文档:"Rename document",删除文档:"Delete document",删除:"Delete",最近动态:"Recent activity",重命名数据库:"Rename database",删除数据库:"Delete database","删除数据库?":"Delete database?","「{name}」及其所有记录将被永久删除。":"“{name}” and all of its records will be permanently deleted.",返回:"Back",展开侧栏:"Expand sidebar",站点配置:"Site settings",配置:"Settings",菜单:"Menu","搜索:“{q}”":"Search: “{q}”",设置:"Settings",站点管理:"Sites",站点:"Sites",发布与分享:"Publish & share",更多:"More","⚡ 离线 — 本地副本模式:可正常查看和编辑全部内容,恢复连接后自动同步":"⚡ Offline — local replica mode: everything stays readable and editable, syncs when the connection returns","⚡ 离线 — 可浏览已缓存的内容,修改会失败;恢复网络后自动恢复":"⚡ Offline — cached content is readable, edits will fail; resumes when the network returns","数据库不存在或已被删除。":"This database does not exist or was deleted.","加载中…":"Loading…",空空如也:"Nothing here yet","从左侧选择一个数据库或文档,或新建一个开始。":"Pick a database or document on the left, or create one to get started.","+ 新建文档":"+ New document",文档:"Documents",记录:"Record","正在从同步存储桶恢复本地副本…":"Restoring the local replica from the sync bucket…",数据表:"Databases",新建文档:"New document",新建数据库:"New database","侧栏:文档":"Sidebar: documents","侧栏:数据表":"Sidebar: databases","侧栏:站点":"Sidebar: sites",移出折叠组:"Unfold",折叠此数据库:"Fold this database",新建子页:"New subpage",移到顶层:"Move to top level",有可用更新:"Update available",收起侧栏:"Collapse sidebar",新建站点:"New site","搜索…":"Search…","已折叠 · {n}":"Folded · {n}",名称:"Name",状态:"Status",待办:"To do",进行中:"In progress",已完成:"Done",优先级:"Priority",高:"High",中:"Medium",低:"Low",截止:"Due",姓名:"Name",公司:"Company",分类:"Category",客户:"Customer",合作:"Partner",潜在:"Lead",邮箱:"Email",空白:"Blank","仅一个「名称」列":"Just a “Name” column",任务:"Tasks","状态·优先级·截止":"Status · Priority · Due",联系人:"Contacts","公司·分类·邮箱":"Company · Category · Email",未命名数据库:"Unnamed database","已创建数据库「{name}」":"Created database “{name}”","创建一个带属性列的结构化数据表。":"Create a structured table with property columns.",取消:"Cancel",创建:"Create","例如:项目、客户、库存…":"e.g. Projects, Customers, Inventory…",图标:"Icon",模板:"Template",导航:"Navigation",新建:"Create",编辑器:"Editor",快速笔记:"Quick Note",命令面板:"Command palette","关闭菜单 / 弹窗":"Close menu / dialog",保存到同步桶:"Save to sync bucket",文内查找:"Find in document",复制当前块:"Duplicate current block",列表缩进:"Indent list item",列表反缩进:"Outdent list item",新建笔记:"New note",上一条笔记:"Previous note",下一条笔记:"Next note",在主窗口中打开:"Open in main window",隐藏小窗:"Hide window",外观:"Appearance",快速小窗:"Quick windows",离线与缓存:"Offline & cache",数据与备份:"Data & backup","设备##page":"Devices",操作审计:"Audit log",站点与发布:"Sites & publishing",快捷键:"Shortcuts",关于:"About",失败:"Failed",重试:"Retry",移除:"Remove","拖动调整宽度,双击复原":"Drag to resize, double-click to reset",确定:"OK",数据库:"Database",未命名记录:"Unnamed record",打开:"Open","输入 ID、前缀或名称":"Enter an ID, prefix or name","搜索页面、输入命令或粘贴 ID":"Search pages, type a command or paste an ID",清空:"Clear","未找到 {ref},可能未同步或已删除":"{ref} not found — it may not be synced yet or was deleted",没有匹配的页面或命令:"No matching pages or commands",暂无可用命令:"No commands available",最近打开:"Recent",命令:"Commands",内容匹配:"Content matches",选择:"Select","可粘贴 ID 或链接":"You can paste an ID or link","文档##kind":"Document","没有找到“{q}”":"Nothing found for “{q}”","换个关键词试试,或用命令面板按标题、ID 直达。":"Try another keyword, or jump by title or ID from the command palette.",打开命令面板:"Open command palette",键盘快捷键:"Keyboard shortcuts","还没有文档,新建一个":"No documents yet — create one","还没有数据表,新建一个":"No databases yet — create one","还没有站点,新建一个":"No sites yet — create one","在全部内容中搜索 “{q}”":"Search all content for “{q}”","已删除「{title}」":"Deleted “{title}”","已删除「{title}」及 {n} 个子页":({title:e,n:t})=>`Deleted “${e}” and ${t} ${d(t,"subpage","subpages")}`,"撤销##undo":"Undo",已删除笔记:"Note deleted","下载中 · {n}":"Downloading · {n}","正在下载本地副本,已接收 {n} 条变更":"Downloading the local replica, {n} changes received",同步中:"Syncing",正在与服务器同步:"Syncing with the server",已更新:"Updated",已同步:"Synced",同步失败:"Sync failed","上次同步失败:{msg} · 本地读写不受影响,点击查看":"Last sync failed: {msg} · local reads and writes are unaffected, click for details",未知错误:"Unknown error",当前文档:"Current document","标注「仅桌面应用」的组合键被浏览器保留,只在桌面应用里可用。":"Shortcuts marked “Desktop app only” are reserved by the browser and only work in the desktop app.",仅桌面应用:"Desktop app only",正在加载:"Loading",链接已复制:"Link copied","内链已复制,可粘贴到任意文档":"Internal link copied — paste it into any document",已是最新:"Already up to date","已复制 ID":"ID copied",已创建副本:"Copy created"};var Ee={"在独立窗口中打开":"Open in Separate Window","无法打开独立窗口:{msg}":"Could not open window: {msg}","已保存到本机":"Saved on this device","有未保存修改":"Unsaved changes","需要处理冲突":"Resolve conflict","保存失败":"Save failed","文档尚未保存,草稿已保留。":"Document not saved. Your draft has been retained.","查看草稿":"View draft","文档不存在或已被删除。未保存内容仍可导出。":"This document no longer exists. You can still export your unsaved work.","载入最新版本?":"Load the latest version?","未保存的本地改动将被丢弃。":"Your unsaved local changes will be discarded.","用本地版本覆盖?":"Replace with your local version?","其他位置的修改将被当前草稿替换。":"Changes made elsewhere will be replaced with this draft.","本地草稿":"Local draft","另存草稿":"Save draft as new document","草稿已另存为文档":"Draft saved as a new document",没有匹配的页面:"No matching pages",没有匹配的块:"No matching blocks",上一个:"Previous",下一个:"Next","查找:下一个":"Find: next","查找:上一个":"Find: previous",文件:"File",纯文本:"Plain text",文本:"Text",普通段落:"Plain paragraph","标题 1":"Heading 1",大号标题:"Large heading","标题 2":"Heading 2",中号标题:"Medium heading","标题 3":"Heading 3",小号标题:"Small heading",无序列表:"Bulleted list",项目符号:"Bullet points",有序列表:"Numbered list",编号列表:"Numbered items",待办清单:"To-do list",复选项:"Checkboxes",引用:"Quote",引用块:"Quote block",代码:"Code",代码块:"Code block",表格:"Table",插入表格:"Insert a table",分隔线:"Divider",水平分隔:"Horizontal rule",图片:"Image",上传或拖入图片:"Upload or drop an image",视频:"Video",上传视频文件:"Upload a video file",音频:"Audio",上传音频文件:"Upload an audio file",上传任意文件:"Upload any file","嵌入并渲染 HTML":"Embed and render HTML","保存失败:{msg}":"Save failed: {msg}","加载中…":"Loading…","文档已被其他端修改,自动保存已暂停。":"This document was changed elsewhere; autosave is paused.","载入最新(弃本地改动)":"Load latest (discard local changes)",用本地版本覆盖:"Overwrite with local version","已分享 · 管理分享":"Shared · Manage sharing",已分享:"Shared",未知错误:"Unknown error",同步失败:"Sync failed","上次同步 {when}":"Last synced {when}",实时同步:"Live sync",对齐方式:"Alignment",左对齐:"Align left",居中:"Center",右对齐:"Align right",在左侧插入列:"Insert column left",在右侧插入列:"Insert column right",复制列:"Duplicate column",左移列:"Move column left",右移列:"Move column right",删除列:"Delete column",在上方插入行:"Insert row above",在下方插入行:"Insert row below",复制行:"Duplicate row",上移行:"Move row up",下移行:"Move row down",删除行:"Delete row",行选项:"Row options",列选项:"Column options",复制:"Copy",清空:"Clear",新增列:"Add column",新增行:"Add row",表头:"Header","输入代码…":"Type code…",关闭自动换行:"Turn off line wrap","自动换行(所有代码块)":"Line wrap (all code blocks)",换行:"Wrap","格式化代码 (Shift+Alt+F)":"Format code (Shift+Alt+F)","格式化中…":"Formatting…",已格式化:"Formatted",失败:"Failed",格式化:"Format",复制代码:"Copy code",已复制:"Copied","转换为({n} 行)":({n:e})=>`Turn into (${e} ${d(e,"line","lines")})`,转换为:"Turn into",复制块组:"Duplicate blocks",复制块:"Duplicate block",删除块组:"Delete blocks",删除块:"Delete block",在下方插入:"Insert below","拖动重排 / 点击菜单":"Drag to reorder / click for menu",媒体:"Media",字节未同步到本设备或加载失败:"Not synced to this device or failed to load",重试:"Retry","上传中…":"Uploading…","双击预览 / 标注":"Double-click to preview / annotate",预览:"Preview",下载文件:"Download file",矩形框选:"Rectangle",箭头:"Arrow",文字:"Text","撤销 (⌘Z)":"Undo (⌘Z)","重做 (⇧⌘Z)":"Redo (⇧⌘Z)",取消:"Cancel","保存中…":"Saving…",保存:"Save",关闭:"Close",在文档中查找:"Find in document",无结果:"No results",区分大小写:"Match case",全词匹配:"Whole word",全词:"Word",缩小:"Zoom out",重置:"Reset",放大:"Zoom in","标注 / 编辑":"Annotate / edit",加粗:"Bold",斜体:"Italic",删除线:"Strikethrough",行内代码:"Inline code",链接:"Link",插入链接:"Insert link",链接地址:"URL","{name} 超过 {mb}MB 上限":"{name} exceeds the {mb}MB limit","上传失败:{msg}":"Upload failed: {msg}","已选 {n} 字":"{n} words selected","{n} 字符":"{n} chars","{n} 字":"{n} words","约 {n} 分钟阅读":"~{n} min read","JSON 解析失败:{msg}":"JSON parse failed: {msg}","格式化组件加载失败(离线?):{msg}":"Formatter failed to load (offline?): {msg}",文档不存在或未同步:"Document does not exist or is not synced","⏳ 正在上传 {name}…":"⏳ Uploading {name}…",文档目录:"Table of contents",列1:"Column 1",列2:"Column 2",基础块:"Basic blocks",链接到:"Link to",'输入文本,"/" 唤出命令':'Type text, "/" for commands',"HTML 预览":"HTML preview",编辑源码:"Edit source",源码:"Source"};var ke={上一条:"Previous",下一条:"Next",复制失败:"Copy failed","{n} 列已隐藏":({n:e})=>`${e} ${d(e,"column","columns")} hidden`,"{n} 项":({n:e})=>`${e} ${d(e,"item","items")}`,"{name} 副本":"{name} copy","「{name}」的筛选、排序和隐藏列设置将一并删除,记录不受影响。":"The filters, sorts and hidden columns of “{name}” will be deleted. Records are not affected.",按属性筛选:"Filter by property",包含:"contains",包含全部:"contains all of",包含任一:"contains any of",本月:"This month",标题列始终显示:"The title column is always shown",不包含:"does not contain",不等于:"is not",不是:"is not",不晚于:"is on or before",不为空:"is not empty",不早于:"is on or after",等于:"is","调整或清除筛选条件后会再次显示记录。":"Adjust or clear the filters to see records again.",都不包含:"contains none of",非空:"Filled",复制视图:"Duplicate view","改为「{type}」会清空这一列的 {n} 个单元格,可在版本历史中回滚。":({type:e,n:t})=>`Changing to “${e}” clears ${t} ${d(t,"cell","cells")} in this column. You can roll back from version history.`,"更改属性类型?":"Change property type?",关闭搜索:"Close search","过去 30 天":"Past 30 days","过去 7 天":"Past 7 days",还没有选项:"No options yet","还有 {n} 个隐藏的属性":({n:e})=>`${e} more hidden ${d(e,"property","properties")}`,"换个关键词,或清除筛选条件。":"Try another keyword, or clear the filters.",换行文本:"Wrap text",计数:"Count",计算:"Calculate",满足全部:"Match all",满足任一:"Match any",没有符合条件的记录:"No records match",没有匹配的属性:"No matching properties",平均:"Average",求和:"Sum",全部显示:"Show all",筛选:"Filter",筛选此列:"Filter by this column",删除视图:"Delete view","删除视图?":"Delete view?",是:"is",收起隐藏的属性:"Hide hidden properties",属性:"Properties",数值:"Number",搜索属性:"Search properties",添加排序:"Add sort",晚于:"is after",唯一值:"Unique",为空:"is empty",未勾选:"Unchecked","未来 30 天":"Next 30 days","未来 7 天":"Next 7 days",未选择:"None selected",无:"None","显示 {m} / {n} 条记录":"Showing {m} of {n} records",显示:"Show","新建视图 · 选择布局":"New view · Choose a layout",新建视图:"New view","选择…":"Choose…",移除排序:"Remove sort",移除筛选:"Remove filter",已勾选:"Checked",隐藏:"Hide",隐藏全部:"Hide all",右侧插入列:"Insert column right",在表中搜索:"Search in table","在表中搜索…":"Search in table…",在范围内:"is within",早于:"is before",中位数:"Median",重命名视图:"Rename view",最大:"Max",最小:"Min",左侧插入列:"Insert column left",已撤销:"Undone",已重做:"Redone","{n} 个单元格因类型不匹配未写入":({n:e})=>`${e} ${d(e,"cell","cells")} skipped: value doesn't fit the column type`,设置属性:"Set property","设置属性…":"Set property…",勾选:"Check",取消勾选:"Uncheck",应用:"Apply",编辑单元格:"Edit cell",选中整行:"Select row",选中全部单元格:"Select all cells",复制单元格:"Copy cells",粘贴到单元格:"Paste into cells",清空单元格:"Clear cells",撤销单元格修改:"Undo cell edit",重做:"Redo",清除:"Clear","↑↓ 换日 · ↵ 选定":"↑↓ day · ↵ pick","看板视图需要一个「单选」或「多选」属性来分组。请在表格视图中添加一个单选列后再切换到看板。":"The board needs a select or multi-select property to group by. Add one in the table view, then switch back.",按属性分组:"Group by property","分组:{name}":"Group: {name}",多选分组不支持拖拽改值:"Dragging can't reassign a multi-select group",未分组:"Ungrouped",看板:"Board",日历:"Calendar",时间轴:"Timeline",在浮窗中打开这个数据库的看板:"Open this database's board in a floating window",记录不存在或已被删除:"Record does not exist or has been deleted",移动属性:"Move property",排序:"Sort",清除排序后可拖拽移动:"Clear the sort to drag rows",拖拽移动:"Drag to move",新建属性:"New property",新建记录:"New record","共 {n} 条记录":({n:e})=>`${e} ${d(e,"record","records")}`,"{n} 已选":"{n} selected","删除记录?":"Delete record?","确定删除选中的 {n} 条记录?":({n:e})=>`Delete the ${e} selected ${d(e,"record","records")}?`,"已删除 {n} 条记录":({n:e})=>`Deleted ${e} ${d(e,"record","records")}`,"{n} 个单元格":({n:e})=>`${e} ${d(e,"cell","cells")}`,填充:"Fill",移动记录:"Move record","创建选项失败:{msg}":"Failed to create option: {msg}",搜索或创建选项:"Search or create an option","创建记录失败:{msg}":"Failed to create record: {msg}",搜索或创建记录:"Search or create a record",该属性未设置关联目标:"This property has no relation target",无法加载目标数据表:"Could not load the target database",目标数据表暂无记录:"The target database has no records yet","还有 {n} 条,继续输入过滤":({n:e})=>`${e} more ${d(e,"match","matches")}, keep typing to filter`,"创建文档失败:{msg}":"Failed to create document: {msg}",搜索或创建文档:"Search or create a document","暂无文档,输入标题创建":"No documents yet; type a title to create one","更新属性失败:{msg}":"Failed to update property: {msg}","选项 1":"Option 1","选项 2":"Option 2",已存在同名选项:"An option with that name already exists","重命名选项失败:{msg}":"Failed to rename option: {msg}",至少保留一个选项:"Keep at least one option","删除选项?":"Delete option?","「{name}」将从所有使用它的记录中清除。":"“{name}” will be cleared from every record that uses it.","删除选项失败:{msg}":"Failed to delete option: {msg}",属性类型:"Property type",选项:"Options",确认:"Confirm",重命名选项:"Rename option",删除选项:"Delete option",添加选项:"Add option",关联目标:"Relation target",选择关联的数据表:"Choose the related database","新建属性失败:{msg}":"Failed to create property: {msg}",删除属性:"Delete property","删除属性?":"Delete property?","「{name}」及其所有单元格数据将被移除。":"“{name}” and all of its cell data will be removed.",搜索数据表:"Search databases",没有匹配的数据表:"No matching databases",还没有数据库:"No databases yet",没有匹配的记录:"No matching records",没有匹配的文档:"No matching documents",没有匹配的选项:"No matching options","还没有选项,输入名称创建":"No options yet — type a name to create one",升序:"Ascending",还没有记录:"No records yet","新建一条记录,或从 CLI 导入。":"Create a record, or import from the CLI.","没有符合筛选的动态。":"No activity matches the filters.",降序:"Descending",当前表:"Current table","新建属性 · 选择类型":"New property · Choose a type",排序依据:"Sort by",清除排序:"Clear sort",打开记录:"Open record",复制记录:"Duplicate record",删除记录:"Delete record",返回字段:"Back to fields",字段修改历史:"Field history",在主视图打开:"Open in main view",修复:"Repair",回滚:"Rollback",本设备:"This device","(空)":"(empty)","{n} 块修改":({n:e})=>`${e} ${d(e,"block","blocks")} changed`,"{n} 块删除":({n:e})=>`${e} ${d(e,"block","blocks")} deleted`,元数据:"Metadata",变更:"Changes",源码:"Source","展开 {n} 个未变更块":({n:e})=>`Show ${e} unchanged ${d(e,"block","blocks")}`,"展开 {n} 行未变更内容":({n:e})=>`Show ${e} unchanged ${d(e,"line","lines")}`,"恢复到此版本?":"Restore this version?","标题和正文将恢复到 {when} 的状态。此操作会作为一次新修订记录,任何版本都仍可从历史找回。":"The title and body will be restored to their state at {when}. This is recorded as a new revision; every version stays recoverable from history.",恢复:"Restore",已恢复:"Restored","文档刚被其他端修改,已刷新,请重试":"The document was just changed elsewhere and has been refreshed; please try again",当前:"Current",取消对比基准:"Unpin comparison base","设为对比基准(Shift+点击)":"Pin as comparison base (Shift+click)","{n} 次连续小修改":({n:e})=>`${e} consecutive small ${d(e,"edit","edits")}`,显示修复:"Show repairs","对比基准:{when}":"Comparing against: {when}",清除基准:"Clear base",此修订删除了文档:"This revision deleted the document",初始版本:"Initial version","更早的修订已被存储压缩合并,此处按整篇新增显示":"Earlier revisions were compacted in storage, so this one shows as a whole-document addition","两个版本相同。":"The two versions are identical.","(空文档)":"(empty document)","此修订未改动正文。":"This revision did not change the body.",恢复到此版本:"Restore this version","「{name}」字段历史":"History of “{name}”","该单元格的每一次写入,最新在前。":"Every write to this cell, newest first.","暂无写入记录。":"No writes yet.","(清空)":"(cleared)",已删除:"Deleted",调整排序:"Reordered",全部记录:"All records",全部设备:"All devices",清除筛选:"Clear filters","暂无动态。":"No activity yet.",只看这条记录:"Only this record","(已删字段)":"(deleted field)","…还有 {n} 项":({n:e})=>`…${e} more ${d(e,"item","items")}`,"记录字段将恢复到该修订时的值。此操作会作为一次新修订记录。":"The record's fields will be restored to their values at that revision. This is recorded as a new revision.",修改历史:"Edit history","暂无历史。":"No history yet.",查看此字段完整历史:"View this field's full history",字段:"Field",站点渠道:"Site channel",站点文件:"Site file",文档块:"Document block",附件策略:"Attachment policy","修改##audit":"Edited",段落:"Paragraph",撤销这组改动:"Undo this change set","会以一次新的正向修改还原到改动前的状态,本身也会记入审计,可以再撤销。之后被其他编辑覆盖过的字段会保留,不会被回退。":"Restores the pre-change state as a new forward edit, which is itself audited and can be undone. Fields overwritten by later edits are kept, not rolled back.",撤销改动:"Undo changes","已恢复 {n} 项,保留了 {kept} 项更晚的编辑":({n:e,kept:t})=>`Restored ${e} ${d(e,"item","items")}, kept ${t} later ${d(t,"edit","edits")}`,"已恢复 {n} 项":({n:e})=>`Restored ${e} ${d(e,"item","items")}`,"无需恢复:已是改动前的状态":"Nothing to restore: already in the pre-change state",全部:"All","AI 操作":"AI actions",这台设备:"This device",其他设备:"Other devices","工作区里每一次改动:谁、什么时候、改了什么。AI 通过命令行做的操作会标出来,可以逐条检查并撤销。":"Every change in the workspace: who, when, and what. Actions the AI made through the command line are marked so you can review and undo them one by one.","还没有 AI 操作的记录。":"No AI actions recorded yet.","暂无改动记录。":"No changes recorded yet.",以一次新的正向修改撤销这组改动:"Undo this change set with a new forward edit","撤销##audit":"Undo","早期版本的改动,仅可查看摘要。":"Change from an earlier version; only the summary is available.",",":", ",显示更早的记录:"Show earlier changes","已显示全部 · 只保留最近 90 天,更早的改动已合并且无法撤销":"That's everything · Only the last 90 days are kept; earlier changes were merged and cannot be undone","日历视图需要一个「日期」属性。请在表格视图中添加一个日期列后再切换到日历。":"The calendar view needs a Date property. Add a date column in the table view, then switch to the calendar.",按日期属性:"By date property",上个月:"Previous month",下个月:"Next month","{date} · {n} 条记录":({date:e,n:t})=>`${e} · ${t} ${d(t,"record","records")}`,"+{n} 更多":"+{n} more","时间轴视图需要一个「日期」属性作为开始日期。请在表格视图中添加一个日期列后再切换到时间轴。":"The timeline view needs a Date property as the start date. Add a date column in the table view, then switch to the timeline.",开始日期:"Start date","结束日期(可选)":"End date (optional)","无(里程碑)":"None (milestone)","(里程碑)":"(milestone)",点击空白处排期:"Click an empty spot to schedule","未排期 · 排到今天":"Unscheduled · Schedule for today",数字:"Number",单选:"Select",多选:"Multi-select",复选框:"Checkbox",日期:"Date",关联:"Relation"};var Se={"Logo 底色":"Logo background","浅底 Logo":"Light logo","深底 Logo":"Dark logo","Logo 配色":"Logo colors","彩色(默认)":"Color (default)","蓝色单色":"Monochrome blue","应用内标识与浏览器图标即时切换,选择保存在当前设备。安装图标使用默认彩色版。":"App logos and the browser icon update instantly. Your choice is saved on this device. Installed icons use the default color version.",浅色:"Light",始终使用明亮界面:"Always use the light interface",深色:"Dark",始终使用暗色界面:"Always use the dark interface",随操作系统外观自动切换:"Follow the operating system appearance","重命名失败:{msg}":"Rename failed: {msg}",此设备:"This device",重命名本机:"Rename this device",工作区:"Workspace",颜色主题:"Color theme",字数统计:"Word count","在文档右下角显示字数,悬停查看字符数与预计阅读时间。":"Show the word count in the bottom-right corner of a document; hover for characters and estimated reading time.",入口验证成功并已保存:"Entry point verified and saved",已清除设备托管入口:"Device hosting entry point cleared","已连接 Edge;{notes}":"Connected to Edge; {notes}","已连接 Edge":"Connected to Edge","断开 Edge?":"Disconnect Edge?","只移除本机连接,不会删除 Cloudflare Worker 或 D1。存在活动 Room 时会拒绝断开。":"Only removes this device's connection; the Cloudflare Worker and D1 are kept. Disconnecting is refused while Rooms are active.",断开:"Disconnect","已断开 Edge":"Disconnected from Edge",不可达:"Unreachable",需升级:"Upgrade needed",设备托管入口:"Device hosting entry point",公网入口地址:"Public entry URL","已验证 · {scope}":"Verified · {scope}","公网 HTTPS":"Public HTTPS",局域网:"LAN",仅本机:"Local only","填写已由反向代理、隧道或公网 IP 转发到本节点的地址;保存时会访问 /health 并核对节点身份。":"Enter the address a reverse proxy, tunnel or public IP forwards to this node; saving fetches /health and checks the node identity.","验证中…":"Verifying…",验证并保存:"Verify and save","Desktop 内置 sidecar 只监听本机且关闭鉴权,不能直接暴露到公网。需要设备托管时请另行启动带鉴权的":"The desktop's built-in sidecar listens on localhost only with authentication off and cannot be exposed to the internet. For device hosting, run an authenticated",",或使用下方 Edge。":" separately, or use Edge below.","Edge 始终在线托管":"Edge always-on hosting","此设备把数据存放在云端存储桶、不常驻在线,站点需由 Edge 托管。你的设备离线期间,Edge 继续提供最后一次同步的版本。":"This device keeps its data in a cloud bucket and is not always online, so sites must be hosted by Edge. While your device is offline, Edge keeps serving the last synced version.","Edge Room 不依赖设备在线。可一键部署到你的 Cloudflare 账户,或连接已有兼容端点。":"Edge Rooms do not depend on your device being online. Deploy to your Cloudflare account in one click, or connect an existing compatible endpoint.",端点:"Endpoint","升级部署…":"Upgrade deployment…","{status} · 版本 {version} / {expected} · 活动 Room {rooms} · {note}":"{status} · version {version} / {expected} · active Rooms {rooms} · {note}",未知:"unknown","暂无 Room":"No Rooms yet","Room 同步存在异常":"Room sync has errors","Room 最近同步 {ago}":"Rooms last synced {ago}",等待首次同步:"Waiting for the first sync","部署到 Cloudflare":"Deploy to Cloudflare","在你自己的账户创建 Worker + D1(可顺带创建 R2 同步桶),断开时不会自动删除。":"Creates a Worker + D1 in your own account (optionally an R2 sync bucket too); nothing is deleted automatically when you disconnect.","一键部署…":"Deploy…","连接已有 Edge":"Connect an existing Edge","任意兼容 /v1/inbox 协议的端点。":"Any endpoint compatible with the /v1/inbox protocol.","断开 Edge":"Disconnect Edge","只移除本机连接,不会删除 Cloudflare Worker 或 D1;存在活动 Room 时会拒绝断开。":"Only removes this device's connection; the Cloudflare Worker and D1 are kept. Disconnecting is refused while Rooms are active.","已开始在此浏览器保存完整副本,正在下载数据…":"Started keeping a full replica in this browser; downloading data…","停止使用此同步存储桶?":"Stop using this sync bucket?",切回在线模式:"Switch back to online mode","此页面没有在线主节点可回退。停止后会返回「连接同步存储桶」;已下载的数据仍保留在此浏览器里,重新连接后可从断点继续。若要同时删除本地数据,请使用下方「删除此浏览器的副本」。":"This page has no online primary node to fall back to. Stopping returns you to “Connect a sync bucket”; downloaded data stays in this browser and resumes where it left off when you reconnect. To also delete the local data, use “Delete this browser's replica” below.","此浏览器将恢复纯在线模式。已下载的本地数据保留在此浏览器里,重新启用后从断点续传。":"This browser returns to online-only mode. Downloaded local data stays in this browser and resumes where it left off when re-enabled.",停止使用:"Stop using","已停止,请重新连接同步存储桶":"Stopped; reconnect a sync bucket",已切回在线模式:"Switched back to online mode",删除此浏览器的副本:"Delete this browser's replica","将删除此浏览器的副本并返回「连接同步存储桶」。\n① 已同步到存储桶的内容不会丢;\n② 尚未同步的本地编辑会丢失;\n③ 尚未上传的本地附件会丢失;\n④ 建议先确认上方显示“当前版本已确认”。":`Deletes this browser's replica and returns to “Connect a sync bucket”.
2
5
  1. Content already synced to the bucket is safe;
3
6
  2. Local edits not yet synced will be lost;
4
7
  3. Local attachments not yet uploaded will be lost;
@@ -6,13 +9,13 @@ function z(e){if(!e||e.length===0)return"zh-CN";for(let t of e){if(!t)continue;i
6
9
  1. Synced content stays on the workspace primary node;
7
10
  2. Local edits not yet synced will be lost;
8
11
  3. Local attachments not yet uploaded will be lost;
9
- 4. Run “Sync now” first.`,删除并重置:"Delete and reset","已删除此浏览器的副本,请重新连接同步存储桶":"This browser's replica was deleted; reconnect a sync bucket",已删除此浏览器的副本:"This browser's replica was deleted","重置失败:{msg}":"Reset failed: {msg}","开启后断网也能读写全部内容,改动自动同步。":"Read and write everything even without a network; changes sync automatically.","副本异常(已自动回退在线模式):{msg}":"Replica error (fell back to online mode): {msg}","正在下载数据… 已接收 {n} 条变更":({n:e})=>`Downloading data… ${e} ${l(e,"change","changes")} received`,"离线可用 · 上次同步 {when}(推送 {pushed} / 拉取 {pulled})":"Available offline · last synced {when} (pushed {pushed} / pulled {pulled})","上次同步失败({when}):{msg} — 本地读写不受影响,恢复网络后自动重试":"Last sync failed ({when}): {msg} — local reads and writes are unaffected; retries automatically once the network is back","等待首次同步…":"Waiting for the first sync…","此浏览器暂不支持保存完整副本:{reason}":"This browser cannot keep a full replica: {reason}"," · 节点 {node}":" · node {node}"," · 本地占用 {mb} MB":" · {mb} MB used locally",离线副本:"Offline replica",在此浏览器保存完整副本:"Keep a full replica in this browser",立即同步:"Sync now","把本机改动推送出去,并拉取其他设备的最新内容。":"Push local changes and pull the latest from other devices.","删除此浏览器里的全部本地数据并切回在线模式;未同步的编辑和未上传的附件会丢失。":"Deletes all local data in this browser and switches back to online mode; unsynced edits and unuploaded attachments will be lost.",删除副本:"Delete replica",清理腾空间:"Free up space","将释放约 {bytes}。只清本机缓存,文件已存在云端,用到时自动取回。待上传的内容不会动。":"About {bytes} will be freed. Only the local cache is cleared; the files are in the cloud and are fetched again when needed. Pending uploads are untouched.","已腾出 {bytes}({n} 项)":({bytes:e,n:t})=>`Freed ${e} (${t} ${l(t,"item","items")})`,"本地数据已受保护,系统不会自动清理":"Local data is protected; the system will not clear it automatically",浏览器暂未授予保护:"The browser did not grant protection",本机缓存:"Local cache","只存在这台设备上;清理不影响其他设备,文件用到时自动取回。":"Exists only on this device; clearing does not affect other devices, and files are fetched again when needed.","{n} 项(约 {bytes})还没上传,仅存在这台设备上。建议先「立即同步」再清理——清理不会动这些待上传的内容。":({n:e,bytes:t})=>`${e} ${l(e,"item","items")} (about ${t}) not uploaded yet and only on this device. Run “Sync now” before clearing — clearing never touches pending uploads.`,无需清理:"Nothing to clear",防止浏览器自动清理:"Prevent the browser from clearing automatically","已开启:系统空间紧张时不会自动清掉本地数据。":"On: local data will not be cleared automatically when the system runs low on space.","浏览器未授予保护;系统空间紧张时可能清理本地数据。可稍后重试,或保持定期同步。":"The browser did not grant protection; local data may be cleared when the system runs low on space. Retry later, or keep syncing regularly.","开启后,系统空间紧张时也不会自动清掉本地数据。浏览器授予与否由其自行决定。":"When on, local data is not cleared automatically even when the system runs low on space. Whether the browser grants this is up to the browser.",请求保留本地数据:"Request to keep local data","此浏览器为本工作区共占用约 {usage}(含本地数据库与缓存)。缓存超过 {quota} 时自动清理最久没用、已有备份的,你固定的不动。":"This browser uses about {usage} for this workspace (local database plus cache). When the cache exceeds {quota}, the least recently used items that already have a backup are cleared automatically; pinned items are kept.","无法获取最新版本(网络不可用或 GitHub API 受限)":"Could not fetch the latest version (network unavailable or GitHub API rate-limited)",已是最新版本:"Already up to date","已下载 v{v}":"Downloaded v{v}","从 GitHub Releases 获取核心更新":"Fetch core updates from GitHub Releases",检查更新:"Check for updates","正在检查…":"Checking…","下载中 · {pct}%":"Downloading · {pct}%","下载中…":"Downloading…",下载:"Download",新版本:"New version",可用:"available","下载并更新到 v{v}":"Download and update to v{v}","已就绪,重启后生效":"is ready; restart to apply","重启应用以更新到 v{v}":"Restart the app to update to v{v}",重启:"Restart",检查失败:"Check failed",更新:"Updates",软件更新:"Software update",资源:"Resources","GitHub 仓库":"GitHub repository",源代码与问题反馈:"Source code and issue tracker",更新日志:"Changelog",各版本的发布说明:"Release notes for each version","快捷键已设为 {keys}":"Shortcut set to {keys}","设置失败:{msg}":"Could not save: {msg}",唤起快捷键:"Summon shortcut","点击右侧按钮,然后按下你想用的组合键。":"Click the button on the right, then press the key combination you want.",重置默认:"Reset to default","按下组合键…":"Press a key combination…",默认始终置顶:"Always on top by default","用全局快捷键随时唤起小窗:记录想法,或瞄一眼任务看板。小窗也可从菜单栏图标打开。":"Summon a small window anytime with a global shortcut: jot down an idea, or glance at the task board. The windows can also be opened from the menu bar icon.","小窗浮在其他窗口之上;也可在小窗内用 \uD83D\uDCCC 按钮切换。":"The window floats above other windows; you can also toggle it with the \uD83D\uDCCC button inside the window.",快速看板:"Quick Board","看板小窗浮在其他窗口之上;AI 通过 CLI 更新任务时看板实时刷新。":"The board window floats above other windows; it refreshes live when an AI updates tasks through the CLI.",管理本机缓存:"Manage local cache","已下载到这台设备的图片和大文件。清理只删本机副本,需要时从云端重新取回;桶里的原件始终不动。":"Images and large files downloaded to this device. Clearing only removes the local copy and fetches it again from the cloud when needed; the originals in the bucket are never touched.",管理附件存储:"Manage attachment storage","工作区里的全部图片和大文件,所有设备共用。可清理项已有长期备份、随时能腾空间(用时自动取回);没被任何文档或站点引用的孤儿可彻底删除。":"All images and large files in the workspace, shared by every device. Clearable items already have a long-term backup and can free space anytime (fetched again when used); orphans referenced by no document or site can be deleted permanently.",其他:"Other",待上传:"Pending upload",已固定:"Pinned",孤儿:"Orphan",可清理:"Clearable",保留:"Retained",大小:"Size",类型:"Type",最近使用:"Last used",全部类型:"All types",全部状态:"All statuses",保留中:"Retained","已清理 {n} 项 · 腾出 {bytes}":({n:e,bytes:t})=>`Cleared ${e} ${l(e,"item","items")} · freed ${t}`,没有可清理的项:"Nothing to clear","删除孤儿 blob":"Delete orphan blobs","将从这台设备移除 {n} 个未被任何文档/站点引用的 blob,约 {bytes}。桶里的原件不动,需要时仍可重新下载。":({n:e,bytes:t})=>`Removes ${e} ${l(e,"blob","blobs")} referenced by no document or site from this device, about ${t}. The originals in the bucket are kept and can be downloaded again when needed.`,"将永久删除 {n} 个未被任何文档/站点引用的 blob,约 {bytes}。此操作不可恢复。":({n:e,bytes:t})=>`Permanently deletes ${e} ${l(e,"blob","blobs")} referenced by no document or site, about ${t}. This cannot be undone.`,"已删除 {n} 项 · 腾出 {bytes}":({n:e,bytes:t})=>`Deleted ${e} ${l(e,"item","items")} · freed ${t}`,没有可删除的孤儿:"No orphans to delete",排序依据:"Sort by",按类型:"By type",按状态:"By status","已选 {n} 项 · {bytes}":({n:e,bytes:t})=>`${e} ${l(e,"item","items")} selected · ${t}`,"共 {n} 项 · {bytes}":({n:e,bytes:t})=>`${e} ${l(e,"item","items")} · ${t}`,"清理所选({n})":"Clear selected ({n})",清理所选:"Clear selected","删除孤儿({n})":"Delete orphans ({n})",删除孤儿:"Delete orphans","搜索 hash 或类型…":"Search hash or type…","无法读取:{err}":"Could not read: {err}",没有符合条件的项:"No matching items","这台设备还没有缓存的 blob":"No cached blobs on this device yet",取消固定:"Unpin","固定(不被自动清理)":"Pin (never cleared automatically)",可释放:"Freeable",都备份好了:"All backed up",暂时无需清理:"Nothing to clear for now",未设置长期备份:"No long-term backup set",指定后才能清理:"Set one to enable clearing","检查中…":"Checking…",未检查:"Not checked",检查后才知道:"Check to find out",原件都在这里:"All originals are here",还没有笔记:"No notes yet",删除当前笔记:"Delete current note",笔记列表:"Note list",取消置顶:"Unpin from top",始终置顶:"Always on top","{error}(点击关闭)":"{error} (click to dismiss)","此浏览器不支持 Web Worker。":"This browser does not support Web Workers.","需要 HTTPS(安全上下文)。当前是 http:// 访问,浏览器不开放离线所需的 OPFS 与 Service Worker——给服务器配置 TLS(--tls-cert/--tls-key,或 Caddy / Tailscale Serve 反代;iPhone 需要受信任的证书),或在本机用 localhost 访问。":"HTTPS (a secure context) is required. This page is served over http://, so the browser withholds the OPFS and Service Worker features offline mode needs. Configure TLS on the server (--tls-cert/--tls-key, or a Caddy / Tailscale Serve reverse proxy; iPhone needs a trusted certificate), or open it locally via localhost.","此浏览器不支持 OPFS 本地存储(需要 Safari 17+ / Chrome / Firefox 较新版本)。":"This browser does not support OPFS local storage (requires Safari 17+ or a recent Chrome / Firefox).","阿里云 OSS":"Alibaba Cloud OSS",上海:"Shanghai",北京:"Beijing",广州:"Guangzhou",成都:"Chengdu",南京:"Nanjing",香港:"Hong Kong",新加坡:"Singapore",东京:"Tokyo",杭州:"Hangzhou",深圳:"Shenzhen",美国东部:"US East","美国东部 2":"US East 2",美国西部:"US West","美国西部 2":"US West 2",爱尔兰:"Ireland",法兰克福:"Frankfurt",切换数据库:"Switch database",还没有数据库:"No databases yet",搜索数据库:"Search databases","实时同步中——AI 通过 CLI 的修改会即时出现":"Live — changes an AI makes through the CLI appear instantly",还没有看板:"No board yet","在主窗口创建一个带「单选」属性的数据库,任务进度就会出现在这里。":"Create a database with a “Select” property in the main window and task progress will show up here.",编辑器尚未就绪:"The editor is not ready yet",缺少文件路径:"Missing file path",文件编辑窗口仅在桌面应用内可用:"The file editor window is only available in the desktop app","已导入到 MetaHub":"Imported into MetaHub",预览模式:"Preview",源码模式:"Source","在 MetaHub 中打开":"Open in MetaHub","已导入 ✓":"Imported ✓","导入中…":"Importing…","导入到 MetaHub":"Import into MetaHub",本地副本启动超时:"Timed out starting the local replica",本地副本启动失败:"Failed to start the local replica","此页面没有工作区主节点,不能使用服务器配对;请重新连接同步存储桶。":"This page has no workspace primary node, so server pairing is unavailable; reconnect a sync bucket.","无法获取配对码: {status}":"Could not get a pairing code: {status}","此设备通过同步存储桶交换数据,不支持设备托管发布;请在在线主节点操作":"This device exchanges data through a sync bucket and does not support device-hosted publishing; do this on the online primary node",此设备没有可配置的托管入口:"This device has no configurable hosting entry point",本机:"This device",这台设备上的副本:"The replica on this device",工作区主节点:"Workspace primary node",当前连接的工作区:"The workspace you are connected to",本机工作区:"Local workspace",这台电脑上的工作区:"The workspace on this computer",危险操作:"Danger zone"};var we={"加载失败:{msg}":"Failed to load: {msg}",连接存储桶:"Connect a bucket",所有设备通过它同步:"Every device syncs through it",两台服务器互相实时同步:"Two servers sync each other in real time",已触发同步:"Sync started","同步失败:{msg}":"Sync failed: {msg}","停止使用 {name}":"Stop using {name}","将停止与它同步。桶内数据不受影响,可以随时重新连接。":"Syncing with it will stop. Data in the bucket is untouched, and you can reconnect at any time.",停止使用:"Stop using",停止此浏览器离线同步:"Stop offline sync in this browser","此浏览器将不再直连 {name}。存储桶仍在服务器上;此浏览器在线时照常经服务器同步。":"This browser will no longer connect to {name} directly. The bucket stays on the server, and this browser keeps syncing through the server while online.",停止:"Stop","停止直连 {name}":"Stop direct sync with {name}","将停止与它的直连同步。它上面已同步的数据仍在,无法远程删除。":"Direct sync with it will stop. Data already synced to it stays there and cannot be deleted remotely.",停止直连:"Stop direct sync","实时 · 数据在这里产生":"Live · data is created here",直连:"Direct",已暂停:"Paused","重试中…":"Retrying…",重试:"Retry",地址:"Address","节点 ID":"Node ID",上次成功同步:"Last successful sync",尚未确认:"Unconfirmed","{n} 条改动":({n:e})=>`${e} ${l(e,"change","changes")}`,立即同步:"Sync now",恢复同步:"Resume sync",暂停同步:"Pause sync",这台设备的名字与接入权限在:"This device's name and access are managed on the",设备:"Device","页管理。":"page.","同步失败 · {error}":"Sync failed · {error}","上次同步 {ago}":"Last synced {ago}",还没同步过:"Never synced",本机发布:"Published from this device",此浏览器可离线同步:"Offline sync available in this browser",这台设备:"This device","服务器,此浏览器也保存了一份":"Server, with a copy in this browser",服务器:"Server",服务商:"Provider",存储桶:"Bucket",加密:"Encryption","已加密,上传前在本地完成":"Encrypted locally before upload",未加密:"Not encrypted",凭据保存在:"Credentials stored on",用途:"Roles",上次同步:"Last sync",让此浏览器可离线同步:"Enable offline sync in this browser","更换存储密钥…":"Rotate storage key…","导出恢复码…":"Export recovery code…","让此浏览器可离线同步后,服务器连不上时它也能直接经存储桶同步。":"With offline sync enabled, this browser can sync straight through the bucket whenever the server is unreachable.","更换密钥…":"Rotate key…","先在「离线与缓存」把这台设备设为在本机保存,再连接存储桶。":"First set this device to keep data locally under “Offline & cache”, then connect a bucket.",前往设置:"Open settings",添加设备:"Add device",添加同步目标:"Add sync target",同步目标:"Sync targets","这台设备的云端后端,其他设备通过它加入并同步。":"This device's cloud backend; other devices join and sync through it.","数据保存在这些地方;上传到云端前已在本地加密。":"Your data lives in these places, encrypted locally before it goes to the cloud.","用 Cloudflare 开始":"Start with Cloudflare","登录后创建 R2 存储桶并部署 Edge,向导会带你在控制台生成访问密钥。":"Sign in to create an R2 bucket and deploy Edge; the wizard walks you through generating access keys in the console.","连接 Cloudflare":"Connect Cloudflare",还没有其他同步目标:"No other sync targets yet","连接一个存储桶,所有设备通过它交换加密后的工作区数据。":"Connect a bucket so every device can exchange encrypted workspace data through it.","还没连接存储桶。直连只在两边都在线时同步;存储桶能让离线的设备稍后补齐。":"No bucket connected yet. Direct sync only works while both sides are online; a bucket lets offline devices catch up later.",它是怎么工作的:"How it works","存储桶只做中转:每台设备把改动加密后上传,再拉取别人的——不必同时在线,也不需要公网 IP。直连则是两台服务器互相推送,实时但要求两边同时在线。":"A bucket is just a relay: each device encrypts its changes, uploads them and pulls everyone else's, with no need to be online at the same time or have a public IP. Direct sync pushes between two servers in real time, but both must be online.","这台设备把改动发布到桶,其他设备从桶拉取;新设备扫码加入即可。":"This device publishes changes to the bucket and other devices pull from it; a new device joins by scanning the QR code.","这台设备把整个工作区发布到桶,新设备扫码加入后从桶恢复。":"This device publishes the whole workspace to the bucket; a new device scans the QR code and restores from it.","密钥只保存在服务器;信任的设备重输一次密钥即可直连桶,离线、在外也不中断。":"The key stays on the server only; a trusted device re-enters it once to sync straight through the bucket, even offline or away.","清理腾出 {bytes}":"Clean up to free {bytes}","只删除别处已确认备份的副本,文件不会丢,用到时自动取回。":"Only copies confirmed backed up elsewhere are removed. Nothing is lost; files are fetched again when needed.",清理:"Clean up","已腾出 {bytes}({n} 项)":({bytes:e,n:t})=>`Freed ${e} (${t} ${l(t,"item","items")})`,暂时没有可清理的:"Nothing to clean up right now","附件原件保存在「{place}」,所有设备共用。":"Original attachments are stored on “{place}”, shared by every device.","开启的位置会保留所有附件的完整副本。只要确认这里有备份,其他设备就可以放心清理本地缓存。":"Places switched on keep a full copy of every attachment. Once a backup is confirmed here, other devices can safely clear their local cache.",附件:"Attachments",长期保留副本:"Long-term copies","这里保留全部附件原件,无需清理":"All original attachments are kept here; nothing to clean up",还没指定长期保留的位置:"No long-term location chosen yet",核对备份后才能清理:"Verify backups before cleaning up","都已备份,暂时没有可清理的":"Everything is backed up; nothing to clean up right now","核对中…":"Verifying…","上次核对 {ago}":"Last verified {ago}",还没核对过:"Not verified yet","查看附件…":"View attachments…",云端存储桶:"Cloud bucket",未命名设备:"Unnamed device","还没有长期保留的位置。先指定一处,其他设备才能放心清理本地缓存。":"No long-term location yet. Pick one first so other devices can safely clear their local cache.",清理前需要几处确认:"Confirmations required before cleanup","设备清理缓存前,要有几处长期副本确认持有该附件。":"How many long-term copies must confirm they hold an attachment before a device clears it from its cache.",一处即可:"Any one place",任一长期副本确认即可清理:"Any single long-term copy is enough",每处都要:"Every place",所有长期副本都确认后才清理:"Clean up only after every long-term copy confirms","部分位置连不上,相关文件暂不清理":"Some places are unreachable; their files are kept for now",重新核对:"Verify again","占用超过 {bytes} 时,自动清理最久没用、已有备份的;你固定的不动。":"Above {bytes}, the least recently used files that are backed up are cleaned automatically; pinned files stay.",命令行:"Command line",桌面应用:"Desktop app",浏览器:"Browser",网页:"Web","设备来源刷新失败:{msg}":"Failed to refresh device sources: {msg}","撤销 {name} 的接入":"Revoke access for {name}","将同时撤销它的 {n} 个授权码。它将无法再同步进这个工作区。已同步到它上面的数据仍在,无法远程删除。":({n:e})=>`Its ${e} enrollment codes will be revoked too. It can no longer sync into this workspace. Data already synced to it stays there and cannot be deleted remotely.`,"它将无法再同步进这个工作区。已同步到它上面的数据仍在,无法远程删除。":"It can no longer sync into this workspace. Data already synced to it stays there and cannot be deleted remotely.",撤销接入:"Revoke access",重命名这台设备:"Rename this device","给 {name} 起个名字":"Name {name}",所有设备都会看到这个名字:"Every device will see this name","例如:工作室 iMac":"e.g. Studio iMac","重命名失败:{msg}":"Rename failed: {msg}",工作区所在的服务器:"The server hosting this workspace","经存储桶 {name} 同步":"Syncs through bucket {name}",直连同步:"Direct sync",直连已暂停:"Direct sync paused",通过授权码接入:"Joined with an enrollment code",曾同步过:"Synced before",工作区所在的这台设备:"The device hosting this workspace","经存储桶 {name}":"Through bucket {name}","(在线,租约至 {until})":"(online, lease until {until})",授权码:"Enrollment code","曾写入过工作区,来源未确认":"Has written to the workspace; source unconfirmed",接入方式:"Connection",应用:"App",最近活动:"Last active",重命名:"Rename","同步状态、暂停与停止直连在":"Sync status, pausing and stopping direct sync live in",数据与备份:"Data & backup","。":".","撤销后它不能再同步进来;此前已同步到它上面的数据无法远程删除。":"Once revoked it can no longer sync in; data already synced to it cannot be deleted remotely.","这台设备和其他设备共用同一把存储密钥,无法单独移除。":"This device shares the storage key with the others, so it cannot be removed on its own. After you ",更换密钥:"rotate the key","后它将不能再同步;已下载到它上面的数据无法追回。":" it can no longer sync; data already downloaded to it cannot be recalled.","正在核对已连接的存储桶…":"Checking connected buckets…","只能确认它曾写入过工作区,无法确认它现在经哪条渠道接入,所以这里不提供断开操作。":"We can only confirm it once wrote to the workspace, not which channel it uses now, so there is no disconnect action here.","{n} 台设备同步过这个工作区。":({n:e})=>`${e} ${l(e,"device has","devices have")} synced this workspace.`,"最近 30 天":"Last 30 days","还没有其他设备。":"No other devices yet.",很久没活动:"Inactive for a while","超过 30 天没有同步的设备。如果已经不再使用,可以在这里撤销它们的接入。":"Devices that have not synced for over 30 days. If they are no longer in use, revoke their access here.","数据只在这一处,还没有备份":"Data lives in one place only; no backup yet","已保存在 {n} 处 · {m} 个附件还只在这里":({n:e,m:t})=>`Saved in ${e} ${l(e,"place","places")} · ${t} ${l(t,"attachment is","attachments are")} still only here`,"已保存在 {n} 处 · 有改动尚未同步":({n:e})=>`Saved in ${e} ${l(e,"place","places")} · unsynced changes`,"已保存在 {n} 处 · {m} 处同步失败":({n:e,m:t})=>`Saved in ${e} ${l(e,"place","places")} · ${t} ${l(t,"place","places")} failing to sync`,"已保存在 {n} 处 · 首次同步进行中":({n:e})=>`Saved in ${e} ${l(e,"place","places")} · first sync in progress`,"已保存在 {n} 处 · 备份确认已过期":({n:e})=>`Saved in ${e} ${l(e,"place","places")} · backup confirmation expired`,"已安全保存在 {n} 处 · 上次确认 {ago}":({n:e,ago:t})=>`Safely saved in ${e} ${l(e,"place","places")} · last confirmed ${t}`,"已安全保存在 {n} 处":({n:e})=>`Safely saved in ${e} ${l(e,"place","places")}`,尚未配置任何同步目标:"No sync target configured yet",同步失败:"Sync failed",尚未完成首次同步:"First sync not finished yet",有改动尚未同步:"Unsynced changes",备份确认已过期:"Backup confirmation expired",有附件仅在本机:"Some attachments exist only on this device","{place}:{issue}":"{place}: {issue}","{issue} — {detail}":"{issue} — {detail}",本机:"This device","实时(本机)":"Live (this device)",当前版本已确认:"Current version confirmed",确认已过期:"Confirmation expired",尚未同步:"Not synced yet",已停用:"Disabled",完整副本:"Full replica",工作区同步:"Workspace sync",附件长期保存:"Long-term attachments",发布快照:"Published snapshots",尚未确认当前版本:"Current version not confirmed yet",加密口令必填:"Encryption passphrase is required","加密口令不正确,请输入与其他设备相同的口令。":"Wrong encryption passphrase. Enter the same passphrase as on your other devices.","连接被浏览器拦截:请给存储桶配置 CORS,允许此源的 GET/PUT/HEAD/DELETE。":"The browser blocked the connection. Configure CORS on the bucket to allow GET/PUT/HEAD/DELETE from this origin.","连接失败:{msg}":"Connection failed: {msg}","endpoint、bucket、access key、secret key 必填":"Endpoint, bucket, access key and secret key are required",没有在图片中找到有效的接入二维码:"No valid enrollment QR code found in the image",连接你的存储桶:"Connect your bucket","扫描主设备上的二维码:在桌面端打开 设置 → 同步 → 手机接入。":"Scan the QR code on your primary device: open Settings → Sync → Mobile access on the desktop.",相机权限被拒绝:"Camera access denied","允许访问相机,或改用下方粘贴/手动方式。":"Allow camera access, or paste or fill in the details below instead.",重试授权:"Try again",这不是有效的接入二维码:"This is not a valid enrollment QR code",无效的接入链接或代码:"Invalid enrollment link or code",从图片识别:"Read from image",手动填写:"Enter manually","已读取桶信息,输入加密口令即可同步你的数据。":"Bucket details loaded. Enter the encryption passphrase to sync your data.","已读取桶信息,点击连接即可同步你的数据。":"Bucket details loaded. Click Connect to sync your data.",端到端加密:"End-to-end encrypted",加密口令:"Encryption passphrase",与其他设备相同的口令:"Same passphrase as your other devices","连接中…":"Connecting…",连接并同步:"Connect and sync",重新扫码:"Scan again",改为手动填写:"Enter manually instead","填入 S3 兼容存储桶(R2/COS/MinIO/S3)的连接信息;数据上传前端到端加密。":"Enter the connection details of an S3-compatible bucket (R2/COS/MinIO/S3). Data is end-to-end encrypted before upload.",路径前缀:"Path prefix",改为扫码:"Scan a QR code instead","提示:iPhone 请用 Safari 的「分享 → 添加到主屏」装成 App,离线与持久存储才生效。":"Tip: on iPhone, use Safari's Share → Add to Home Screen to install as an app; offline mode and persistent storage only work that way.","或粘贴接入链接 / 接入码…":"Or paste an enrollment link / code…",当前服务器:"Current server"};var ve={回滚待确认:"Rollback pending confirmation",撤销待确认:"Revoke pending confirmation",发布失败:"Publish failed","正在创建 Edge 渠道":"Creating Edge channel","已上线 · Edge 始终在线":"Live · Edge, always on","已上线 · 设备托管":"Live · device hosted","正在同步 · 设备托管":"Syncing · device hosted","已设公开 · 入口未验证":"Set public · entry point unverified",链接分享中:"Shared by link",链接已过期:"Link expired",私有预览:"Private preview",在线:"Online",同步中:"Syncing",入口未验证:"Entry point unverified",正在创建:"Creating",等待控制设备:"Waiting for controller device",失败:"Failed",已过期:"Expired",任何人:"Anyone",持链接者:"Anyone with the link",已复制:"Copied","{n} 个 api/ 下的文件会被站点数据 API 遮蔽(仍会上传,但无法访问)":({n:e})=>`${e} ${l(e,"file","files")} under api/ will be shadowed by the site data API (uploaded, but unreachable)`,"已上传 {n} 个文件":({n:e})=>`Uploaded ${e} ${l(e,"file","files")}`,"已上传 {ok}/{total} 个,{failed} 个失败":"Uploaded {ok}/{total}, {failed} failed",部分文件上传失败:"Some files failed to upload","{n} 个文件未能上传,可修正后重新拖入(成功的文件无需重传)。":({n:e})=>`${e} ${l(e,"file","files")} could not be uploaded. Fix and drop them in again; successful files need no re-upload.`,关闭:"Close","上传中 {done}/{total}…":"Uploading {done}/{total}…","已关闭 SPA 模式":"SPA mode off","已开启 SPA 模式(无扩展名路径回落到 index.html)":"SPA mode on (extensionless paths fall back to index.html)",配置管理:"Configuration",复制私有预览地址:"Copy private preview URL","发布与分享…":"Publish & share…","关闭 SPA 模式":"Turn off SPA mode","开启 SPA 模式":"Turn on SPA mode","修改标题…":"Edit title…",修改标题:"Edit title",标题:"Title","重命名(slug)…":"Rename (slug)…",删除站点:"Delete site","删除站点?":"Delete site?","「{name}」及其 {n} 个文件将被删除。":({name:e,n:t})=>`“${e}” and its ${t} ${l(t,"file","files")} will be deleted.`,删除:"Delete","加载失败:{msg}":"Failed to load: {msg}","拖入一个目录即可上传为站点(目录名 = 站点名)":"Drop a folder to upload it as a site (folder name = site name)","目录名「{name}」无法转成有效的站点名":"Folder name “{name}” cannot be turned into a valid site name",目录里没有可上传的文件:"The folder has no files to upload","创建站点「{name}」?":"Create site “{name}”?","站点「{name}」不存在。创建它并上传 {n} 个文件?":({name:e,n:t})=>`Site “${e}” does not exist. Create it and upload ${t} ${l(t,"file","files")}?`,创建并上传:"Create and upload",站点管理:"Sites","发布静态站点,生成可分享的访问链接。":"Publish static sites and get shareable links.",新建站点:"New site",正在加载:"Loading",还没有站点:"No sites yet","站点是一组网页文件,可先在本机预览,发布后供他人访问。":"A site is a set of web files. Preview it locally, then publish it for others.",未命名站点:"Untitled site",点击复制公开地址:"Click to copy the public URL","存在多个公开地址;打开详情选择":"Several public URLs; open details to choose","私密链接不在卡片显示;打开详情管理":"Private links are not shown on the card; open details to manage",点击复制私有预览地址:"Click to copy the private preview URL","{n} 个公开地址":({n:e})=>`${e} public ${l(e,"URL","URLs")}`,"{n} 个私密链接":({n:e})=>`${e} private ${l(e,"link","links")}`,"私有预览 · {url}":"Private preview · {url}","{n} 文件":({n:e})=>`${e} ${l(e,"file","files")}`,预览:"Preview","回滚仍未确认:{msg}":"Rollback still unconfirmed: {msg}",目标设备不可达:"target device unreachable",目标设备已确认恢复发布前状态:"Target device confirmed the pre-publish state is restored",重试回滚:"Retry rollback",管理:"Manage",发布:"Publish",更多:"More","共 {n} 个站点":({n:e})=>`${e} ${l(e,"site","sites")}`,"{n} 个文件":({n:e})=>`${e} ${l(e,"file","files")}`,请填写站点名称:"Enter a site name","已创建站点「{name}」":"Created site “{name}”","创建一组网页文件,随你的设备自动同步。":"Create a set of web files that sync with your devices.",取消:"Cancel",创建:"Create","名称(slug)":"Name (slug)","例如:docs、demo、status":"e.g. docs, demo, status","标题(可选)":"Title (optional)",人类可读的标题:"A human-readable title","将通过 /sites/<名称>/ 对外提供。":"Served at /sites/<name>/.","请输入有效的站点名(a-z、0-9、-)":"Enter a valid site name (a-z, 0-9, -)","已重命名为「{name}」":"Renamed to “{name}”","重命名(slug)":"Rename (slug)","slug 决定站点的访问地址 /sites/<slug>/。":"The slug sets the site address: /sites/<slug>/.","保存中…":"Saving…",重命名:"Rename","新名称(slug)":"New name (slug)","将保存为 /sites/{name}/":"Will be saved as /sites/{name}/",无法从输入得到有效的站点名:"No valid site name can be made from the input","注意:重命名后旧链接将失效 — /sites/{name}/ 不再可访问,已分发的地址需要更新。":"Note: old links break after renaming — /sites/{name}/ will no longer work, so distributed URLs must be updated.",站点不存在:"Site not found","「{name}」可能已被删除或重命名。":"“{name}” may have been deleted or renamed.",查看全部站点:"View all sites",站点还没有文件:"This site has no files yet","上传文件后,这里会直接显示站点页面。":"Once files are uploaded, the site page shows here.",去配置:"Configure",还没有文件:"No files yet",打开站点:"Open site",打开:"Open","删除文件?":"Delete file?","「{path}」将从站点「{name}」中删除。":"“{path}” will be removed from site “{name}”.","{n} 个文件 · 创建于 {date}":({n:e,date:t})=>`${e} ${l(e,"file","files")} · created ${t}`,"已发布 · {n} 个渠道":({n:e})=>`Published · ${e} ${l(e,"channel","channels")}`,私有:"Private",访问:"Access",仅你和已配对设备可打开:"Only you and paired devices can open it",复制地址:"Copy URL",访问站点:"Visit site",发布渠道:"Publish channels","还没有发布 — 目前只有你(和已配对设备)能打开上面的预览地址。":"Not published yet — only you (and paired devices) can open the preview URL above.","{host}托管":"{host} hosted",需要口令:"Password required","文件##tab":"Files",上传文件:"Upload files","上传整个目录(保留相对路径),也可以直接拖拽目录进来":"Upload a whole folder (relative paths kept); you can also drag a folder in",上传目录:"Upload folder","上传文件后即可通过站点地址访问。":"Once files are uploaded, the site is reachable at its URL.",文件大小:"File size",复制路径:"Copy path","(无法加载内容)":"(content could not be loaded)","二进制文件 · 在新标签打开查看":"Binary file · open in a new tab to view","Cloudflare 授权失败":"Cloudflare authorization failed","请先确认将创建或更新列出的 Cloudflare 资源":"Confirm first that the listed Cloudflare resources will be created or updated","请先用 Cloudflare 登录":"Sign in with Cloudflare first","请选择要部署到的 Cloudflare 账号":"Choose the Cloudflare account to deploy to","{n} 个站点重新接线失败:{sites}":({n:e,sites:t})=>`${e} ${l(e,"site","sites")} failed to rewire: ${t}`,"、":", ",";":"; ","R2 桶创建失败:{msg}":"R2 bucket creation failed: {msg}","Edge 部署完成;{notes}":"Edge deployed; {notes}","Edge 部署完成":"Edge deployed","请填入 Access Key ID 与 Secret":"Enter the Access Key ID and Secret",请设置加密口令:"Set an encryption passphrase",同步桶已接入:"Sync bucket connected",接入同步桶:"Connect sync bucket","R2 桶 {name} 已创建。最后一步:Cloudflare 不允许应用代为创建 S3 凭据,请到控制台生成后粘贴到这里。":"R2 bucket {name} created. Last step: Cloudflare does not let apps create S3 credentials, so generate them in the dashboard and paste them here.",稍后再接:"Connect later","接入中…":"Connecting…","R2 → 管理 R2 API 令牌":"R2 → Manage R2 API Tokens",",创建一个「对象读和写」令牌(可只勾选 {bucket} 这个桶),把页面显示的 Access Key ID 与 Secret Access Key 粘贴到下面。":", create an “Object Read & Write” token (you can limit it to the {bucket} bucket), then paste the Access Key ID and Secret Access Key shown there below.","加密口令(新工作区在此设置;其他设备加入时需输入同一口令)":"Encryption passphrase (set here for a new workspace; other devices enter the same one to join)","口令保护数据机密性,请牢记;接入完成后建议在「设备与授权 → 导出恢复码」打印备份。":"The passphrase protects your data; remember it. After connecting, print a backup via “Devices → Export recovery code”.","升级 Edge":"Upgrade Edge","部署 Edge":"Deploy Edge","将在你自己的 Cloudflare 账户中创建或更新一个 Worker、一个 D1 数据库和 workers.dev 入口。凭据仅用于本次请求,不会保存。":"Creates or updates a Worker, a D1 database and a workers.dev entry point in your own Cloudflare account. Credentials are used for this request only and never stored.","部署中…":"Deploying…",确认并部署:"Confirm and deploy","Cloudflare 账号":"Cloudflare account","选择账号…":"Choose an account…","{name}({id})":"{name} ({id})","✅ 已登录:{name}":"✅ Signed in: {name}","等待浏览器授权…":"Waiting for browser authorization…","用 Cloudflare 登录":"Sign in with Cloudflare","将打开 Cloudflare 授权页,只申请 Workers 与 D1 的最小权限;令牌仅用于本次部署、不会保存。":"Opens the Cloudflare consent page requesting only the minimal Workers and D1 permissions; the token is used for this deployment only and never stored.","改用 API Token":"Use an API token instead","临时 API Token":"Temporary API token","需要 Workers Scripts Write 与 D1 Write。Cloudflare Token 是账户级凭据,请仅使用最小权限 Token。":"Needs Workers Scripts Write and D1 Write. A Cloudflare token is an account-level credential; use one with minimal permissions.","改用 Cloudflare 登录":"Sign in with Cloudflare instead","Worker 名称":"Worker name","D1 名称":"D1 name","workers.dev 子域(账户尚未设置时创建)":"workers.dev subdomain (created if the account has none)","子域属于整个 Cloudflare 账户;如果账户已有子域,将使用现有值并在完成结果中提示。":"The subdomain belongs to the whole Cloudflare account; if one already exists it is used and noted in the result.","同时创建 R2 同步桶(创建桶后仍需单独生成 S3 凭据)":"Also create an R2 sync bucket (S3 credentials must still be generated separately)","R2 桶名称":"R2 bucket name","创建后还差一步:到 Cloudflare 控制台生成 S3 凭据并粘贴(授权无法代办这一步),本向导会引导你完成。":"One more step after creation: generate S3 credentials in the Cloudflare dashboard and paste them (authorization cannot do this for you); this wizard walks you through it.","检测到未完成部署,当前步骤:{step}。使用相同名称可继续,不会自动删除已创建资源。":"An unfinished deployment was found at step {step}. Keep the same names to continue; created resources are not deleted automatically.","我确认创建或更新上述 Cloudflare 资源;断开 MetaHub 时不会自动删除它们。":"I confirm creating or updating the Cloudflare resources above; they are not deleted automatically when MetaHub disconnects.",密钥必填:"Secret is required",加密口令必填:"Encryption passphrase is required","服务器未提供该桶的完整配置,无法在本设备激活":"The server did not provide the full bucket configuration; cannot activate on this device","已让本机直连存储桶 · 离线、在外也能直接同步":"This device now syncs with the bucket directly · works offline and away","连接被浏览器拦截:请给存储桶配置 CORS,允许此源的 GET/PUT/HEAD/DELETE。":"The browser blocked the connection: configure CORS on the bucket to allow GET/PUT/HEAD/DELETE from this origin.","启用失败:{msg}":"Failed to enable: {msg}",在本设备启用直连:"Enable direct sync on this device","服务器已配置这个存储桶。为保护密钥,服务器不会把它同步到浏览器——再输入一次密钥,这台设备就能与桶直接同步(离线、在外也不中断)。":"The server already has this bucket configured. To protect the secret it is never sent to the browser — enter it once more and this device syncs with the bucket directly (offline and away too).","连接中…":"Connecting…",启用直连:"Enable direct sync",存储桶:"Bucket",服务地址:"Endpoint","访问密钥 ID":"Access key ID",密钥:"Secret",重新输入该桶的密钥:"Re-enter the bucket's secret",加密口令:"Encryption passphrase",与其它设备相同的加密口令:"The same passphrase as your other devices","跨设备共用的那把加密钥——用它解开桶里别人的数据。":"The encryption key shared across devices — it unlocks the data others put in the bucket.","壳地址(手机访问的静态站点域名;留空用当前地址)":"Shell URL (static site domain the phone opens; blank = current address)",复制链接:"Copy link",已复制链接:"Link copied","手机相机扫码打开,输入加密口令即可同步。":"Scan with the phone camera, enter the passphrase, and it syncs.",在另一台电脑的终端运行:"Run in a terminal on the other computer",复制命令:"Copy command",已复制命令:"Command copied",复制接入码:"Copy enrollment code",已复制接入码:"Enrollment code copied","对方运行后会提示输入加密口令。也可在":"It will prompt for the passphrase. Alternatively, in the","向导选「粘贴接入码加入存储」。":"wizard choose “Join storage with an enrollment code”.","{time} 后过期":"Expires in {time}","已过期,请重新生成":"Expired, generate a new one",复制配对码:"Copy pairing code",已复制配对码:"Pairing code copied","生成失败:{msg}":"Failed to generate: {msg}",地址和配对码必填:"Address and pairing code are required","已配对 {url}":"Paired with {url}","配对失败:{msg}":"Pairing failed: {msg}",邀请另一台服务器配对:"Invite another server to pair","生成中…":"Generating…",生成本机配对码:"Generate a pairing code","已有对方的配对码?":"Have the other side's pairing code?",对方服务器地址:"Other server's address",配对码:"Pairing code","对方「生成本机配对码」得到的码":"The code from “Generate a pairing code” on the other side","本机可达地址(可选)":"Reachable address of this server (optional)",留空则仅本机主动同步:"Blank = only this server initiates sync","配对中…":"Pairing…",配对:"Pair",直连另一台服务器:"Connect another server directly","两台服务器互相实时同步。一边生成配对码,另一边填入。":"Two servers sync with each other in real time. Generate a pairing code on one side and enter it on the other.",完成:"Done",在手机上打开本服务器:"Open this server on your phone","服务器地址(手机访问的域名;留空用当前地址)":"Server address (domain the phone opens; blank = current address)","⚠ 含访问令牌,请勿公开分享。":"⚠ Contains the access token; do not share publicly.",手机扫码:"Scan on phone","电脑 / 命令行":"Computer / CLI",手机打开服务器:"Open server on phone",找不到该存储的配置:"Configuration for this storage was not found",添加设备:"Add device","让另一台设备加入同一个工作区——手机扫码,或在电脑上粘贴接入码。":"Bring another device into this workspace — scan on a phone, or paste the enrollment code on a computer.","加载中…":"Loading…","⚠ 接入码含桶访问密钥,请勿公开分享;不含加密口令——新设备需另输口令。":"⚠ The enrollment code contains the bucket access key; do not share publicly. It does not contain the passphrase — the new device enters it separately.","https://<账户ID>.r2.cloudflarestorage.com":"https://<account-id>.r2.cloudflarestorage.com","R2 控制台 → 管理 R2 API 令牌,创建仅限目标桶的对象读写凭据;区域填 auto。价格与额度以 Cloudflare 控制台为准。":"R2 dashboard → Manage R2 API Tokens: create object read/write credentials limited to the target bucket; region is auto. Pricing and quotas as shown in the Cloudflare dashboard.","https://s3.<区域>.amazonaws.com":"https://s3.<region>.amazonaws.com","IAM 用户的访问密钥;区域如 us-east-1。":"An IAM user's access key; region such as us-east-1.","https://minio.你的域名":"https://minio.your-domain","自建 MinIO 的访问地址与 access / secret key。":"Your self-hosted MinIO endpoint and access / secret key.","腾讯云 COS":"Tencent Cloud COS","https://<桶名-APPID>.cos.<区域>.myqcloud.com":"https://<bucket-APPID>.cos.<region>.myqcloud.com","桶名须含 APPID,用虚拟主机风格地址(host 以桶名开头)。":"The bucket name must include the APPID; use a virtual-hosted-style endpoint (host starts with the bucket name).",自定义:"Custom","任何 S3 兼容存储桶。":"Any S3-compatible bucket.","服务地址、桶名称、访问密钥 ID、密钥都要填":"Endpoint, bucket name, access key ID and secret are all required","加密口令必填(或关闭加密)":"Encryption passphrase is required (or turn encryption off)","已连接存储桶 · 服务器开始同步到桶,这台设备也直接同步":"Bucket connected · the server syncs to it, and so does this device directly","已连接存储桶 · 服务器开始同步到桶":"Bucket connected · the server now syncs to it","已连接存储桶 · 这台设备开始同步到桶":"Bucket connected · this device now syncs to it","连接被浏览器拦截:请给存储桶配置 CORS,允许此源的 GET/PUT/HEAD/DELETE(R2 控制台一条规则即可)。":"The browser blocked the connection: configure CORS on the bucket to allow GET/PUT/HEAD/DELETE from this origin (one rule in the R2 dashboard is enough).","添加失败:{msg}":"Failed to add: {msg}",连接存储桶:"Connect a bucket","连接一个 S3 兼容存储桶,所有设备就能同步——不需要公网服务器。新桶自动初始化;已有桶用相同加密口令即可加入。":"Connect an S3-compatible bucket and all devices sync — no public server needed. A new bucket is initialized automatically; join an existing one with the same passphrase.",连接:"Connect","连接后,工作区主节点负责把整库同步到这个桶;这台设备和其他设备都从它同步。":"Once connected, the workspace primary node syncs the whole hub to this bucket; this and other devices sync from it.","连接后,这台设备(本机)把整库同步到这个桶;新设备扫码加入即可一起用。":"Once connected, this device syncs the whole hub to this bucket; new devices join by scanning.",存储服务商:"Storage provider",桶名称:"Bucket name","端到端加密(强烈建议;关闭后文件以明文存放,仅限完全信任的存储)":"End-to-end encryption (strongly recommended; off = files stored in plain text, only for fully trusted storage)","新桶以此创建;其它设备用同一口令加入":"Used to create a new bucket; other devices join with the same passphrase","这是跨设备共用的一把加密钥——记牢它,换设备 / 加入已有桶时都要用它解开数据。":"This is the encryption key shared across devices — remember it; you need it to unlock data on a new device or when joining an existing bucket.",进阶:"Advanced",区域:"Region","大多按服务商默认即可(R2 用 auto)。":"Usually the provider default (auto for R2).",路径前缀:"Path prefix","同一个桶里隔离多个工作区时用;默认 metahub。":"For keeping several workspaces apart in one bucket; default metahub.","新密钥需要同时填 Access Key ID 和 Secret。":"A new key needs both the Access Key ID and the Secret.","两次输入的新口令不一致。":"The two new passphrases do not match.","没有要更改的内容。":"Nothing to change.",轮换完成:"Rotation complete","✓ 存储密钥已更换 — 现在可以到服务商控制台停用旧密钥了。":"✓ Storage key rotated — you can now disable the old key in the provider console.","✓ 口令已更换 — 已配置设备无需重输,只有新加入的设备需要新口令。":"✓ Passphrase changed — configured devices need not re-enter it; only newly joining devices use the new one.","⚠️ 轮换后的首次同步失败:{msg}(凭据已保存,稍后可在同步页重试)":"⚠️ First sync after rotation failed: {msg} (credentials saved; retry later from the sync page)","其他设备用下面的新接入码重新接入(数据与同步进度不受影响)。丢失设备此前已同步的数据无法远程抹除;它只是无法再获取之后的数据。":"Other devices reconnect with the new enrollment code below (data and sync progress are unaffected). Data a lost device already synced cannot be wiped remotely; it just receives nothing further.",复制:"Copy",轮换存储密钥:"Rotate storage keys","适用于「手机丢了 / 密钥泄露 / 想换口令」。":"For a lost phone, a leaked key, or a passphrase change.","轮换中…":"Rotating…",开始轮换:"Start rotation","请先在存储服务商控制台新建一把 Access Key——先不要删除旧的,完成后再停用。 中途任何失败,旧钥匙都还能用;重试即可续接。":"In the storage provider console, create a new Access Key first — do not delete the old one yet; disable it once done. If anything fails midway the old key keeps working; retry to resume.","新 Access Key ID(留空 = 不更换密钥)":"New Access Key ID (blank = keep current key)","新 Secret Access Key":"New Secret Access Key","同时更换加密口令(不会重新加密历史数据;已配置设备无需重输)":"Also change the passphrase (past data is not re-encrypted; configured devices need not re-enter it)",新口令:"New passphrase",再输一次确认:"Confirm new passphrase",导出恢复码:"Export recovery code",打印:"Print","没有加密的存储桶,无需恢复码。":"No encrypted bucket; no recovery code needed.","Metahub 恢复码":"Metahub recovery code","存储桶 {bucket} · 生成于 {date}":"Bucket {bucket} · generated {date}","这串代码等于你全部数据的钥匙。忘记加密口令时,凭它可恢复数据并设置新口令。 请打印或抄写,放在安全的地方;不要保存在会被同步的笔记里。 任何拿到它的人都能读取你的全部数据。":"This code is the key to all your data. If you forget the passphrase, it restores your data and lets you set a new one. Print it or write it down and keep it somewhere safe; do not store it in a note that syncs. Anyone holding it can read all your data."};var Ee={"Edge 托管 · 设备离线仍可访问":"Edge hosted · reachable while offline",修改:"Update",全部:"All",关闭:"Close",分享:"Share","加载失败:{msg}":"Failed to load: {msg}",发布与分享:"Publish & share",同步失败:"Sync failed",复制:"Copy",复制链接:"Copy link",存储桶:"Bucket",已过期:"Expired",打开:"Open",撤销:"Revoke",更多:"More",本机:"This device",正在创建:"Creating",清理:"Clean up",清除筛选:"Clear filters",立即同步:"Sync now",设备:"Device",重试:"Retry",链接已复制:"Link copied",链接已过期:"Link expired","\uD83D\uDD12 有口令":"\uD83D\uDD12 Passcode","{n} 个分享":({n:e})=>`${e} ${l(e,"share","shares")}`,"1 小时":"1 hour","24 小时":"24 hours","30 天":"30 days","7 天":"7 days","Edge 链接":"Edge link","Edge 托管 — 设备离线仍可访问":"Edge hosted — reachable while your devices are offline","Edge 托管 · 设备离线仍可访问(当前端点仅支持 inbox)":"Edge hosted · reachable while offline (this endpoint only supports inbox)","Edge 托管 · 设备离线仍可访问(公开网页暂不支持)":"Edge hosted · reachable while offline (not available for public pages yet)","Edge 托管 · 设备离线仍可访问(请先配置)":"Edge hosted · reachable while offline (set up Edge first)",按来源筛选:"Filter by source",本设备未记录已验证入口:"No verified entry recorded on this device",撤销链接:"Revoke link","撤销已请求,等待 Edge 确认销毁":"Revoke requested, waiting for Edge to confirm teardown","撤销已请求,等待控制设备上线完成清理":"Revoke requested, waiting for the controlling device to come online and clean up","撤销已请求,等待控制设备完成清理":"Revoke requested, waiting for the controlling device to clean up","撤销已提交,但 Edge 尚未确认销毁 Room;本地保留管理记录并会继续重试。":"Revoke submitted, but Edge has not confirmed the Room teardown yet; the local record is kept and retries continue.","撤销意图会同步到控制设备。若控制设备离线,Edge Room 会显示“等待控制设备”,上线后自动销毁。":"The revoke syncs to the controlling device. If it is offline, the Edge Room shows “waiting for controller” and is torn down once it comes online.","撤销意图会同步到托管设备;该设备收到后将停止提供这个渠道。":"The revoke syncs to the hosting device, which stops serving this channel once it receives it.","撤销这个分享?":"Revoke this share?","撤销这个链接?":"Revoke this link?",撤销中:"Revoking","处理中…":"Working…",创建并复制链接:"Create and copy link",创建失败:"Creation failed",创建私密链接:"Create private link","创建中…":"Creating…","此设备通过同步存储桶交换数据、不常驻在线;请改为 Edge 托管的链接":"This device exchanges data through the sync bucket and is not always online; use an Edge-hosted link instead","此设备通过同步存储桶交换数据、不常驻在线。请在在线主节点管理公开发布;这里仍可创建由 Edge 托管的私密分享链接。":"This device exchanges data through the sync bucket and is not always online. Manage public publishing on the online primary node; you can still create Edge-hosted private links here.","此设备通过同步存储桶交换数据、不常驻在线,无法直接托管站点。":"This device exchanges data through the sync bucket and is not always online, so it cannot host a site directly.","从过期链接重新创建 — 口令需重新设置(旧口令无法恢复)。":"Re-creating from an expired link — set the passcode again (the old one cannot be recovered).","存储桶 {name}":"Bucket {name}","存储桶分享是快照链接:内容是导出时的版本,之后不会随数据变化;「更新内容并续期」才会覆盖快照。":"A bucket share is a snapshot link: the content is the version at export time and does not follow later changes; “Update content and renew” overwrites the snapshot.","存储桶分享是一份快照;链接需续签后生成":"A bucket share is a snapshot; the link is generated on renewal",打开链接:"Open link",打开原表格:"Open source table",打开原对象:"Open source item",打开原文档:"Open source document",打开站点配置:"Open site settings","当前 Edge 端点仅支持数据收件(inbox),不能托管站点":"This Edge endpoint only supports the data inbox and cannot host sites","当前 Edge 端点仅支持数据收件(inbox),不能托管站点;重新部署官方 Edge Worker 即可启用。":"This Edge endpoint only supports the data inbox and cannot host sites; redeploy the official Edge Worker to enable it.","当前访问渠道({n})":"Current access channels ({n})","当前设备还没有配置公网或局域网入口,访客将无法访问。":"This device has no public or LAN entry configured yet, so visitors cannot reach it.",等待生成地址:"Waiting for an address","(等待首次同步)":"(waiting for first sync)",点击复制:"Click to copy",读:"Read",发布公开网页:"Publish public page","发布失败,目标设备的回滚尚未确认;在确认前它可能仍可公开访问。":"Publishing failed and the target device has not confirmed the rollback; it may still be publicly reachable until it does.",发布未返回访问地址:"Publishing returned no address","发布与分享:{title}":"Publish & share: {title}","发布中…":"Publishing…","分享:{title}":"Share: {title}",分享列表加载失败:"Failed to load shares","改用 Edge 托管":"Use Edge hosting instead","高级:数据授权(让页面读写数据)…":"Advanced: data grants (let the page read and write data)…","高级:允许修改已有行…":"Advanced: allow editing existing rows…",更改:"Change",更新快照并续期:"Update snapshot and renew","更新快照内容并续期?":"Update snapshot content and renew?",更新内容并续期:"Update content and renew",公开:"Public",公开地址已复制:"Public address copied",还没有分享出去的内容:"Nothing shared yet","还没有分享这个对象。":"This item has not been shared yet.","还没有连接 Edge。部署到你自己的 Cloudflare 后,站点始终在线,你的设备关机也能访问。":"Edge is not connected yet. Deploy to your own Cloudflare and the site stays online even when your devices are off.","还没有任何访问渠道 — 在下方创建。":"No access channels yet — create one below.","换个关键词,或清除当前的状态与来源筛选。":"Try another keyword, or clear the status and source filters.","记录将被删除。设备与 Edge 分享如需再次公开,可以重新分享。":"The records will be deleted. Device and Edge shares can be shared again later.","将用当前最新数据覆盖这份快照,接收者会看到更新后的版本;同时生成新的访问链接。":"The snapshot will be overwritten with the latest data, recipients will see the updated version, and a new access link is generated.",可编辑:"Editable","可编辑(仅服务器)":"Editable (server only)",可选:"Optional",口令:"Passcode",口令保护:"Passcode protected",快过期:"Expiring",快照链接:"Snapshot link",来源:"Source",了解此设备的同步方式:"How this device syncs","链接立即失效,接收者将无法再打开。":"The link stops working immediately and recipients can no longer open it.","链接无法被猜到;可加口令和有效期。":"The link cannot be guessed; add a passcode and expiry if you like.","链接已过期 — 过期链接无法访问,可重新创建":"Link expired — expired links cannot be opened; you can re-create it",链接已失效:"Link no longer valid","链接有效至 {when}":"Link valid until {when}","(没有可授权的数据库)":"(no databases to grant)","没有可托管站点的设备。":"No device can host the site.",没有匹配的分享:"No matching shares","内容不变,重新生成访问链接":"Content unchanged, generates a new access link","内容生成于 {when}":"Content generated {when}","内容同步于 {ago}":"Content synced {ago}","前往设置 → 站点与发布":"Go to Settings → Sites & publishing","清理 {n} 个已过期分享?":({n:e})=>`Clean up ${e} expired ${l(e,"share","shares")}?`,清理全部已过期:"Clean up all expired","清理中…":"Cleaning up…","请先在“设置 → 站点与发布”连接或部署 Edge":"Connect or deploy Edge in “Settings → Sites & publishing” first","请先在“设置 → 站点与发布”配置当前设备的公网或局域网入口":"Configure this device's public or LAN entry in “Settings → Sites & publishing” first",渠道已撤销:"Channel revoked",全部来源:"All sources",权限:"Permission",确认公开发布:"Confirm public publish","确认公开发布到设备?":"Publish publicly on this device?","任何人将无法再通过公开地址访问这个站点;已有的分享链接不受影响。浏览器或 CDN 缓存可能仍保留数分钟。":"Nobody will be able to reach this site through the public address anymore; existing share links are unaffected. Browser or CDN caches may linger for a few minutes.",任何人可访问:"Anyone can access","任何人无需登录即可访问公开地址。":"Anyone can open the public address without signing in.","任何人无需登录即可通过这个地址访问。公开权限会同步,但只有选定的托管设备提供此渠道;改回私有后,浏览器或 CDN 缓存仍可能保留数分钟。数据授权:{n} 个数据库。设备必须保持在线。":({n:e})=>`Anyone can open this address without signing in. Public permissions sync, but only the selected hosting device serves this channel; after switching back to private, browser or CDN caches may linger for a few minutes. Data grants: ${e} ${l(e,"database","databases")}. The device must stay online.`,"设备 {name}":"Device {name}",设备链接:"Device link",设备在线托管:"Hosted on a device","设备在线托管 — {device},需保持在线":"Hosted on a device — {device}, must stay online","设备在线托管(此设备无法托管)":"Hosted on a device (this device cannot host)",收起高级选项:"Hide advanced options",收起数据授权:"Hide data grants","数据授权:已开启 {n} 张表…":({n:e})=>`Data grants: ${e} ${l(e,"table","tables")} enabled…`,"搜索标题 / 链接…":"Search titles / links…","所有对外公开的链接都在这里:谁能看、看到哪个版本、还能看多久。":"Every outbound link lives here: who can see it, which version, and for how long.",停止公开:"Stop public access",停止公开访问:"Stop public access","停止公开访问?":"Stop public access?","停止这个公开渠道?":"Stop this public channel?","(同步失败)":"(sync failed)","同步中…":"Syncing…","托管设备必须保持在线。渠道和撤销意图会随工作区同步,其他设备不会自动获得这个公开地址。":"The hosting device must stay online. Channels and revokes sync with the workspace; other devices do not automatically get this public address.",托管位置:"Hosting","无法读取现有公开权限:{msg}":"Could not read the current public permissions: {msg}","现有公开权限尚未加载。":"Current public permissions have not loaded.","现有公开权限尚未加载,请先点「重新加载权限」。":"Current public permissions have not loaded; click “Reload permissions” first.",新建分享:"New share",新增:"Create",新增访问方式:"Add access",续期:"Renew","续期(仅链接)":"Renew (link only)",延长有效期:"Extend expiry","页面内的数据接口({api})默认关闭。若这个站点需要访客读取或提交数据,在这里按表开启。":"The page's data API ({api}) is off by default. If this site needs visitors to read or submit data, enable it per table here.","已按过期链接预填上方表单。":"The form above was pre-filled from the expired link.","已按过期链接预填上方表单;原口令无法恢复,请重新设置。":"The form above was pre-filled from the expired link; the old passcode cannot be recovered, set a new one.",已撤销:"Revoked",已更新快照内容并复制新链接:"Snapshot updated and the new link copied","已配对的托管设备当前未确认可用。请先同步成功,或配置当前主节点的访问入口。":"No paired hosting device is confirmed available. Sync successfully first, or configure this primary node's entry.","已清理 {done} 个,{fail} 个失败:{err}":"Cleaned up {done}, {fail} failed: {err}","已清理 {n} 个分享":({n:e})=>`Cleaned up ${e} ${l(e,"share","shares")}`,已提交撤销:"Revoke submitted","已提交停止公开,等待托管设备收到同步":"Stop request submitted, waiting for the hosting device to sync","(已停用)":"(disabled)",已停止公开访问:"Public access stopped","已通过「{source}」创建私密链接,地址已复制":"Private link created via “{source}”, address copied","已通过「{source}」上线,地址已复制":"Now live via “{source}”, address copied","已有分享({n})":"Existing shares ({n})","已重新生成访问链接并复制(内容不变)":"Access link regenerated and copied (content unchanged)","已重新生成访问链接并复制(内容为 {when} 的快照)":"Access link regenerated and copied (snapshot from {when})","已重新生成访问链接,新链接已复制(内容不变)":"Access link regenerated, new link copied (content unchanged)",永不过期:"Never expires",永久:"Permanent",用当前最新内容覆盖接收者看到的版本:"Overwrite what recipients see with the latest content",有效:"Active",有效期:"Expires","在文档、表格或站点里点「分享」即可创建链接。":"Click “Share” in a document, table or site to create a link.","站点页面与管理界面同源运行:站点内的脚本可以以你的身份读写整个工作区。只发布你信任的代码。":"Site pages run on the same origin as the admin UI: scripts inside the site can read and write the whole workspace as you. Only publish code you trust.","站点已上线,地址已复制":"Site is live, address copied",这类对象无法在这里重新分享:"This kind of item cannot be re-shared here",正在加载分享列表:"Loading shares","正在同步到目标设备;确认地址可访问后才会视为发布成功":"Syncing to the target device; publishing counts as done once the address is confirmed reachable",只读:"Read-only","至 {when}":"Until {when}",重试撤销:"Retry revoke",重新创建链接:"Re-create link",重新分享:"Share again",重新加载权限:"Reload permissions",准备中:"Preparing"};var ke={...ge,...fe,...me,...ye,...be,...we,...ve,...Ee};function St(e,t){if(!t)return e;return e.replace(/\{(\w+)\}/g,(n,r)=>(r in t)?String(t[r]):n)}var Rt="##";function Se(e,t,n){let r=e==="en"?ke[t]:void 0;if(typeof r==="function")return r(n??{});let o=t.indexOf(Rt);return St(r??(o<0?t:t.slice(0,o)),n)}function V(e,t,n,r){let o=n.get("id"),i=n.get("db"),s=r??{},a=(c)=>c!=null?Number(c):void 0;switch(`${e} ${t}`){case"GET /api/databases":return{op:"listDatabases",args:[]};case"POST /api/databases":return{op:"createDatabase",args:[s]};case"PATCH /api/database":return o?{op:"updateDatabase",args:[o,s]}:null;case"POST /api/database/duplicate":return o?{op:"duplicateDatabase",args:[o,s]}:null;case"DELETE /api/database":return o?{op:"deleteDatabase",args:[o]}:null;case"GET /api/database/activity":return i?{op:"listDatabaseActivity",args:[i,a(n.get("limit"))]}:null;case"POST /api/repair/clock":return{op:"repairClock",args:[]};case"GET /api/audit":return{op:"listAuditEntries",args:[{limit:a(n.get("limit")),before:n.get("before")??void 0,actor:n.get("actor")??void 0}]};case"GET /api/audit/entry":{let c=n.get("txn");return c?{op:"auditEntryDetail",args:[c]}:null}case"POST /api/audit/revert":{let c=n.get("txn");return c?{op:"revertChangeGroup",args:[c]}:null}case"GET /api/properties":return i?{op:"listProperties",args:[i]}:null;case"POST /api/properties":{let{db:c,...p}=s;return c?{op:"addProperty",args:[c,p]}:null}case"PATCH /api/property":return o?{op:"updateProperty",args:[o,s]}:null;case"PATCH /api/property/width":return o?{op:"setPropertyWidth",args:[o,s.width]}:null;case"POST /api/property/option/rename":{let{from:c,to:p}=s;return o?{op:"renameSelectOption",args:[o,c,p]}:null}case"POST /api/property/option/remove":return o?{op:"removeSelectOption",args:[o,s.name]}:null;case"DELETE /api/property":return o?{op:"removeProperty",args:[o]}:null;case"GET /api/property/history":return o?{op:"listPropertyRevisions",args:[o]}:null;case"POST /api/property/revert":return o?{op:"revertProperty",args:[o,s.to]}:null;case"GET /api/records":return i?{op:"listRecords",args:[i,{sort:n.get("sort")??void 0,limit:a(n.get("limit"))}]}:null;case"POST /api/records":return i?{op:"createRecord",args:[i,s]}:null;case"GET /api/record":return o?{op:"getRecord",args:[o]}:null;case"PATCH /api/record":return o?{op:"updateRecord",args:[o,s]}:null;case"PATCH /api/record/order":{let{target:c,where:p}=s;return o&&c?{op:"moveRecord",args:[o,c,p]}:null}case"GET /api/record/history":return o?{op:"listRecordRevisions",args:[o]}:null;case"GET /api/record/at":return o?{op:"recordAtVersion",args:[o,n.get("version")]}:null;case"GET /api/record/field-history":return o?{op:"recordFieldHistory",args:[o,n.get("prop")]}:null;case"POST /api/record/revert":return o?{op:"revertRecord",args:[o,s.to]}:null;case"DELETE /api/record":return o?{op:"deleteRecord",args:[o]}:null;case"GET /api/documents":return{op:"listDocuments",args:[{database_id:n.get("db")??void 0,parent_id:n.get("parent")??void 0}]};case"POST /api/documents":return{op:"createDocument",args:[s]};case"GET /api/document":return o?{op:"getDocument",args:[o]}:null;case"PATCH /api/document":{let{if_match:c,...p}=s;return o?{op:"updateDocument",args:[o,p,c]}:null}case"POST /api/document/duplicate":return o?{op:"duplicateDocument",args:[o,s]}:null;case"PATCH /api/document/move":{let{target:c,where:p}=s;return o&&c?{op:"moveDocument",args:[o,c,p]}:null}case"GET /api/document/history":return o?{op:"listDocumentRevisions",args:[o]}:null;case"GET /api/document/at":return o?{op:"documentAtVersion",args:[o,n.get("version")]}:null;case"POST /api/document/revert":{let{to:c,if_match:p}=s;return o&&c?{op:"revertDocument",args:[o,c,p]}:null}case"DELETE /api/document":return o?{op:"deleteDocument",args:[o]}:null;case"GET /api/nodes":return{op:"nodes",args:[]};case"GET /api/sync/health":return{op:"dataMap",args:[]};case"GET /api/search":{let c=n.get("q");return c?{op:"search",args:[c,a(n.get("limit"))]}:null}case"GET /api/resolve":{let c=n.get("ref");return c?{op:"resolve",args:[c,a(n.get("limit"))]}:null}case"GET /api/sites":return{op:"listSites",args:[]};case"GET /api/site/files":{let c=n.get("site");return c?{op:"listSiteFiles",args:[c]}:null}case"POST /api/sites":return{op:"createSite",args:[s]};case"PATCH /api/site":return o?{op:"updateSite",args:[o,s]}:null;case"DELETE /api/site":return o?{op:"deleteSite",args:[o]}:null;case"GET /api/site/grants":return o?{op:"getSiteGrants",args:[o]}:null;case"PUT /api/site/grants":return o?{op:"setSiteGrants",args:[o,s]}:null;case"GET /api/site-hosting":return{op:"siteHosting",args:[]};case"PATCH /api/site/channel":{let c=s.id;return c?{op:"revokeSiteChannel",args:[c]}:null}case"DELETE /api/site/file":{let c=n.get("site"),p=n.get("path");return c&&p?{op:"deleteSiteFile",args:[c,p]}:null}case"GET /api/shares":return{op:"listLocalShares",args:[n.get("target")??void 0]};case"POST /api/share":return{op:"createLocalShare",args:[s]};case"DELETE /api/share":case"DELETE /api/share/managed":return n.get("slug")?{op:"revokeLocalShare",args:[n.get("slug")]}:null;default:return null}}function Dt(e,t){if(e===null)return"unknown";if(e.status>=500)return"unknown";if(e.ok)return t?.ok===!0?"server":"none";return"none"}async function Re(){let e=null,t=null;try{e=await fetch("/health",{cache:"no-store"}),t=e.ok?await e.json().catch(()=>null):null}catch{e=null}return Dt(e,t)}var _t=Math.floor(167772160);function Ct(e){if(e instanceof ArrayBuffer)return e;let t=new Uint8Array(e.byteLength);return t.set(e),t.buffer}async function xt(e){let t=await crypto.subtle.digest("SHA-256",Ct(e));return[...new Uint8Array(t)].map((n)=>n.toString(16).padStart(2,"0")).join("")}async function Y(e,t){return(await xt(e)).slice(0,t.length)===t}var Tt={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",avif:"image/avif",svg:"image/svg+xml",pdf:"application/pdf"};function De(e){let t=e.lastIndexOf("."),n=t>=0?e.slice(t+1).toLowerCase():"";return Tt[n]??"application/octet-stream"}var U=null;function At(){if(U)return U;return U=new Promise((e,t)=>{let n=indexedDB.open("mh-blobs",1);n.onupgradeneeded=()=>{let r=n.result;if(!r.objectStoreNames.contains("spool"))r.createObjectStore("spool",{keyPath:"hash"});if(!r.objectStoreNames.contains("meta"))r.createObjectStore("meta",{keyPath:"hash"})},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)}),U}function C(e,t,n){return At().then((r)=>new Promise((o,i)=>{let s=n(r.transaction(e,t).objectStore(e));s.onsuccess=()=>o(s.result),s.onerror=()=>i(s.error)}))}var q=(e)=>`/blob/${e}`;async function Ot(e,t,n){let r=await C("meta","readonly",(i)=>i.get(e)),o={hash:e,size:t,accessed:Date.now(),pinned:r?.pinned??0,content_type:n??r?.content_type};await C("meta","readwrite",(i)=>i.put(o))}async function Lt(e){let t=await C("meta","readonly",(n)=>n.get(e));if(!t)return;t.accessed=Date.now(),await C("meta","readwrite",(n)=>n.put(t))}function Nt(e){return e.trim().toLowerCase().startsWith("text/html")}async function _e(e){let t=await caches.open("mh-blob-v1"),n=await t.match(q(e));if(!n)return;if(Nt(n.headers.get("content-type")??"")){await t.delete(q(e)),await C("meta","readwrite",(r)=>r.delete(e)).catch(()=>{});return}return Lt(e),n}async function J(e,t,n){await(await caches.open("mh-blob-v1")).put(q(e),new Response(t,{headers:{"content-type":n,"cache-control":"public, max-age=31536000, immutable"}})),await Ot(e,t.byteLength,n),await Pt()}async function Pt(){let e=await C("meta","readonly",(o)=>o.getAll())??[],t=e.reduce((o,i)=>o+i.size,0);if(t<=209715200)return;let n=await caches.open("mh-blob-v1"),r=e.filter((o)=>!o.pinned).sort((o,i)=>o.accessed-i.accessed);for(let o of r){if(t<=_t)break;await n.delete(q(o.hash)),await C("meta","readwrite",(i)=>i.delete(o.hash)),t-=o.size}}var Ce='<script src="/mh-runtime.js"></script>';function xe(e){let t=e.toLowerCase().indexOf("<head>");if(t>=0)return e.slice(0,t+6)+Ce+e.slice(t+6);return Ce+e}class u extends Error{code;i18n;constructor(e,t,n){super(t);this.code=e;this.i18n=n;this.name="MhError"}}function $t(e,t){return t?e.replace(/\{(\w+)\}/g,(n,r)=>(r in t)?String(t[r]):n):e}function Te(e,t,n){return new u(e,$t(t,n),{key:t,params:n})}function Q(e,t){let n=e.transaction(t);return(n.immediate??n)()}function N(e=6){let t=crypto.getRandomValues(new Uint8Array(e)),n="";for(let r of t)n+="0123456789abcdefghijklmnopqrstuvwxyz".charAt(r%36);return n}function X(e,t="item"){return e.normalize("NFKD").replace(/[^\x20-\x7E]/g,"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,32).replace(/-+$/g,"")||t}var Mt=new Set(["db","prop","rec","doc","blk","site","sf","chan"]);function _(e,t,n=e){return`${e}_${X(t,n)}-${N()}`}function A(e){let t=e.indexOf("_");if(t<=0)return null;let n=e.slice(0,t);return Mt.has(n)?n:null}function Ae(e){let t=e.query("SELECT value FROM meta WHERE key = 'node_id'").get();if(t)return t.value;let n=N(8);return e.query("INSERT INTO meta (key, value) VALUES ('node_id', ?)").run(n),n}var ee=65535,It=300000;function Oe(e,t){if(t<=ee)return{millis:e,counter:t};return{millis:e+Math.floor(t/(ee+1)),counter:t%(ee+1)}}function Le(e){let{millis:t,counter:n}=Oe(e.millis,e.counter);return`${String(t).padStart(15,"0")}-${n.toString(16).padStart(4,"0")}-${e.node}`}function Ft(e){let t=e.indexOf("-"),n=e.indexOf("-",t+1);return{millis:Number(e.slice(0,t)),counter:parseInt(e.slice(t+1,n),16),node:e.slice(n+1)}}function Ht(e,t){let n=e.query("SELECT value FROM meta WHERE key = 'hlc'").get();return n?Ft(n.value):{millis:0,counter:0,node:t}}function Bt(e,t){e.query("INSERT INTO meta (key, value) VALUES ('hlc', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(Le(t))}function Ne(e,t,n=Date.now()){let r=Ht(e,t);if(r.millis>n+It)throw Te("clock_skew","本机逻辑时钟比系统时间超前 {min} 分钟(系统时钟曾被调错,或收到过时钟错误设备的数据),运行 mh repair --clock 修复",{min:Math.ceil((r.millis-n)/60000)});let o=Math.max(r.millis,n),i=o===r.millis?r.counter+1:0,s={...Oe(o,i),node:t};return Bt(e,s),Le(s)}function Pe(e){let t=e.filter((r)=>!!r.text&&r.text.length>0),n=[];return t.forEach((r,o)=>{n.push(...r.text.split(`
12
+ 4. Run “Sync now” first.`,删除并重置:"Delete and reset","已删除此浏览器的副本,请重新连接同步存储桶":"This browser's replica was deleted; reconnect a sync bucket",已删除此浏览器的副本:"This browser's replica was deleted","重置失败:{msg}":"Reset failed: {msg}","开启后断网也能读写全部内容,改动自动同步。":"Read and write everything even without a network; changes sync automatically.","副本异常(已自动回退在线模式):{msg}":"Replica error (fell back to online mode): {msg}","正在下载数据… 已接收 {n} 条变更":({n:e})=>`Downloading data… ${e} ${d(e,"change","changes")} received`,"离线可用 · 上次同步 {when}(推送 {pushed} / 拉取 {pulled})":"Available offline · last synced {when} (pushed {pushed} / pulled {pulled})","上次同步失败({when}):{msg} — 本地读写不受影响,恢复网络后自动重试":"Last sync failed ({when}): {msg} — local reads and writes are unaffected; retries automatically once the network is back","等待首次同步…":"Waiting for the first sync…","此浏览器暂不支持保存完整副本:{reason}":"This browser cannot keep a full replica: {reason}"," · 节点 {node}":" · node {node}"," · 本地占用 {mb} MB":" · {mb} MB used locally",离线副本:"Offline replica",在此浏览器保存完整副本:"Keep a full replica in this browser",立即同步:"Sync now","把本机改动推送出去,并拉取其他设备的最新内容。":"Push local changes and pull the latest from other devices.","删除此浏览器里的全部本地数据并切回在线模式;未同步的编辑和未上传的附件会丢失。":"Deletes all local data in this browser and switches back to online mode; unsynced edits and unuploaded attachments will be lost.",删除副本:"Delete replica",清理腾空间:"Free up space","将释放约 {bytes}。只清本机缓存,文件已存在云端,用到时自动取回。待上传的内容不会动。":"About {bytes} will be freed. Only the local cache is cleared; the files are in the cloud and are fetched again when needed. Pending uploads are untouched.","已腾出 {bytes}({n} 项)":({bytes:e,n:t})=>`Freed ${e} (${t} ${d(t,"item","items")})`,"本地数据已受保护,系统不会自动清理":"Local data is protected; the system will not clear it automatically",浏览器暂未授予保护:"The browser did not grant protection",本机缓存:"Local cache","只存在这台设备上;清理不影响其他设备,文件用到时自动取回。":"Exists only on this device; clearing does not affect other devices, and files are fetched again when needed.","{n} 项(约 {bytes})还没上传,仅存在这台设备上。建议先「立即同步」再清理——清理不会动这些待上传的内容。":({n:e,bytes:t})=>`${e} ${d(e,"item","items")} (about ${t}) not uploaded yet and only on this device. Run “Sync now” before clearing — clearing never touches pending uploads.`,无需清理:"Nothing to clear",防止浏览器自动清理:"Prevent the browser from clearing automatically","已开启:系统空间紧张时不会自动清掉本地数据。":"On: local data will not be cleared automatically when the system runs low on space.","浏览器未授予保护;系统空间紧张时可能清理本地数据。可稍后重试,或保持定期同步。":"The browser did not grant protection; local data may be cleared when the system runs low on space. Retry later, or keep syncing regularly.","开启后,系统空间紧张时也不会自动清掉本地数据。浏览器授予与否由其自行决定。":"When on, local data is not cleared automatically even when the system runs low on space. Whether the browser grants this is up to the browser.",请求保留本地数据:"Request to keep local data","此浏览器为本工作区共占用约 {usage}(含本地数据库与缓存)。缓存超过 {quota} 时自动清理最久没用、已有备份的,你固定的不动。":"This browser uses about {usage} for this workspace (local database plus cache). When the cache exceeds {quota}, the least recently used items that already have a backup are cleared automatically; pinned items are kept.","无法获取最新版本(网络不可用或 GitHub API 受限)":"Could not fetch the latest version (network unavailable or GitHub API rate-limited)",已是最新版本:"Already up to date","已下载 v{v}":"Downloaded v{v}","从 GitHub Releases 获取核心更新":"Fetch core updates from GitHub Releases",检查更新:"Check for updates","正在检查…":"Checking…","下载中 · {pct}%":"Downloading · {pct}%","下载中…":"Downloading…",下载:"Download",新版本:"New version",可用:"available","下载并更新到 v{v}":"Download and update to v{v}","已就绪,重启后生效":"is ready; restart to apply","重启应用以更新到 v{v}":"Restart the app to update to v{v}",重启:"Restart",检查失败:"Check failed",更新:"Updates",软件更新:"Software update",资源:"Resources","GitHub 仓库":"GitHub repository",源代码与问题反馈:"Source code and issue tracker",更新日志:"Changelog",各版本的发布说明:"Release notes for each version","快捷键已设为 {keys}":"Shortcut set to {keys}","设置失败:{msg}":"Could not save: {msg}",唤起快捷键:"Summon shortcut","点击右侧按钮,然后按下你想用的组合键。":"Click the button on the right, then press the key combination you want.",重置默认:"Reset to default","按下组合键…":"Press a key combination…",默认始终置顶:"Always on top by default","用全局快捷键随时唤起小窗:记录想法,或瞄一眼任务看板。小窗也可从菜单栏图标打开。":"Summon a small window anytime with a global shortcut: jot down an idea, or glance at the task board. The windows can also be opened from the menu bar icon.","小窗浮在其他窗口之上;也可在小窗内用 \uD83D\uDCCC 按钮切换。":"The window floats above other windows; you can also toggle it with the \uD83D\uDCCC button inside the window.",快速看板:"Quick Board","看板小窗浮在其他窗口之上;AI 通过 CLI 更新任务时看板实时刷新。":"The board window floats above other windows; it refreshes live when an AI updates tasks through the CLI.",管理本机缓存:"Manage local cache","已下载到这台设备的图片和大文件。清理只删本机副本,需要时从云端重新取回;桶里的原件始终不动。":"Images and large files downloaded to this device. Clearing only removes the local copy and fetches it again from the cloud when needed; the originals in the bucket are never touched.",管理附件存储:"Manage attachment storage","工作区里的全部图片和大文件,所有设备共用。可清理项已有长期备份、随时能腾空间(用时自动取回);没被任何文档或站点引用的孤儿可彻底删除。":"All images and large files in the workspace, shared by every device. Clearable items already have a long-term backup and can free space anytime (fetched again when used); orphans referenced by no document or site can be deleted permanently.",其他:"Other",待上传:"Pending upload",已固定:"Pinned",孤儿:"Orphan",可清理:"Clearable",保留:"Retained",大小:"Size",类型:"Type",最近使用:"Last used",全部类型:"All types",全部状态:"All statuses",保留中:"Retained","已清理 {n} 项 · 腾出 {bytes}":({n:e,bytes:t})=>`Cleared ${e} ${d(e,"item","items")} · freed ${t}`,没有可清理的项:"Nothing to clear","删除孤儿 blob":"Delete orphan blobs","将从这台设备移除 {n} 个未被任何文档/站点引用的 blob,约 {bytes}。桶里的原件不动,需要时仍可重新下载。":({n:e,bytes:t})=>`Removes ${e} ${d(e,"blob","blobs")} referenced by no document or site from this device, about ${t}. The originals in the bucket are kept and can be downloaded again when needed.`,"将永久删除 {n} 个未被任何文档/站点引用的 blob,约 {bytes}。此操作不可恢复。":({n:e,bytes:t})=>`Permanently deletes ${e} ${d(e,"blob","blobs")} referenced by no document or site, about ${t}. This cannot be undone.`,"已删除 {n} 项 · 腾出 {bytes}":({n:e,bytes:t})=>`Deleted ${e} ${d(e,"item","items")} · freed ${t}`,没有可删除的孤儿:"No orphans to delete",排序依据:"Sort by",按类型:"By type",按状态:"By status","已选 {n} 项 · {bytes}":({n:e,bytes:t})=>`${e} ${d(e,"item","items")} selected · ${t}`,"共 {n} 项 · {bytes}":({n:e,bytes:t})=>`${e} ${d(e,"item","items")} · ${t}`,"清理所选({n})":"Clear selected ({n})",清理所选:"Clear selected","删除孤儿({n})":"Delete orphans ({n})",删除孤儿:"Delete orphans","搜索 hash 或类型…":"Search hash or type…","无法读取:{err}":"Could not read: {err}",没有符合条件的项:"No matching items","这台设备还没有缓存的 blob":"No cached blobs on this device yet",取消固定:"Unpin","固定(不被自动清理)":"Pin (never cleared automatically)",可释放:"Freeable",都备份好了:"All backed up",暂时无需清理:"Nothing to clear for now",未设置长期备份:"No long-term backup set",指定后才能清理:"Set one to enable clearing","检查中…":"Checking…",未检查:"Not checked",检查后才知道:"Check to find out",原件都在这里:"All originals are here",还没有笔记:"No notes yet",删除当前笔记:"Delete current note",笔记列表:"Note list",取消置顶:"Unpin from top",始终置顶:"Always on top","此浏览器不支持 Web Worker。":"This browser does not support Web Workers.","需要 HTTPS(安全上下文)。当前是 http:// 访问,浏览器不开放离线所需的 OPFS 与 Service Worker——给服务器配置 TLS(--tls-cert/--tls-key,或 Caddy / Tailscale Serve 反代;iPhone 需要受信任的证书),或在本机用 localhost 访问。":"HTTPS (a secure context) is required. This page is served over http://, so the browser withholds the OPFS and Service Worker features offline mode needs. Configure TLS on the server (--tls-cert/--tls-key, or a Caddy / Tailscale Serve reverse proxy; iPhone needs a trusted certificate), or open it locally via localhost.","此浏览器不支持 OPFS 本地存储(需要 Safari 17+ / Chrome / Firefox 较新版本)。":"This browser does not support OPFS local storage (requires Safari 17+ or a recent Chrome / Firefox).","阿里云 OSS":"Alibaba Cloud OSS",上海:"Shanghai",北京:"Beijing",广州:"Guangzhou",成都:"Chengdu",南京:"Nanjing",香港:"Hong Kong",新加坡:"Singapore",东京:"Tokyo",杭州:"Hangzhou",深圳:"Shenzhen",美国东部:"US East","美国东部 2":"US East 2",美国西部:"US West","美国西部 2":"US West 2",爱尔兰:"Ireland",法兰克福:"Frankfurt",切换数据库:"Switch database",还没有数据库:"No databases yet",搜索数据库:"Search databases","实时同步中——AI 通过 CLI 的修改会即时出现":"Live — changes an AI makes through the CLI appear instantly",还没有看板:"No board yet","在主窗口创建一个带「单选」属性的数据库,任务进度就会出现在这里。":"Create a database with a “Select” property in the main window and task progress will show up here.",编辑器尚未就绪:"The editor is not ready yet",缺少文件路径:"Missing file path",文件编辑窗口仅在桌面应用内可用:"The file editor window is only available in the desktop app","已导入到 MetaHub":"Imported into MetaHub",预览模式:"Preview",源码模式:"Source","在 MetaHub 中打开":"Open in MetaHub","已导入 ✓":"Imported ✓","导入中…":"Importing…","导入到 MetaHub":"Import into MetaHub",本地副本启动超时:"Timed out starting the local replica",本地副本启动失败:"Failed to start the local replica","此页面没有工作区主节点,不能使用服务器配对;请重新连接同步存储桶。":"This page has no workspace primary node, so server pairing is unavailable; reconnect a sync bucket.","无法获取配对码: {status}":"Could not get a pairing code: {status}","此设备通过同步存储桶交换数据,不支持设备托管发布;请在在线主节点操作":"This device exchanges data through a sync bucket and does not support device-hosted publishing; do this on the online primary node",此设备没有可配置的托管入口:"This device has no configurable hosting entry point",本机:"This device",这台设备上的副本:"The replica on this device",工作区主节点:"Workspace primary node",当前连接的工作区:"The workspace you are connected to",本机工作区:"Local workspace",这台电脑上的工作区:"The workspace on this computer",危险操作:"Danger zone"};var Re={"加载失败:{msg}":"Failed to load: {msg}",连接存储桶:"Connect a bucket",所有设备通过它同步:"Every device syncs through it",两台服务器互相实时同步:"Two servers sync each other in real time",已触发同步:"Sync started","同步失败:{msg}":"Sync failed: {msg}","停止使用 {name}":"Stop using {name}","将停止与它同步。桶内数据不受影响,可以随时重新连接。":"Syncing with it will stop. Data in the bucket is untouched, and you can reconnect at any time.",停止使用:"Stop using",停止此浏览器离线同步:"Stop offline sync in this browser","此浏览器将不再直连 {name}。存储桶仍在服务器上;此浏览器在线时照常经服务器同步。":"This browser will no longer connect to {name} directly. The bucket stays on the server, and this browser keeps syncing through the server while online.",停止:"Stop","停止直连 {name}":"Stop direct sync with {name}","将停止与它的直连同步。它上面已同步的数据仍在,无法远程删除。":"Direct sync with it will stop. Data already synced to it stays there and cannot be deleted remotely.",停止直连:"Stop direct sync","实时 · 数据在这里产生":"Live · data is created here",直连:"Direct",已暂停:"Paused","重试中…":"Retrying…",重试:"Retry",地址:"Address","节点 ID":"Node ID",上次成功同步:"Last successful sync",尚未确认:"Unconfirmed","{n} 条改动":({n:e})=>`${e} ${d(e,"change","changes")}`,立即同步:"Sync now",恢复同步:"Resume sync",暂停同步:"Pause sync",这台设备的名字与接入权限在:"This device's name and access are managed on the",设备:"Device","页管理。":"page.","同步失败 · {error}":"Sync failed · {error}","上次同步 {ago}":"Last synced {ago}",还没同步过:"Never synced",本机发布:"Published from this device",此浏览器可离线同步:"Offline sync available in this browser",这台设备:"This device","服务器,此浏览器也保存了一份":"Server, with a copy in this browser",服务器:"Server",服务商:"Provider",存储桶:"Bucket",加密:"Encryption","已加密,上传前在本地完成":"Encrypted locally before upload",未加密:"Not encrypted",凭据保存在:"Credentials stored on",用途:"Roles",上次同步:"Last sync",让此浏览器可离线同步:"Enable offline sync in this browser","更换存储密钥…":"Rotate storage key…","导出恢复码…":"Export recovery code…","让此浏览器可离线同步后,服务器连不上时它也能直接经存储桶同步。":"With offline sync enabled, this browser can sync straight through the bucket whenever the server is unreachable.","更换密钥…":"Rotate key…","先在「离线与缓存」把这台设备设为在本机保存,再连接存储桶。":"First set this device to keep data locally under “Offline & cache”, then connect a bucket.",前往设置:"Open settings",添加设备:"Add device",添加同步目标:"Add sync target",同步目标:"Sync targets","这台设备的云端后端,其他设备通过它加入并同步。":"This device's cloud backend; other devices join and sync through it.","数据保存在这些地方;上传到云端前已在本地加密。":"Your data lives in these places, encrypted locally before it goes to the cloud.","用 Cloudflare 开始":"Start with Cloudflare","登录后创建 R2 存储桶并部署 Edge,向导会带你在控制台生成访问密钥。":"Sign in to create an R2 bucket and deploy Edge; the wizard walks you through generating access keys in the console.","连接 Cloudflare":"Connect Cloudflare",还没有其他同步目标:"No other sync targets yet","连接一个存储桶,所有设备通过它交换加密后的工作区数据。":"Connect a bucket so every device can exchange encrypted workspace data through it.","还没连接存储桶。直连只在两边都在线时同步;存储桶能让离线的设备稍后补齐。":"No bucket connected yet. Direct sync only works while both sides are online; a bucket lets offline devices catch up later.",它是怎么工作的:"How it works","存储桶只做中转:每台设备把改动加密后上传,再拉取别人的——不必同时在线,也不需要公网 IP。直连则是两台服务器互相推送,实时但要求两边同时在线。":"A bucket is just a relay: each device encrypts its changes, uploads them and pulls everyone else's, with no need to be online at the same time or have a public IP. Direct sync pushes between two servers in real time, but both must be online.","这台设备把改动发布到桶,其他设备从桶拉取;新设备扫码加入即可。":"This device publishes changes to the bucket and other devices pull from it; a new device joins by scanning the QR code.","这台设备把整个工作区发布到桶,新设备扫码加入后从桶恢复。":"This device publishes the whole workspace to the bucket; a new device scans the QR code and restores from it.","密钥只保存在服务器;信任的设备重输一次密钥即可直连桶,离线、在外也不中断。":"The key stays on the server only; a trusted device re-enters it once to sync straight through the bucket, even offline or away.","清理腾出 {bytes}":"Clean up to free {bytes}","只删除别处已确认备份的副本,文件不会丢,用到时自动取回。":"Only copies confirmed backed up elsewhere are removed. Nothing is lost; files are fetched again when needed.",清理:"Clean up","已腾出 {bytes}({n} 项)":({bytes:e,n:t})=>`Freed ${e} (${t} ${d(t,"item","items")})`,暂时没有可清理的:"Nothing to clean up right now","附件原件保存在「{place}」,所有设备共用。":"Original attachments are stored on “{place}”, shared by every device.","开启的位置会保留所有附件的完整副本。只要确认这里有备份,其他设备就可以放心清理本地缓存。":"Places switched on keep a full copy of every attachment. Once a backup is confirmed here, other devices can safely clear their local cache.",附件:"Attachments",长期保留副本:"Long-term copies","这里保留全部附件原件,无需清理":"All original attachments are kept here; nothing to clean up",还没指定长期保留的位置:"No long-term location chosen yet",核对备份后才能清理:"Verify backups before cleaning up","都已备份,暂时没有可清理的":"Everything is backed up; nothing to clean up right now","核对中…":"Verifying…","上次核对 {ago}":"Last verified {ago}",还没核对过:"Not verified yet","查看附件…":"View attachments…",云端存储桶:"Cloud bucket",未命名设备:"Unnamed device","还没有长期保留的位置。先指定一处,其他设备才能放心清理本地缓存。":"No long-term location yet. Pick one first so other devices can safely clear their local cache.",清理前需要几处确认:"Confirmations required before cleanup","设备清理缓存前,要有几处长期副本确认持有该附件。":"How many long-term copies must confirm they hold an attachment before a device clears it from its cache.",一处即可:"Any one place",任一长期副本确认即可清理:"Any single long-term copy is enough",每处都要:"Every place",所有长期副本都确认后才清理:"Clean up only after every long-term copy confirms","部分位置连不上,相关文件暂不清理":"Some places are unreachable; their files are kept for now",重新核对:"Verify again","占用超过 {bytes} 时,自动清理最久没用、已有备份的;你固定的不动。":"Above {bytes}, the least recently used files that are backed up are cleaned automatically; pinned files stay.",命令行:"Command line",桌面应用:"Desktop app",浏览器:"Browser",网页:"Web","设备来源刷新失败:{msg}":"Failed to refresh device sources: {msg}","撤销 {name} 的接入":"Revoke access for {name}","将同时撤销它的 {n} 个授权码。它将无法再同步进这个工作区。已同步到它上面的数据仍在,无法远程删除。":({n:e})=>`Its ${e} enrollment codes will be revoked too. It can no longer sync into this workspace. Data already synced to it stays there and cannot be deleted remotely.`,"它将无法再同步进这个工作区。已同步到它上面的数据仍在,无法远程删除。":"It can no longer sync into this workspace. Data already synced to it stays there and cannot be deleted remotely.",撤销接入:"Revoke access",重命名这台设备:"Rename this device","给 {name} 起个名字":"Name {name}",所有设备都会看到这个名字:"Every device will see this name","例如:工作室 iMac":"e.g. Studio iMac","重命名失败:{msg}":"Rename failed: {msg}",工作区所在的服务器:"The server hosting this workspace","经存储桶 {name} 同步":"Syncs through bucket {name}",直连同步:"Direct sync",直连已暂停:"Direct sync paused",通过授权码接入:"Joined with an enrollment code",曾同步过:"Synced before",工作区所在的这台设备:"The device hosting this workspace","经存储桶 {name}":"Through bucket {name}","(在线,租约至 {until})":"(online, lease until {until})",授权码:"Enrollment code","曾写入过工作区,来源未确认":"Has written to the workspace; source unconfirmed",接入方式:"Connection",应用:"App",最近活动:"Last active",重命名:"Rename","同步状态、暂停与停止直连在":"Sync status, pausing and stopping direct sync live in",数据与备份:"Data & backup","。":".","撤销后它不能再同步进来;此前已同步到它上面的数据无法远程删除。":"Once revoked it can no longer sync in; data already synced to it cannot be deleted remotely.","这台设备和其他设备共用同一把存储密钥,无法单独移除。":"This device shares the storage key with the others, so it cannot be removed on its own. After you ",更换密钥:"rotate the key","后它将不能再同步;已下载到它上面的数据无法追回。":" it can no longer sync; data already downloaded to it cannot be recalled.","正在核对已连接的存储桶…":"Checking connected buckets…","只能确认它曾写入过工作区,无法确认它现在经哪条渠道接入,所以这里不提供断开操作。":"We can only confirm it once wrote to the workspace, not which channel it uses now, so there is no disconnect action here.","{n} 台设备同步过这个工作区。":({n:e})=>`${e} ${d(e,"device has","devices have")} synced this workspace.`,"最近 30 天":"Last 30 days","还没有其他设备。":"No other devices yet.",很久没活动:"Inactive for a while","超过 30 天没有同步的设备。如果已经不再使用,可以在这里撤销它们的接入。":"Devices that have not synced for over 30 days. If they are no longer in use, revoke their access here.","数据只在这一处,还没有备份":"Data lives in one place only; no backup yet","已保存在 {n} 处 · {m} 个附件还只在这里":({n:e,m:t})=>`Saved in ${e} ${d(e,"place","places")} · ${t} ${d(t,"attachment is","attachments are")} still only here`,"已保存在 {n} 处 · 有改动尚未同步":({n:e})=>`Saved in ${e} ${d(e,"place","places")} · unsynced changes`,"已保存在 {n} 处 · {m} 处同步失败":({n:e,m:t})=>`Saved in ${e} ${d(e,"place","places")} · ${t} ${d(t,"place","places")} failing to sync`,"已保存在 {n} 处 · 首次同步进行中":({n:e})=>`Saved in ${e} ${d(e,"place","places")} · first sync in progress`,"已保存在 {n} 处 · 备份确认已过期":({n:e})=>`Saved in ${e} ${d(e,"place","places")} · backup confirmation expired`,"已安全保存在 {n} 处 · 上次确认 {ago}":({n:e,ago:t})=>`Safely saved in ${e} ${d(e,"place","places")} · last confirmed ${t}`,"已安全保存在 {n} 处":({n:e})=>`Safely saved in ${e} ${d(e,"place","places")}`,尚未配置任何同步目标:"No sync target configured yet",同步失败:"Sync failed",尚未完成首次同步:"First sync not finished yet",有改动尚未同步:"Unsynced changes",备份确认已过期:"Backup confirmation expired",有附件仅在本机:"Some attachments exist only on this device","{place}:{issue}":"{place}: {issue}","{issue} — {detail}":"{issue} — {detail}",本机:"This device","实时(本机)":"Live (this device)",当前版本已确认:"Current version confirmed",确认已过期:"Confirmation expired",尚未同步:"Not synced yet",已停用:"Disabled",完整副本:"Full replica",工作区同步:"Workspace sync",附件长期保存:"Long-term attachments",发布快照:"Published snapshots",尚未确认当前版本:"Current version not confirmed yet",加密口令必填:"Encryption passphrase is required","加密口令不正确,请输入与其他设备相同的口令。":"Wrong encryption passphrase. Enter the same passphrase as on your other devices.","连接被浏览器拦截:请给存储桶配置 CORS,允许此源的 GET/PUT/HEAD/DELETE。":"The browser blocked the connection. Configure CORS on the bucket to allow GET/PUT/HEAD/DELETE from this origin.","连接失败:{msg}":"Connection failed: {msg}","endpoint、bucket、access key、secret key 必填":"Endpoint, bucket, access key and secret key are required",没有在图片中找到有效的接入二维码:"No valid enrollment QR code found in the image",连接你的存储桶:"Connect your bucket","扫描主设备上的二维码:在桌面端打开 设置 → 同步 → 手机接入。":"Scan the QR code on your primary device: open Settings → Sync → Mobile access on the desktop.",相机权限被拒绝:"Camera access denied","允许访问相机,或改用下方粘贴/手动方式。":"Allow camera access, or paste or fill in the details below instead.",重试授权:"Try again",这不是有效的接入二维码:"This is not a valid enrollment QR code",无效的接入链接或代码:"Invalid enrollment link or code",从图片识别:"Read from image",手动填写:"Enter manually","已读取桶信息,输入加密口令即可同步你的数据。":"Bucket details loaded. Enter the encryption passphrase to sync your data.","已读取桶信息,点击连接即可同步你的数据。":"Bucket details loaded. Click Connect to sync your data.",端到端加密:"End-to-end encrypted",加密口令:"Encryption passphrase",与其他设备相同的口令:"Same passphrase as your other devices","连接中…":"Connecting…",连接并同步:"Connect and sync",重新扫码:"Scan again",改为手动填写:"Enter manually instead","填入 S3 兼容存储桶(R2/COS/MinIO/S3)的连接信息;数据上传前端到端加密。":"Enter the connection details of an S3-compatible bucket (R2/COS/MinIO/S3). Data is end-to-end encrypted before upload.",路径前缀:"Path prefix",改为扫码:"Scan a QR code instead","提示:iPhone 请用 Safari 的「分享 → 添加到主屏」装成 App,离线与持久存储才生效。":"Tip: on iPhone, use Safari's Share → Add to Home Screen to install as an app; offline mode and persistent storage only work that way.","或粘贴接入链接 / 接入码…":"Or paste an enrollment link / code…",当前服务器:"Current server"};var De={回滚待确认:"Rollback pending confirmation",撤销待确认:"Revoke pending confirmation",发布失败:"Publish failed","正在创建 Edge 渠道":"Creating Edge channel","已上线 · Edge 始终在线":"Live · Edge, always on","已上线 · 设备托管":"Live · device hosted","正在同步 · 设备托管":"Syncing · device hosted","已设公开 · 入口未验证":"Set public · entry point unverified",链接分享中:"Shared by link",链接已过期:"Link expired",私有预览:"Private preview",在线:"Online",同步中:"Syncing",入口未验证:"Entry point unverified",正在创建:"Creating",等待控制设备:"Waiting for controller device",失败:"Failed",已过期:"Expired",任何人:"Anyone",持链接者:"Anyone with the link",已复制:"Copied","{n} 个 api/ 下的文件会被站点数据 API 遮蔽(仍会上传,但无法访问)":({n:e})=>`${e} ${d(e,"file","files")} under api/ will be shadowed by the site data API (uploaded, but unreachable)`,"已上传 {n} 个文件":({n:e})=>`Uploaded ${e} ${d(e,"file","files")}`,"已上传 {ok}/{total} 个,{failed} 个失败":"Uploaded {ok}/{total}, {failed} failed",部分文件上传失败:"Some files failed to upload","{n} 个文件未能上传,可修正后重新拖入(成功的文件无需重传)。":({n:e})=>`${e} ${d(e,"file","files")} could not be uploaded. Fix and drop them in again; successful files need no re-upload.`,关闭:"Close","上传中 {done}/{total}…":"Uploading {done}/{total}…","已关闭 SPA 模式":"SPA mode off","已开启 SPA 模式(无扩展名路径回落到 index.html)":"SPA mode on (extensionless paths fall back to index.html)",配置管理:"Configuration",复制私有预览地址:"Copy private preview URL","发布与分享…":"Publish & share…","关闭 SPA 模式":"Turn off SPA mode","开启 SPA 模式":"Turn on SPA mode","修改标题…":"Edit title…",修改标题:"Edit title",标题:"Title","重命名(slug)…":"Rename (slug)…",删除站点:"Delete site","删除站点?":"Delete site?","「{name}」及其 {n} 个文件将被删除。":({name:e,n:t})=>`“${e}” and its ${t} ${d(t,"file","files")} will be deleted.`,删除:"Delete","加载失败:{msg}":"Failed to load: {msg}","拖入一个目录即可上传为站点(目录名 = 站点名)":"Drop a folder to upload it as a site (folder name = site name)","目录名「{name}」无法转成有效的站点名":"Folder name “{name}” cannot be turned into a valid site name",目录里没有可上传的文件:"The folder has no files to upload","创建站点「{name}」?":"Create site “{name}”?","站点「{name}」不存在。创建它并上传 {n} 个文件?":({name:e,n:t})=>`Site “${e}” does not exist. Create it and upload ${t} ${d(t,"file","files")}?`,创建并上传:"Create and upload",站点管理:"Sites","发布静态站点,生成可分享的访问链接。":"Publish static sites and get shareable links.",新建站点:"New site",正在加载:"Loading",还没有站点:"No sites yet","站点是一组网页文件,可先在本机预览,发布后供他人访问。":"A site is a set of web files. Preview it locally, then publish it for others.",未命名站点:"Untitled site",点击复制公开地址:"Click to copy the public URL","存在多个公开地址;打开详情选择":"Several public URLs; open details to choose","私密链接不在卡片显示;打开详情管理":"Private links are not shown on the card; open details to manage",点击复制私有预览地址:"Click to copy the private preview URL","{n} 个公开地址":({n:e})=>`${e} public ${d(e,"URL","URLs")}`,"{n} 个私密链接":({n:e})=>`${e} private ${d(e,"link","links")}`,"私有预览 · {url}":"Private preview · {url}","{n} 文件":({n:e})=>`${e} ${d(e,"file","files")}`,预览:"Preview","回滚仍未确认:{msg}":"Rollback still unconfirmed: {msg}",目标设备不可达:"target device unreachable",目标设备已确认恢复发布前状态:"Target device confirmed the pre-publish state is restored",重试回滚:"Retry rollback",管理:"Manage",发布:"Publish",更多:"More","共 {n} 个站点":({n:e})=>`${e} ${d(e,"site","sites")}`,"{n} 个文件":({n:e})=>`${e} ${d(e,"file","files")}`,请填写站点名称:"Enter a site name","已创建站点「{name}」":"Created site “{name}”","创建一组网页文件,随你的设备自动同步。":"Create a set of web files that sync with your devices.",取消:"Cancel",创建:"Create","名称(slug)":"Name (slug)","例如:docs、demo、status":"e.g. docs, demo, status","标题(可选)":"Title (optional)",人类可读的标题:"A human-readable title","将通过 /sites/<名称>/ 对外提供。":"Served at /sites/<name>/.","请输入有效的站点名(a-z、0-9、-)":"Enter a valid site name (a-z, 0-9, -)","已重命名为「{name}」":"Renamed to “{name}”","重命名(slug)":"Rename (slug)","slug 决定站点的访问地址 /sites/<slug>/。":"The slug sets the site address: /sites/<slug>/.","保存中…":"Saving…",重命名:"Rename","新名称(slug)":"New name (slug)","将保存为 /sites/{name}/":"Will be saved as /sites/{name}/",无法从输入得到有效的站点名:"No valid site name can be made from the input","注意:重命名后旧链接将失效 — /sites/{name}/ 不再可访问,已分发的地址需要更新。":"Note: old links break after renaming — /sites/{name}/ will no longer work, so distributed URLs must be updated.",站点不存在:"Site not found","「{name}」可能已被删除或重命名。":"“{name}” may have been deleted or renamed.",查看全部站点:"View all sites",站点还没有文件:"This site has no files yet","上传文件后,这里会直接显示站点页面。":"Once files are uploaded, the site page shows here.",去配置:"Configure",还没有文件:"No files yet",打开站点:"Open site",打开:"Open","删除文件?":"Delete file?","「{path}」将从站点「{name}」中删除。":"“{path}” will be removed from site “{name}”.","{n} 个文件 · 创建于 {date}":({n:e,date:t})=>`${e} ${d(e,"file","files")} · created ${t}`,"已发布 · {n} 个渠道":({n:e})=>`Published · ${e} ${d(e,"channel","channels")}`,私有:"Private",访问:"Access",仅你和已配对设备可打开:"Only you and paired devices can open it",复制地址:"Copy URL",访问站点:"Visit site",发布渠道:"Publish channels","还没有发布 — 目前只有你(和已配对设备)能打开上面的预览地址。":"Not published yet — only you (and paired devices) can open the preview URL above.","{host}托管":"{host} hosted",需要口令:"Password required","文件##tab":"Files",上传文件:"Upload files","上传整个目录(保留相对路径),也可以直接拖拽目录进来":"Upload a whole folder (relative paths kept); you can also drag a folder in",上传目录:"Upload folder","上传文件后即可通过站点地址访问。":"Once files are uploaded, the site is reachable at its URL.",文件大小:"File size",复制路径:"Copy path","(无法加载内容)":"(content could not be loaded)","二进制文件 · 在新标签打开查看":"Binary file · open in a new tab to view","Cloudflare 授权失败":"Cloudflare authorization failed","请先确认将创建或更新列出的 Cloudflare 资源":"Confirm first that the listed Cloudflare resources will be created or updated","请先用 Cloudflare 登录":"Sign in with Cloudflare first","请选择要部署到的 Cloudflare 账号":"Choose the Cloudflare account to deploy to","{n} 个站点重新接线失败:{sites}":({n:e,sites:t})=>`${e} ${d(e,"site","sites")} failed to rewire: ${t}`,"、":", ",";":"; ","R2 桶创建失败:{msg}":"R2 bucket creation failed: {msg}","Edge 部署完成;{notes}":"Edge deployed; {notes}","Edge 部署完成":"Edge deployed","请填入 Access Key ID 与 Secret":"Enter the Access Key ID and Secret",请设置加密口令:"Set an encryption passphrase",同步桶已接入:"Sync bucket connected",接入同步桶:"Connect sync bucket","R2 桶 {name} 已创建。最后一步:Cloudflare 不允许应用代为创建 S3 凭据,请到控制台生成后粘贴到这里。":"R2 bucket {name} created. Last step: Cloudflare does not let apps create S3 credentials, so generate them in the dashboard and paste them here.",稍后再接:"Connect later","接入中…":"Connecting…","R2 → 管理 R2 API 令牌":"R2 → Manage R2 API Tokens",",创建一个「对象读和写」令牌(可只勾选 {bucket} 这个桶),把页面显示的 Access Key ID 与 Secret Access Key 粘贴到下面。":", create an “Object Read & Write” token (you can limit it to the {bucket} bucket), then paste the Access Key ID and Secret Access Key shown there below.","加密口令(新工作区在此设置;其他设备加入时需输入同一口令)":"Encryption passphrase (set here for a new workspace; other devices enter the same one to join)","口令保护数据机密性,请牢记;接入完成后建议在「设备与授权 → 导出恢复码」打印备份。":"The passphrase protects your data; remember it. After connecting, print a backup via “Devices → Export recovery code”.","升级 Edge":"Upgrade Edge","部署 Edge":"Deploy Edge","将在你自己的 Cloudflare 账户中创建或更新一个 Worker、一个 D1 数据库和 workers.dev 入口。凭据仅用于本次请求,不会保存。":"Creates or updates a Worker, a D1 database and a workers.dev entry point in your own Cloudflare account. Credentials are used for this request only and never stored.","部署中…":"Deploying…",确认并部署:"Confirm and deploy","Cloudflare 账号":"Cloudflare account","选择账号…":"Choose an account…","{name}({id})":"{name} ({id})","✅ 已登录:{name}":"✅ Signed in: {name}","等待浏览器授权…":"Waiting for browser authorization…","用 Cloudflare 登录":"Sign in with Cloudflare","将打开 Cloudflare 授权页,只申请 Workers 与 D1 的最小权限;令牌仅用于本次部署、不会保存。":"Opens the Cloudflare consent page requesting only the minimal Workers and D1 permissions; the token is used for this deployment only and never stored.","改用 API Token":"Use an API token instead","临时 API Token":"Temporary API token","需要 Workers Scripts Write 与 D1 Write。Cloudflare Token 是账户级凭据,请仅使用最小权限 Token。":"Needs Workers Scripts Write and D1 Write. A Cloudflare token is an account-level credential; use one with minimal permissions.","改用 Cloudflare 登录":"Sign in with Cloudflare instead","Worker 名称":"Worker name","D1 名称":"D1 name","workers.dev 子域(账户尚未设置时创建)":"workers.dev subdomain (created if the account has none)","子域属于整个 Cloudflare 账户;如果账户已有子域,将使用现有值并在完成结果中提示。":"The subdomain belongs to the whole Cloudflare account; if one already exists it is used and noted in the result.","同时创建 R2 同步桶(创建桶后仍需单独生成 S3 凭据)":"Also create an R2 sync bucket (S3 credentials must still be generated separately)","R2 桶名称":"R2 bucket name","创建后还差一步:到 Cloudflare 控制台生成 S3 凭据并粘贴(授权无法代办这一步),本向导会引导你完成。":"One more step after creation: generate S3 credentials in the Cloudflare dashboard and paste them (authorization cannot do this for you); this wizard walks you through it.","检测到未完成部署,当前步骤:{step}。使用相同名称可继续,不会自动删除已创建资源。":"An unfinished deployment was found at step {step}. Keep the same names to continue; created resources are not deleted automatically.","我确认创建或更新上述 Cloudflare 资源;断开 MetaHub 时不会自动删除它们。":"I confirm creating or updating the Cloudflare resources above; they are not deleted automatically when MetaHub disconnects.",密钥必填:"Secret is required",加密口令必填:"Encryption passphrase is required","服务器未提供该桶的完整配置,无法在本设备激活":"The server did not provide the full bucket configuration; cannot activate on this device","已让本机直连存储桶 · 离线、在外也能直接同步":"This device now syncs with the bucket directly · works offline and away","连接被浏览器拦截:请给存储桶配置 CORS,允许此源的 GET/PUT/HEAD/DELETE。":"The browser blocked the connection: configure CORS on the bucket to allow GET/PUT/HEAD/DELETE from this origin.","启用失败:{msg}":"Failed to enable: {msg}",在本设备启用直连:"Enable direct sync on this device","服务器已配置这个存储桶。为保护密钥,服务器不会把它同步到浏览器——再输入一次密钥,这台设备就能与桶直接同步(离线、在外也不中断)。":"The server already has this bucket configured. To protect the secret it is never sent to the browser — enter it once more and this device syncs with the bucket directly (offline and away too).","连接中…":"Connecting…",启用直连:"Enable direct sync",存储桶:"Bucket",服务地址:"Endpoint","访问密钥 ID":"Access key ID",密钥:"Secret",重新输入该桶的密钥:"Re-enter the bucket's secret",加密口令:"Encryption passphrase",与其它设备相同的加密口令:"The same passphrase as your other devices","跨设备共用的那把加密钥——用它解开桶里别人的数据。":"The encryption key shared across devices — it unlocks the data others put in the bucket.","壳地址(手机访问的静态站点域名;留空用当前地址)":"Shell URL (static site domain the phone opens; blank = current address)",复制链接:"Copy link",已复制链接:"Link copied","手机相机扫码打开,输入加密口令即可同步。":"Scan with the phone camera, enter the passphrase, and it syncs.",在另一台电脑的终端运行:"Run in a terminal on the other computer",复制命令:"Copy command",已复制命令:"Command copied",复制接入码:"Copy enrollment code",已复制接入码:"Enrollment code copied","对方运行后会提示输入加密口令。也可在":"It will prompt for the passphrase. Alternatively, in the","向导选「粘贴接入码加入存储」。":"wizard choose “Join storage with an enrollment code”.","{time} 后过期":"Expires in {time}","已过期,请重新生成":"Expired, generate a new one",复制配对码:"Copy pairing code",已复制配对码:"Pairing code copied","生成失败:{msg}":"Failed to generate: {msg}",地址和配对码必填:"Address and pairing code are required","已配对 {url}":"Paired with {url}","配对失败:{msg}":"Pairing failed: {msg}",邀请另一台服务器配对:"Invite another server to pair","生成中…":"Generating…",生成本机配对码:"Generate a pairing code","已有对方的配对码?":"Have the other side's pairing code?",对方服务器地址:"Other server's address",配对码:"Pairing code","对方「生成本机配对码」得到的码":"The code from “Generate a pairing code” on the other side","本机可达地址(可选)":"Reachable address of this server (optional)",留空则仅本机主动同步:"Blank = only this server initiates sync","配对中…":"Pairing…",配对:"Pair",直连另一台服务器:"Connect another server directly","两台服务器互相实时同步。一边生成配对码,另一边填入。":"Two servers sync with each other in real time. Generate a pairing code on one side and enter it on the other.",完成:"Done",在手机上打开本服务器:"Open this server on your phone","服务器地址(手机访问的域名;留空用当前地址)":"Server address (domain the phone opens; blank = current address)","⚠ 含访问令牌,请勿公开分享。":"⚠ Contains the access token; do not share publicly.",手机扫码:"Scan on phone","电脑 / 命令行":"Computer / CLI",手机打开服务器:"Open server on phone",找不到该存储的配置:"Configuration for this storage was not found",添加设备:"Add device","让另一台设备加入同一个工作区——手机扫码,或在电脑上粘贴接入码。":"Bring another device into this workspace — scan on a phone, or paste the enrollment code on a computer.","加载中…":"Loading…","⚠ 接入码含桶访问密钥,请勿公开分享;不含加密口令——新设备需另输口令。":"⚠ The enrollment code contains the bucket access key; do not share publicly. It does not contain the passphrase — the new device enters it separately.","https://<账户ID>.r2.cloudflarestorage.com":"https://<account-id>.r2.cloudflarestorage.com","R2 控制台 → 管理 R2 API 令牌,创建仅限目标桶的对象读写凭据;区域填 auto。价格与额度以 Cloudflare 控制台为准。":"R2 dashboard → Manage R2 API Tokens: create object read/write credentials limited to the target bucket; region is auto. Pricing and quotas as shown in the Cloudflare dashboard.","https://s3.<区域>.amazonaws.com":"https://s3.<region>.amazonaws.com","IAM 用户的访问密钥;区域如 us-east-1。":"An IAM user's access key; region such as us-east-1.","https://minio.你的域名":"https://minio.your-domain","自建 MinIO 的访问地址与 access / secret key。":"Your self-hosted MinIO endpoint and access / secret key.","腾讯云 COS":"Tencent Cloud COS","https://<桶名-APPID>.cos.<区域>.myqcloud.com":"https://<bucket-APPID>.cos.<region>.myqcloud.com","桶名须含 APPID,用虚拟主机风格地址(host 以桶名开头)。":"The bucket name must include the APPID; use a virtual-hosted-style endpoint (host starts with the bucket name).",自定义:"Custom","任何 S3 兼容存储桶。":"Any S3-compatible bucket.","服务地址、桶名称、访问密钥 ID、密钥都要填":"Endpoint, bucket name, access key ID and secret are all required","加密口令必填(或关闭加密)":"Encryption passphrase is required (or turn encryption off)","已连接存储桶 · 服务器开始同步到桶,这台设备也直接同步":"Bucket connected · the server syncs to it, and so does this device directly","已连接存储桶 · 服务器开始同步到桶":"Bucket connected · the server now syncs to it","已连接存储桶 · 这台设备开始同步到桶":"Bucket connected · this device now syncs to it","连接被浏览器拦截:请给存储桶配置 CORS,允许此源的 GET/PUT/HEAD/DELETE(R2 控制台一条规则即可)。":"The browser blocked the connection: configure CORS on the bucket to allow GET/PUT/HEAD/DELETE from this origin (one rule in the R2 dashboard is enough).","添加失败:{msg}":"Failed to add: {msg}",连接存储桶:"Connect a bucket","连接一个 S3 兼容存储桶,所有设备就能同步——不需要公网服务器。新桶自动初始化;已有桶用相同加密口令即可加入。":"Connect an S3-compatible bucket and all devices sync — no public server needed. A new bucket is initialized automatically; join an existing one with the same passphrase.",连接:"Connect","连接后,工作区主节点负责把整库同步到这个桶;这台设备和其他设备都从它同步。":"Once connected, the workspace primary node syncs the whole hub to this bucket; this and other devices sync from it.","连接后,这台设备(本机)把整库同步到这个桶;新设备扫码加入即可一起用。":"Once connected, this device syncs the whole hub to this bucket; new devices join by scanning.",存储服务商:"Storage provider",桶名称:"Bucket name","端到端加密(强烈建议;关闭后文件以明文存放,仅限完全信任的存储)":"End-to-end encryption (strongly recommended; off = files stored in plain text, only for fully trusted storage)","新桶以此创建;其它设备用同一口令加入":"Used to create a new bucket; other devices join with the same passphrase","这是跨设备共用的一把加密钥——记牢它,换设备 / 加入已有桶时都要用它解开数据。":"This is the encryption key shared across devices — remember it; you need it to unlock data on a new device or when joining an existing bucket.",进阶:"Advanced",区域:"Region","大多按服务商默认即可(R2 用 auto)。":"Usually the provider default (auto for R2).",路径前缀:"Path prefix","同一个桶里隔离多个工作区时用;默认 metahub。":"For keeping several workspaces apart in one bucket; default metahub.","新密钥需要同时填 Access Key ID 和 Secret。":"A new key needs both the Access Key ID and the Secret.","两次输入的新口令不一致。":"The two new passphrases do not match.","没有要更改的内容。":"Nothing to change.",轮换完成:"Rotation complete","✓ 存储密钥已更换 — 现在可以到服务商控制台停用旧密钥了。":"✓ Storage key rotated — you can now disable the old key in the provider console.","✓ 口令已更换 — 已配置设备无需重输,只有新加入的设备需要新口令。":"✓ Passphrase changed — configured devices need not re-enter it; only newly joining devices use the new one.","⚠️ 轮换后的首次同步失败:{msg}(凭据已保存,稍后可在同步页重试)":"⚠️ First sync after rotation failed: {msg} (credentials saved; retry later from the sync page)","其他设备用下面的新接入码重新接入(数据与同步进度不受影响)。丢失设备此前已同步的数据无法远程抹除;它只是无法再获取之后的数据。":"Other devices reconnect with the new enrollment code below (data and sync progress are unaffected). Data a lost device already synced cannot be wiped remotely; it just receives nothing further.",复制:"Copy",轮换存储密钥:"Rotate storage keys","适用于「手机丢了 / 密钥泄露 / 想换口令」。":"For a lost phone, a leaked key, or a passphrase change.","轮换中…":"Rotating…",开始轮换:"Start rotation","请先在存储服务商控制台新建一把 Access Key——先不要删除旧的,完成后再停用。 中途任何失败,旧钥匙都还能用;重试即可续接。":"In the storage provider console, create a new Access Key first — do not delete the old one yet; disable it once done. If anything fails midway the old key keeps working; retry to resume.","新 Access Key ID(留空 = 不更换密钥)":"New Access Key ID (blank = keep current key)","新 Secret Access Key":"New Secret Access Key","同时更换加密口令(不会重新加密历史数据;已配置设备无需重输)":"Also change the passphrase (past data is not re-encrypted; configured devices need not re-enter it)",新口令:"New passphrase",再输一次确认:"Confirm new passphrase",导出恢复码:"Export recovery code",打印:"Print","没有加密的存储桶,无需恢复码。":"No encrypted bucket; no recovery code needed.","Metahub 恢复码":"Metahub recovery code","存储桶 {bucket} · 生成于 {date}":"Bucket {bucket} · generated {date}","这串代码等于你全部数据的钥匙。忘记加密口令时,凭它可恢复数据并设置新口令。 请打印或抄写,放在安全的地方;不要保存在会被同步的笔记里。 任何拿到它的人都能读取你的全部数据。":"This code is the key to all your data. If you forget the passphrase, it restores your data and lets you set a new one. Print it or write it down and keep it somewhere safe; do not store it in a note that syncs. Anyone holding it can read all your data."};var _e={"新建链接":"New link","暂无可用的托管位置":"No hosting destination available","设备与服务器":"Devices and servers","存储桶托管 · 固定快照":"Bucket hosting · Snapshot","设备托管 · 实时内容":"Device hosting · Live content","本机离线也可访问。内容固定为创建时的版本,后续修改需手动更新快照。":"Accessible while this device is offline. Shares the version at creation; update the snapshot to include later edits.","由所选设备提供访问,需保持在线。接收者看到最新内容。":"Served by the selected device, which must stay online. Recipients see the latest content.","设置访问口令":"Set an access passcode","到期状态":"Expiration","操作":"Actions","原口令无法恢复,请为新链接重新设置口令。":"The original passcode cannot be recovered. Set a new one for this link.","链接已准备好。自动复制失败,请选中下方地址手动复制。":"Your link is ready. Copy failed; select the address below to copy it manually.","撤销已提交,等待托管端确认。":"Revocation requested. Waiting for the host to confirm.","链接已撤销。":"Link revoked.","刷新状态":"Refresh status","续期并复制链接":"Renew and copy link","创建分享链接":"Create a share link","无法加载分享状态。请重试后再操作。":"Unable to load sharing status. Please retry before continuing.","分享内容":"Shared content","访问权限":"Access level","可以查看":"Can view","可以编辑":"Can edit","链接有效期":"Link expires","更多设置":"More settings","已设口令":"Passcode set","访问口令":"Passcode","暂无可用的托管位置,请先在设置中连接存储桶或服务器。":"No available host. Connect a bucket or server in Settings first.","正在撤销访问":"Revoking access","链接正在创建":"Link is being created","托管端尚未确认,链接可能仍可访问。":"The host has not confirmed yet. This link may still be accessible.","{date} 到期":"Expires {date}","已设置":"Set","未设置":"Not set","快照时间":"Snapshot taken","管理此链接":"Manage this link","更新快照内容":"Update snapshot","替换为最新版本,并延长链接有效期":"Replace with the latest version and renew the link","仅延长有效期":"Extend expiration only","保留当前快照内容":"Keep the current snapshot content","分享链接":"Shared links","正在加载分享链接…":"Loading shared links…","链接详情":"Link details","Edge 托管 · 设备离线仍可访问":"Edge hosted · reachable while offline",修改:"Update",全部:"All",关闭:"Close",分享:"Share","加载失败:{msg}":"Failed to load: {msg}",发布与分享:"Publish & share",同步失败:"Sync failed",复制:"Copy",复制链接:"Copy link",存储桶:"Bucket",已过期:"Expired",打开:"Open",撤销:"Revoke",更多:"More",本机:"This device",正在创建:"Creating",清理:"Clean up",清除筛选:"Clear filters",立即同步:"Sync now",设备:"Device",重试:"Retry",链接已复制:"Link copied",链接已过期:"Link expired","\uD83D\uDD12 有口令":"\uD83D\uDD12 Passcode","{n} 个分享":({n:e})=>`${e} ${d(e,"share","shares")}`,"1 小时":"1 hour","24 小时":"24 hours","30 天":"30 days","7 天":"7 days","Edge 链接":"Edge link","Edge 托管 — 设备离线仍可访问":"Edge hosted — reachable while your devices are offline","Edge 托管 · 设备离线仍可访问(当前端点仅支持 inbox)":"Edge hosted · reachable while offline (this endpoint only supports inbox)","Edge 托管 · 设备离线仍可访问(公开网页暂不支持)":"Edge hosted · reachable while offline (not available for public pages yet)","Edge 托管 · 设备离线仍可访问(请先配置)":"Edge hosted · reachable while offline (set up Edge first)",按来源筛选:"Filter by source",本设备未记录已验证入口:"No verified entry recorded on this device",撤销链接:"Revoke link","撤销已请求,等待 Edge 确认销毁":"Revoke requested, waiting for Edge to confirm teardown","远端尚未确认完成撤销,管理记录已保留,请重试撤销。":"Remote cleanup is not confirmed. Management records are retained; retry revocation.","撤销已请求,等待控制设备上线完成清理":"Revoke requested, waiting for the controlling device to come online and clean up","撤销已请求,等待控制设备完成清理":"Revoke requested, waiting for the controlling device to clean up","撤销已提交,但 Edge 尚未确认销毁 Room;本地保留管理记录并会继续重试。":"Revoke submitted, but Edge has not confirmed the Room teardown yet; the local record is kept and retries continue.","撤销意图会同步到控制设备。若控制设备离线,Edge Room 会显示“等待控制设备”,上线后自动销毁。":"The revoke syncs to the controlling device. If it is offline, the Edge Room shows “waiting for controller” and is torn down once it comes online.","撤销意图会同步到托管设备;该设备收到后将停止提供这个渠道。":"The revoke syncs to the hosting device, which stops serving this channel once it receives it.","撤销这个分享?":"Revoke this share?","撤销这个链接?":"Revoke this link?",撤销中:"Revoking","处理中…":"Working…",创建并复制链接:"Create and copy link",创建失败:"Creation failed",创建私密链接:"Create private link","创建中…":"Creating…","此设备通过同步存储桶交换数据、不常驻在线;请改为 Edge 托管的链接":"This device exchanges data through the sync bucket and is not always online; use an Edge-hosted link instead","此设备通过同步存储桶交换数据、不常驻在线。请在在线主节点管理公开发布;这里仍可创建由 Edge 托管的私密分享链接。":"This device exchanges data through the sync bucket and is not always online. Manage public publishing on the online primary node; you can still create Edge-hosted private links here.","此设备通过同步存储桶交换数据、不常驻在线,无法直接托管站点。":"This device exchanges data through the sync bucket and is not always online, so it cannot host a site directly.","从过期链接重新创建 — 口令需重新设置(旧口令无法恢复)。":"Re-creating from an expired link — set the passcode again (the old one cannot be recovered).","存储桶 {name}":"Bucket {name}","存储桶分享是一份快照;链接需续签后生成":"A bucket share is a snapshot; the link is generated on renewal",打开链接:"Open link",打开原表格:"Open source table",打开原对象:"Open source item",打开原文档:"Open source document",打开站点配置:"Open site settings","当前 Edge 端点仅支持数据收件(inbox),不能托管站点":"This Edge endpoint only supports the data inbox and cannot host sites","当前 Edge 端点仅支持数据收件(inbox),不能托管站点;重新部署官方 Edge Worker 即可启用。":"This Edge endpoint only supports the data inbox and cannot host sites; redeploy the official Edge Worker to enable it.","当前访问渠道({n})":"Current access channels ({n})","当前设备还没有配置公网或局域网入口,访客将无法访问。":"This device has no public or LAN entry configured yet, so visitors cannot reach it.",等待生成地址:"Waiting for an address","(等待首次同步)":"(waiting for first sync)",点击复制:"Click to copy",读:"Read",发布公开网页:"Publish public page","发布失败,目标设备的回滚尚未确认;在确认前它可能仍可公开访问。":"Publishing failed and the target device has not confirmed the rollback; it may still be publicly reachable until it does.",发布未返回访问地址:"Publishing returned no address","发布与分享:{title}":"Publish & share: {title}","发布中…":"Publishing…",分享列表加载失败:"Failed to load shares","改用 Edge 托管":"Use Edge hosting instead","高级:数据授权(让页面读写数据)…":"Advanced: data grants (let the page read and write data)…","高级:允许修改已有行…":"Advanced: allow editing existing rows…",更改:"Change",更新快照并续期:"Update snapshot and renew","更新快照内容并续期?":"Update snapshot content and renew?",更新内容并续期:"Update content and renew",公开:"Public",公开地址已复制:"Public address copied",还没有分享出去的内容:"Nothing shared yet","还没有连接 Edge。部署到你自己的 Cloudflare 后,站点始终在线,你的设备关机也能访问。":"Edge is not connected yet. Deploy to your own Cloudflare and the site stays online even when your devices are off.","还没有任何访问渠道 — 在下方创建。":"No access channels yet — create one below.","换个关键词,或清除当前的状态与来源筛选。":"Try another keyword, or clear the status and source filters.","记录将被删除。设备与 Edge 分享如需再次公开,可以重新分享。":"The records will be deleted. Device and Edge shares can be shared again later.","将用当前最新数据覆盖这份快照,接收者会看到更新后的版本;同时生成新的访问链接。":"The snapshot will be overwritten with the latest data, recipients will see the updated version, and a new access link is generated.",可编辑:"Editable",可选:"Optional",口令:"Passcode",口令保护:"Passcode protected",快过期:"Expiring",快照链接:"Snapshot link",来源:"Source",了解此设备的同步方式:"How this device syncs","链接立即失效,接收者将无法再打开。":"The link stops working immediately and recipients can no longer open it.","链接无法被猜到;可加口令和有效期。":"The link cannot be guessed; add a passcode and expiry if you like.","链接已过期 — 过期链接无法访问,可重新创建":"Link expired — expired links cannot be opened; you can re-create it",链接已失效:"Link no longer valid","链接有效至 {when}":"Link valid until {when}","(没有可授权的数据库)":"(no databases to grant)","没有可托管站点的设备。":"No device can host the site.",没有匹配的分享:"No matching shares","内容不变,重新生成访问链接":"Content unchanged, generates a new access link","内容生成于 {when}":"Content generated {when}","内容同步于 {ago}":"Content synced {ago}","前往设置 → 站点与发布":"Go to Settings → Sites & publishing","清理 {n} 个已过期分享?":({n:e})=>`Clean up ${e} expired ${d(e,"share","shares")}?`,清理全部已过期:"Clean up all expired","清理中…":"Cleaning up…","请先在“设置 → 站点与发布”连接或部署 Edge":"Connect or deploy Edge in “Settings → Sites & publishing” first","请先在“设置 → 站点与发布”配置当前设备的公网或局域网入口":"Configure this device's public or LAN entry in “Settings → Sites & publishing” first",渠道已撤销:"Channel revoked",全部来源:"All sources",权限:"Permission",确认公开发布:"Confirm public publish","确认公开发布到设备?":"Publish publicly on this device?","任何人将无法再通过公开地址访问这个站点;已有的分享链接不受影响。浏览器或 CDN 缓存可能仍保留数分钟。":"Nobody will be able to reach this site through the public address anymore; existing share links are unaffected. Browser or CDN caches may linger for a few minutes.",任何人可访问:"Anyone can access","任何人无需登录即可访问公开地址。":"Anyone can open the public address without signing in.","任何人无需登录即可通过这个地址访问。公开权限会同步,但只有选定的托管设备提供此渠道;改回私有后,浏览器或 CDN 缓存仍可能保留数分钟。数据授权:{n} 个数据库。设备必须保持在线。":({n:e})=>`Anyone can open this address without signing in. Public permissions sync, but only the selected hosting device serves this channel; after switching back to private, browser or CDN caches may linger for a few minutes. Data grants: ${e} ${d(e,"database","databases")}. The device must stay online.`,"设备 {name}":"Device {name}",设备链接:"Device link",设备在线托管:"Hosted on a device","设备在线托管 — {device},需保持在线":"Hosted on a device — {device}, must stay online","设备在线托管(此设备无法托管)":"Hosted on a device (this device cannot host)",收起高级选项:"Hide advanced options",收起数据授权:"Hide data grants","数据授权:已开启 {n} 张表…":({n:e})=>`Data grants: ${e} ${d(e,"table","tables")} enabled…`,"搜索标题 / 链接…":"Search titles / links…","所有对外公开的链接都在这里:谁能看、看到哪个版本、还能看多久。":"Every outbound link lives here: who can see it, which version, and for how long.",停止公开:"Stop public access",停止公开访问:"Stop public access","停止公开访问?":"Stop public access?","停止这个公开渠道?":"Stop this public channel?","(同步失败)":"(sync failed)","同步中…":"Syncing…","托管设备必须保持在线。渠道和撤销意图会随工作区同步,其他设备不会自动获得这个公开地址。":"The hosting device must stay online. Channels and revokes sync with the workspace; other devices do not automatically get this public address.",托管位置:"Hosting","无法读取现有公开权限:{msg}":"Could not read the current public permissions: {msg}","现有公开权限尚未加载。":"Current public permissions have not loaded.","现有公开权限尚未加载,请先点「重新加载权限」。":"Current public permissions have not loaded; click “Reload permissions” first.",新增:"Create",新增访问方式:"Add access",续期:"Renew","续期(仅链接)":"Renew (link only)",延长有效期:"Extend expiry","页面内的数据接口({api})默认关闭。若这个站点需要访客读取或提交数据,在这里按表开启。":"The page's data API ({api}) is off by default. If this site needs visitors to read or submit data, enable it per table here.",已撤销:"Revoked",已更新快照内容并复制新链接:"Snapshot updated and the new link copied","已配对的托管设备当前未确认可用。请先同步成功,或配置当前主节点的访问入口。":"No paired hosting device is confirmed available. Sync successfully first, or configure this primary node's entry.","已清理 {done} 个,{fail} 个失败:{err}":"Cleaned up {done}, {fail} failed: {err}","已清理 {n} 个分享":({n:e})=>`Cleaned up ${e} ${d(e,"share","shares")}`,已提交撤销:"Revoke submitted","已提交停止公开,等待托管设备收到同步":"Stop request submitted, waiting for the hosting device to sync","(已停用)":"(disabled)",已停止公开访问:"Public access stopped","已通过「{source}」创建私密链接,地址已复制":"Private link created via “{source}”, address copied","已重新生成访问链接并复制(内容不变)":"Access link regenerated and copied (content unchanged)","已重新生成访问链接并复制(内容为 {when} 的快照)":"Access link regenerated and copied (snapshot from {when})","已重新生成访问链接,新链接已复制(内容不变)":"Access link regenerated, new link copied (content unchanged)",永不过期:"Never expires",永久:"Permanent",用当前最新内容覆盖接收者看到的版本:"Overwrite what recipients see with the latest content",有效:"Active",有效期:"Expires","在文档、表格或站点里点「分享」即可创建链接。":"Click “Share” in a document, table or site to create a link.","站点页面与管理界面同源运行:站点内的脚本可以以你的身份读写整个工作区。只发布你信任的代码。":"Site pages run on the same origin as the admin UI: scripts inside the site can read and write the whole workspace as you. Only publish code you trust.","站点已上线,地址已复制":"Site is live, address copied",这类对象无法在这里重新分享:"This kind of item cannot be re-shared here",正在加载分享列表:"Loading shares","正在同步到目标设备;确认地址可访问后才会视为发布成功":"Syncing to the target device; publishing counts as done once the address is confirmed reachable",只读:"Read-only","至 {when}":"Until {when}",重试撤销:"Retry revoke",重新创建链接:"Re-create link",重新分享:"Share again",重新加载权限:"Reload permissions",准备中:"Preparing"};var xe={...we,...ve,...Ee,...ke,...Se,...Re,...De,..._e};function Tt(e,t){if(!t)return e;return e.replace(/\{(\w+)\}/g,(n,r)=>(r in t)?String(t[r]):n)}var At="##";function Ce(e,t,n){let r=e==="en"?xe[t]:void 0;if(typeof r==="function")return r(n??{});let o=t.indexOf(At);return Tt(r??(o<0?t:t.slice(0,o)),n)}function J(e,t,n,r){let o=n.get("id"),i=n.get("db"),s=r??{},a=(l)=>l!=null?Number(l):void 0;switch(`${e} ${t}`){case"GET /api/databases":return{op:"listDatabases",args:[]};case"POST /api/databases":return{op:"createDatabase",args:[s]};case"PATCH /api/database":return o?{op:"updateDatabase",args:[o,s]}:null;case"POST /api/database/duplicate":return o?{op:"duplicateDatabase",args:[o,s]}:null;case"DELETE /api/database":return o?{op:"deleteDatabase",args:[o]}:null;case"GET /api/database/activity":return i?{op:"listDatabaseActivity",args:[i,a(n.get("limit"))]}:null;case"POST /api/repair/clock":return{op:"repairClock",args:[]};case"GET /api/audit":return{op:"listAuditEntries",args:[{limit:a(n.get("limit")),before:n.get("before")??void 0,actor:n.get("actor")??void 0}]};case"GET /api/audit/entry":{let l=n.get("txn");return l?{op:"auditEntryDetail",args:[l]}:null}case"POST /api/audit/revert":{let l=n.get("txn");return l?{op:"revertChangeGroup",args:[l]}:null}case"GET /api/properties":return i?{op:"listProperties",args:[i]}:null;case"POST /api/properties":{let{db:l,...p}=s;return l?{op:"addProperty",args:[l,p]}:null}case"PATCH /api/property":return o?{op:"updateProperty",args:[o,s]}:null;case"PATCH /api/property/width":return o?{op:"setPropertyWidth",args:[o,s.width]}:null;case"POST /api/property/option/rename":{let{from:l,to:p}=s;return o?{op:"renameSelectOption",args:[o,l,p]}:null}case"POST /api/property/option/remove":return o?{op:"removeSelectOption",args:[o,s.name]}:null;case"DELETE /api/property":return o?{op:"removeProperty",args:[o]}:null;case"GET /api/property/history":return o?{op:"listPropertyRevisions",args:[o]}:null;case"POST /api/property/revert":return o?{op:"revertProperty",args:[o,s.to]}:null;case"GET /api/records":return i?{op:"listRecords",args:[i,{sort:n.get("sort")??void 0,limit:a(n.get("limit"))}]}:null;case"POST /api/records":return i?{op:"createRecord",args:[i,s]}:null;case"GET /api/record":return o?{op:"getRecord",args:[o]}:null;case"PATCH /api/record":return o?{op:"updateRecord",args:[o,s]}:null;case"PATCH /api/record/order":{let{target:l,where:p}=s;return o&&l?{op:"moveRecord",args:[o,l,p]}:null}case"GET /api/record/history":return o?{op:"listRecordRevisions",args:[o]}:null;case"GET /api/record/at":return o?{op:"recordAtVersion",args:[o,n.get("version")]}:null;case"GET /api/record/field-history":return o?{op:"recordFieldHistory",args:[o,n.get("prop")]}:null;case"POST /api/record/revert":return o?{op:"revertRecord",args:[o,s.to]}:null;case"DELETE /api/record":return o?{op:"deleteRecord",args:[o]}:null;case"GET /api/documents":return{op:"listDocuments",args:[{database_id:n.get("db")??void 0,parent_id:n.get("parent")??void 0}]};case"POST /api/documents":return{op:"createDocument",args:[s]};case"GET /api/document":return o?{op:"getDocument",args:[o]}:null;case"PATCH /api/document":{let{if_match:l,...p}=s;return o?{op:"updateDocument",args:[o,p,l]}:null}case"POST /api/document/duplicate":return o?{op:"duplicateDocument",args:[o,s]}:null;case"PATCH /api/document/move":{let{target:l,where:p}=s;return o&&l?{op:"moveDocument",args:[o,l,p]}:null}case"GET /api/document/history":return o?{op:"listDocumentRevisions",args:[o]}:null;case"GET /api/document/at":return o?{op:"documentAtVersion",args:[o,n.get("version")]}:null;case"POST /api/document/revert":{let{to:l,if_match:p}=s;return o&&l?{op:"revertDocument",args:[o,l,p]}:null}case"DELETE /api/document":return o?{op:"deleteDocument",args:[o]}:null;case"GET /api/nodes":return{op:"nodes",args:[]};case"GET /api/sync/health":return{op:"dataMap",args:[]};case"GET /api/search":{let l=n.get("q");return l?{op:"search",args:[l,a(n.get("limit"))]}:null}case"GET /api/resolve":{let l=n.get("ref");return l?{op:"resolve",args:[l,a(n.get("limit"))]}:null}case"GET /api/sites":return{op:"listSites",args:[]};case"GET /api/site/files":{let l=n.get("site");return l?{op:"listSiteFiles",args:[l]}:null}case"POST /api/sites":return{op:"createSite",args:[s]};case"PATCH /api/site":return o?{op:"updateSite",args:[o,s]}:null;case"DELETE /api/site":return o?{op:"deleteSite",args:[o]}:null;case"GET /api/site/grants":return o?{op:"getSiteGrants",args:[o]}:null;case"PUT /api/site/grants":return o?{op:"setSiteGrants",args:[o,s]}:null;case"GET /api/site-hosting":return{op:"siteHosting",args:[]};case"PATCH /api/site/channel":{let l=s.id;return l?{op:"revokeSiteChannel",args:[l]}:null}case"DELETE /api/site/file":{let l=n.get("site"),p=n.get("path");return l&&p?{op:"deleteSiteFile",args:[l,p]}:null}case"GET /api/shares":return{op:"listLocalShares",args:[n.get("target")??void 0]};case"POST /api/share":return{op:"createLocalShare",args:[s]};case"DELETE /api/share":case"DELETE /api/share/managed":return n.get("slug")?{op:"revokeLocalShare",args:[n.get("slug")]}:null;default:return null}}function Ot(e,t){if(e===null)return"unknown";if(e.status>=500)return"unknown";if(e.ok)return t?.ok===!0?"server":"none";return"none"}async function Te(){let e=null,t=null;try{e=await fetch("/health",{cache:"no-store"}),t=e.ok?await e.json().catch(()=>null):null}catch{e=null}return Ot(e,t)}var Lt=Math.floor(167772160);function Pt(e){if(e instanceof ArrayBuffer)return e;let t=new Uint8Array(e.byteLength);return t.set(e),t.buffer}async function Nt(e){let t=await crypto.subtle.digest("SHA-256",Pt(e));return[...new Uint8Array(t)].map((n)=>n.toString(16).padStart(2,"0")).join("")}async function Q(e,t){return(await Nt(e)).slice(0,t.length)===t}var $t={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",avif:"image/avif",svg:"image/svg+xml",pdf:"application/pdf"};function Ae(e){let t=e.lastIndexOf("."),n=t>=0?e.slice(t+1).toLowerCase():"";return $t[n]??"application/octet-stream"}var U=null;function Mt(){if(U)return U;return U=new Promise((e,t)=>{let n=indexedDB.open("mh-blobs",1);n.onupgradeneeded=()=>{let r=n.result;if(!r.objectStoreNames.contains("spool"))r.createObjectStore("spool",{keyPath:"hash"});if(!r.objectStoreNames.contains("meta"))r.createObjectStore("meta",{keyPath:"hash"})},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)}),U}function C(e,t,n){return Mt().then((r)=>new Promise((o,i)=>{let s=n(r.transaction(e,t).objectStore(e));s.onsuccess=()=>o(s.result),s.onerror=()=>i(s.error)}))}var q=(e)=>`/blob/${e}`;async function It(e,t,n){let r=await C("meta","readonly",(i)=>i.get(e)),o={hash:e,size:t,accessed:Date.now(),pinned:r?.pinned??0,content_type:n??r?.content_type};await C("meta","readwrite",(i)=>i.put(o))}async function Ft(e){let t=await C("meta","readonly",(n)=>n.get(e));if(!t)return;t.accessed=Date.now(),await C("meta","readwrite",(n)=>n.put(t))}function Ht(e){return e.trim().toLowerCase().startsWith("text/html")}async function Oe(e){let t=await caches.open("mh-blob-v1"),n=await t.match(q(e));if(!n)return;if(Ht(n.headers.get("content-type")??"")){await t.delete(q(e)),await C("meta","readwrite",(r)=>r.delete(e)).catch(()=>{});return}return Ft(e),n}async function X(e,t,n){await(await caches.open("mh-blob-v1")).put(q(e),new Response(t,{headers:{"content-type":n,"cache-control":"public, max-age=31536000, immutable"}})),await It(e,t.byteLength,n),await Bt()}async function Bt(){let e=await C("meta","readonly",(o)=>o.getAll())??[],t=e.reduce((o,i)=>o+i.size,0);if(t<=209715200)return;let n=await caches.open("mh-blob-v1"),r=e.filter((o)=>!o.pinned).sort((o,i)=>o.accessed-i.accessed);for(let o of r){if(t<=Lt)break;await n.delete(q(o.hash)),await C("meta","readwrite",(i)=>i.delete(o.hash)),t-=o.size}}var Le='<script src="/mh-runtime.js"></script>';function Pe(e){let t=e.toLowerCase().indexOf("<head>");if(t>=0)return e.slice(0,t+6)+Le+e.slice(t+6);return Le+e}class u extends Error{code;i18n;constructor(e,t,n){super(t);this.code=e;this.i18n=n;this.name="MhError"}}function Wt(e,t){return t?e.replace(/\{(\w+)\}/g,(n,r)=>(r in t)?String(t[r]):n):e}function Ne(e,t,n){return new u(e,Wt(t,n),{key:t,params:n})}function Z(e,t){let n=e.transaction(t);return(n.immediate??n)()}function P(e=6){let t=crypto.getRandomValues(new Uint8Array(e)),n="";for(let r of t)n+="0123456789abcdefghijklmnopqrstuvwxyz".charAt(r%36);return n}function ee(e,t="item"){return e.normalize("NFKD").replace(/[^\x20-\x7E]/g,"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,32).replace(/-+$/g,"")||t}var Ut=new Set(["db","prop","rec","doc","blk","site","sf","chan"]);function _(e,t,n=e){return`${e}_${ee(t,n)}-${P()}`}function A(e){let t=e.indexOf("_");if(t<=0)return null;let n=e.slice(0,t);return Ut.has(n)?n:null}function $e(e){let t=e.query("SELECT value FROM meta WHERE key = 'node_id'").get();if(t)return t.value;let n=P(8);return e.query("INSERT INTO meta (key, value) VALUES ('node_id', ?)").run(n),n}var ne=65535,qt=300000;function Me(e,t){if(t<=ne)return{millis:e,counter:t};return{millis:e+Math.floor(t/(ne+1)),counter:t%(ne+1)}}function Ie(e){let{millis:t,counter:n}=Me(e.millis,e.counter);return`${String(t).padStart(15,"0")}-${n.toString(16).padStart(4,"0")}-${e.node}`}function Gt(e){let t=e.indexOf("-"),n=e.indexOf("-",t+1);return{millis:Number(e.slice(0,t)),counter:parseInt(e.slice(t+1,n),16),node:e.slice(n+1)}}function jt(e,t){let n=e.query("SELECT value FROM meta WHERE key = 'hlc'").get();return n?Gt(n.value):{millis:0,counter:0,node:t}}function zt(e,t){e.query("INSERT INTO meta (key, value) VALUES ('hlc', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(Ie(t))}function Fe(e,t,n=Date.now()){let r=jt(e,t);if(r.millis>n+qt)throw Ne("clock_skew","本机逻辑时钟比系统时间超前 {min} 分钟(系统时钟曾被调错,或收到过时钟错误设备的数据),运行 mh repair --clock 修复",{min:Math.ceil((r.millis-n)/60000)});let o=Math.max(r.millis,n),i=o===r.millis?r.counter+1:0,s={...Me(o,i),node:t};return zt(e,s),Ie(s)}function He(e){let t=e.filter((r)=>!!r.text&&r.text.length>0),n=[];return t.forEach((r,o)=>{n.push(...r.text.split(`
10
13
  `));let i=Math.max(0,r.blankAfter??0);if(o<t.length-1)n.push("");for(let s=0;s<i;s++)n.push("")}),n.join(`
11
- `)}var Wt=new WeakMap;function te(e,t){let n=e.docs.get(t);if(!n)return;e.docs.delete(t);for(let r of n.blockIds)if(e.blockToDoc.get(r)===t)e.blockToDoc.delete(r)}function $e(e,t,n,r,o){let i=Wt.get(e);if(!i)return;if(t==="documents"){te(i,n);return}if(t!=="doc_blocks")return;let s=i.blockToDoc.get(n);if(s)te(i,s);if(r==="doc_id"&&o)try{let a=JSON.parse(o);if(typeof a==="string")te(i,a)}catch{}}var Me="intent_receipts";var Ut=["hlc","node_id","dataset","row_id","col","value","txn"];var qr=Ut.join(", "),M=null,Ie=null;var qt=null;function Z(e,t,n){if(M!==null)return n();M=(Ie?Ie+"/":"")+(t?t+":":"")+N(8);try{return Q(e,n)}finally{M=null}}function m(e,t=null){return(n,...r)=>Z(n,t,()=>e(n,...r))}var Gt={databases:{table:"databases",cols:new Set(["name","icon","meta","created_hlc","__deleted"])},properties:{table:"properties",cols:new Set(["database_id","name","type","config","position","__deleted"])},documents:{table:"documents",cols:new Set(["title","body","database_id","parent_id","created_hlc","order_key","__deleted"])},doc_blocks:{table:"doc_blocks",cols:new Set(["doc_id","text","order_key","blank_after","__deleted"])},sites:{table:"sites",cols:new Set(["name","title","created_hlc","visibility","spa","public_grants","__deleted"])},site_channels:{table:"site_channels",cols:new Set(["site_id","audience","hosting","controller_node_id","target_ref","canonical_url","policy_json","desired_state","created_hlc","__deleted"])},site_files:{table:"site_files",cols:new Set(["site_id","path","content_type","encoding","content","created_hlc","__deleted"])},blob_policy:{table:"blob_policy",cols:new Set(["full_nodes","redundancy","__deleted"])},nodes:{table:"nodes",cols:new Set(["label","platform","form","app","first_seen","__deleted"])}},jt=new Set(["database_id","created_hlc","order_key","__deleted"]),zt=new Set([Me]),Kt=new Set(["records:__deleted","databases:__deleted","properties:__deleted","documents:__deleted","doc_blocks:__deleted","doc_blocks:blank_after","sites:__deleted","sites:spa","site_channels:__deleted","site_files:__deleted","blob_policy:__deleted","nodes:__deleted"]);function Fe(e,t,n){if(n!==null)return JSON.parse(n);return Kt.has(`${e}:${t}`)?0:null}function He(e){if(e===null||e===void 0)return null;if(typeof e==="boolean")return e?1:0;if(typeof e==="number")return e;if(typeof e==="string")return e;return JSON.stringify(e)}function P(e,t,n){e.query(`INSERT OR IGNORE INTO ${t} (id) VALUES (?)`).run(n)}function Vt(e,t){return e.query("SELECT 1 AS x FROM doc_blocks WHERE doc_id = ? LIMIT 1").get(t)!=null}function Yt(e,t){let n=e.query("SELECT text, blank_after FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(t);P(e,"documents",t),e.query("UPDATE documents SET body = ? WHERE id = ?").run(Pe(n.map((r)=>({text:r.text,blankAfter:r.blank_after??0}))),t)}function Jt(e,t,n,r,o){if(zt.has(t))return;if(t==="records"){if(jt.has(r)){P(e,"records",n);let a=Fe("records",r,o);e.query(`UPDATE records SET "${r}" = ? WHERE id = ?`).run(He(a),n)}else if(P(e,"records",n),o===null)e.query(`UPDATE records SET data = json_remove(data, '$."' || ? || '"') WHERE id = ?`).run(r,n);else e.query(`UPDATE records SET data = json_set(coalesce(data, '{}'), '$."' || ? || '"', json(?)) WHERE id = ?`).run(r,o,n);return}if(t==="databases"&&r.startsWith("meta.")){P(e,"databases",n);let a=r.slice(5);if(o===null)e.query(`UPDATE databases SET meta = json_remove(coalesce(meta, '{}'), '$."' || ? || '"') WHERE id = ?`).run(a,n);else e.query(`UPDATE databases SET meta = json_set(coalesce(meta, '{}'), '$."' || ? || '"', json(?)) WHERE id = ?`).run(a,o,n);return}let i=Gt[t];if(!i||!i.cols.has(r))return;if(t==="documents"&&r==="body"&&Vt(e,n))return;P(e,i.table,n);let s=Fe(t,r,o);if(e.query(`UPDATE ${i.table} SET "${r}" = ? WHERE id = ?`).run(He(s),n),t==="doc_blocks"){let a=e.query("SELECT doc_id FROM doc_blocks WHERE id = ?").get(n);if(a?.doc_id)Yt(e,a.doc_id)}}function Qt(e,t){let n=e.query("INSERT OR IGNORE INTO crdt_changes (hlc, node_id, dataset, row_id, col, value, txn) VALUES (?, ?, ?, ?, ?, ?, ?)").run(t.hlc,t.node_id,t.dataset,t.row_id,t.col,t.value,t.txn??null).changes>0,r=e.query("SELECT MAX(hlc) AS h FROM crdt_changes WHERE dataset = ? AND row_id = ? AND col = ?").get(t.dataset,t.row_id,t.col);if(n)$e(e,t.dataset,t.row_id,t.col,t.value);if(r.h!==t.hlc)return{inserted:n,winner:!1};try{Jt(e,t.dataset,t.row_id,t.col,t.value)}catch(o){if(n)e.query("DELETE FROM crdt_changes WHERE dataset = ? AND row_id = ? AND col = ? AND hlc = ?").run(t.dataset,t.row_id,t.col,t.hlc);throw o}return{inserted:n,winner:!0}}function h(e,t,n,r,o){let i=()=>{let s=qt??Ae(e),a={hlc:Ne(e,s),node_id:s,dataset:t,row_id:n,col:r,value:o===void 0?null:JSON.stringify(o),txn:M};return Qt(e,a),a};return M!==null?i():Q(e,i)}function Be(e){return e.replace(/[^a-zA-Z0-9_]/g,"_")}function We(e){return e.replace(/'/g,"''")}function Ue(e,t){return`idx_rec_${Be(e)}_${Be(t)}`}function Xt(e,t,n){return e.query("SELECT 1 AS ok FROM sqlite_master WHERE type = 'index' AND name = ?").get(Ue(t,n))!=null}function ne(e,t,n){let r=Ue(t,n);e.exec(`CREATE INDEX IF NOT EXISTS "${r}" ON records (data ->> '${We(n)}', created_hlc) WHERE database_id = '${We(t)}' AND __deleted = 0`)}function re(e,t,n){if(Xt(e,t,n.id))return;if(n.type!=="relation"&&n.type!=="doc"){if(e.query("SELECT COUNT(*) AS c FROM records WHERE database_id = ? AND __deleted = 0").get(t).c<2000)return}ne(e,t,n.id)}var qe=new Set(["text","number","checkbox","select","multi_select","date","relation","doc","url"]),Zt=["id","database_id","name","type","config","position"];var Ge=Zt.join(", ");function oe(e,t){if(e==="select"||e==="multi_select"){let n=t?.options;if(!Array.isArray(n)||n.length===0||!n.every((r)=>typeof r==="string"))throw new u("invalid_input",`${e} requires config.options: string[]`)}if(e==="relation"){if(typeof t?.database!=="string")throw new u("invalid_input","relation requires config.database (target database id)")}}function en(e,t){return(e.query("SELECT MAX(position) AS m FROM properties WHERE database_id = ? AND __deleted = 0").get(t).m??0)+1}var je=m(function(t,n,r){if(!w(t,n))throw new u("not_found",`no such database: ${n}`);if(!qe.has(r.type))throw new u("invalid_input",`unknown property type: ${r.type}`);oe(r.type,r.config);let o=r.indexed?{...r.config??{},indexed:!0}:r.config??null,i=_("prop",r.name);if(h(t,"properties",i,"database_id",n),h(t,"properties",i,"name",r.name),h(t,"properties",i,"type",r.type),h(t,"properties",i,"config",o),h(t,"properties",i,"position",r.position??en(t,n)),r.type==="relation"||r.type==="doc"||r.indexed===!0)ne(t,n,i);return x(t,i)});function x(e,t){let n=e.query(`SELECT ${Ge} FROM properties WHERE id = ? AND __deleted = 0`).get(t);if(!n)return null;return{...n,config:n.config?JSON.parse(n.config):null}}function T(e,t){return e.query(`SELECT ${Ge} FROM properties WHERE database_id = ? AND __deleted = 0 ORDER BY position`).all(t).map((r)=>({...r,config:r.config?JSON.parse(r.config):null}))}var Qr=m(function(t,n,r){let o=x(t,n);if(!o)throw new u("not_found",`no such property: ${n}`);let i=r.type!==void 0&&r.type!==o.type;if(r.type!==void 0&&!qe.has(r.type))throw new u("invalid_input",`unknown property type: ${r.type}`);let s=r.type??o.type,a;if(r.config!==void 0){let d={...o.config??{}};for(let[c,p]of Object.entries(r.config))if(p===null)delete d[c];else d[c]=p;a=d,oe(s,a)}else if(i)oe(r.type,o.config??void 0);if(r.name!==void 0)h(t,"properties",n,"name",r.name);if(r.type!==void 0)h(t,"properties",n,"type",r.type);if(a!==void 0)h(t,"properties",n,"config",a);if(r.position!==void 0)h(t,"properties",n,"position",r.position);if(i){let d=t.query("SELECT id FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? IS NOT NULL").all(o.database_id,n);for(let c of d)h(t,"records",c.id,n,null)}return x(t,n)});function ze(e,t){let n=x(e,t);if(!n)throw new u("not_found",`no such property: ${t}`);if(n.type!=="select"&&n.type!=="multi_select")throw new u("invalid_input",`${n.type} property has no options`);return{prop:n,options:n.config?.options??[]}}var Xr=m(function(t,n,r,o){let{prop:i,options:s}=ze(t,n);if(o=o.trim(),!o)throw new u("invalid_input","option name must not be empty");if(!s.includes(r))throw new u("not_found",`no such option: ${r}`);if(o===r)return{property:i,renamed:0};if(s.includes(o))throw new u("conflict",`option already exists: ${o}`);h(t,"properties",n,"config",{...i.config??{},options:s.map((d)=>d===r?o:d)});let a=0;if(i.type==="select"){let d=t.query("SELECT id FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? = ?").all(i.database_id,n,r);for(let c of d)h(t,"records",c.id,n,o);a=d.length}else{let d=t.query("SELECT id, data ->> ? AS v FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? IS NOT NULL").all(n,i.database_id,n);for(let c of d){let p=JSON.parse(c.v);if(!Array.isArray(p)||!p.includes(r))continue;h(t,"records",c.id,n,[...new Set(p.map((g)=>g===r?o:g))]),a++}}return{property:x(t,n),renamed:a}}),Zr=m(function(t,n,r){let{prop:o,options:i}=ze(t,n);if(!i.includes(r))throw new u("not_found",`no such option: ${r}`);if(i.length===1)throw new u("invalid_input","cannot remove the last option; delete the property instead");h(t,"properties",n,"config",{...o.config??{},options:i.filter((a)=>a!==r)});let s=0;if(o.type==="select"){let a=t.query("SELECT id FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? = ?").all(o.database_id,n,r);for(let d of a)h(t,"records",d.id,n,null);s=a.length}else{let a=t.query("SELECT id, data ->> ? AS v FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? IS NOT NULL").all(n,o.database_id,n);for(let d of a){let c=JSON.parse(d.v);if(!Array.isArray(c)||!c.includes(r))continue;h(t,"records",d.id,n,c.filter((p)=>p!==r)),s++}}return{property:x(t,n),cleared:s}}),eo=m(function(t,n){let r=x(t,n);if(!r)return!1;h(t,"properties",n,"__deleted",1);let o=t.query("SELECT id FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? IS NOT NULL").all(r.database_id,n);for(let i of o)h(t,"records",i.id,n,void 0);return!0});var tn={db:{table:"databases",nameCol:"name",scoped:!1,noun:"database"},prop:{table:"properties",nameCol:"name",scoped:!0,noun:"property"},rec:{table:"records",nameCol:null,scoped:!0,noun:"record"},doc:{table:"documents",nameCol:"title",scoped:!0,noun:"document"}},nn=["db","prop","rec","doc"];function rn(e){return e!==null&&e!=="blk"}var Ke="{";function Ve(e,t){return e.query("SELECT id FROM properties WHERE database_id = ? AND type = 'text' AND __deleted = 0 ORDER BY position LIMIT 1").get(t)?.id??null}function on(e,t,n,r){let o=tn[t],i=o.nameCol;if(t==="rec"&&r){let y=Ve(e,r);if(y)i=`data ->> '${y.replace(/'/g,"''")}'`}let s=i??"''",a=[],d=[];a.push("(id >= ? AND id < ?)"),d.push(n,n+Ke);let c=`${t}_${n}`;if(a.push("(id >= ? AND id < ?)"),d.push(c,c+Ke),i)a.push(`lower(${i}) = lower(?)`),d.push(n);let p=`SELECT id, ${s} AS label FROM ${o.table} WHERE __deleted = 0 AND (${a.join(" OR ")})`;if(o.scoped&&r)p+=" AND database_id = ?",d.push(r);return e.query(p).all(...d).map((y)=>({kind:t,id:y.id,label:y.label??""}))}function ie(e,t,n={}){let r=n.kind?[n.kind]:rn(A(t))?[A(t)]:nn,o=[],i=new Set;for(let s of r)for(let a of on(e,s,t,n.databaseId)){if(i.has(a.id))continue;i.add(a.id),o.push(a)}return o}function se(e,t){if(t!==null&&e>=t)throw Error(`fracdex: ${e} >= ${t}`);if(e.endsWith("0")||t!==null&&t.endsWith("0"))throw Error(`fracdex: trailing zero in ${e}/${t}`);if(t!==null){let i=0;while((e[i]??"0")===t[i])i++;if(i>0)return t.slice(0,i)+se(e.slice(i),t.slice(i))}let r=e===""?0:"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".indexOf(e[0]),o=t===null?62:"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".indexOf(t[0]);if(o-r>1)return"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[Math.round(0.5*(r+o))];if(t!==null&&t.length>1)return t.slice(0,1);return"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[r]+se(e.slice(1),null)}function k(e,t){return se(e??"",t)}function I(e,t,n){if(n<=0)return[];if(n===1)return[k(e,t)];if(t===null){let i=k(e,null),s=[i];for(let a=1;a<n;a++)i=k(i,null),s.push(i);return s}if(e===null){let i=k(null,t),s=[i];for(let a=1;a<n;a++)i=k(null,i),s.unshift(i);return s}let r=Math.floor(n/2),o=k(e,t);return[...I(e,o,r),o,...I(o,t,n-r-1)]}function Ye(e){return typeof e==="string"||typeof e==="number"||typeof e==="boolean"}function sn(e){return typeof e==="boolean"?e?1:0:e}function an(e,t,n){let r=t.config?.database;if(!r)return n;let o=ie(e,n,{kind:"rec",databaseId:r}),i=o.find((s)=>s.id===n);if(i)return i.id;if(o.length===1)return o[0].id;if(o.length===0){if(A(n)==="rec")return n;throw new u("not_found",`${t.name}: no such record in target database: ${n}`)}throw new u("ambiguous",`${t.name}: ambiguous relation "${n}" (${o.length} matches); use a full id`)}function ln(e,t,n){let r=ie(e,n,{kind:"doc"}),o=r.find((i)=>i.id===n);if(o)return o.id;if(r.length===1)return r[0].id;if(r.length===0){if(A(n)==="doc")return n;throw new u("not_found",`${t.name}: no such document: ${n}`)}throw new u("ambiguous",`${t.name}: ambiguous document "${n}" (${r.length} matches); use a full id`)}function le(e,t,n){switch(t.type){case"text":case"url":case"date":{if(n===null)return null;if(typeof n!=="string")throw new u("invalid_input",`${t.name} expects a string`);return n}case"number":{if(n===null)return null;let r=typeof n==="number"?n:Number(n);if(!Number.isFinite(r))throw new u("invalid_input",`${t.name} expects a number`);return r}case"checkbox":{if(typeof n==="boolean")return n;if(n==="true"||n===1)return!0;if(n==="false"||n===0||n===null)return!1;throw new u("invalid_input",`${t.name} expects a boolean`)}case"select":{if(n===null)return null;let r=String(n);if(!t.config?.options?.includes(r))throw new u("invalid_input",`${t.name}: '${r}' is not an allowed option`);return r}case"multi_select":{let r=n===null?[]:Array.isArray(n)?n:[n],o=t.config?.options??[];for(let i of r)if(!o.includes(String(i)))throw new u("invalid_input",`${t.name}: '${i}' is not an allowed option`);return r.map(String)}case"relation":return(n===null?[]:Array.isArray(n)?n:[n]).map((o)=>an(e,t,String(o)));case"doc":return(n===null?[]:Array.isArray(n)?n:[n]).map((o)=>ln(e,t,String(o)))}}function cn(e){let t=new Map;for(let n of e){let r=n.name.toLowerCase(),o=t.get(r);if(o)o.push(n);else t.set(r,[n])}return t}function ce(e,t){let n=new Map(e.map((i)=>[i.id,i])),r=cn(e),o=[];for(let[i,s]of Object.entries(t)){let a=n.get(i);if(!a){let d=r.get(i.toLowerCase())??[];if(d.length>1)throw new u("ambiguous",`property name "${i}" matches ${d.length} properties; use a property id`);a=d[0]}if(!a)throw new u("not_found",`unknown property: ${i}`);o.push({prop:a,value:s})}return o}function Xe(e,t,n){return ce(T(e,t),n).map(({prop:r,value:o})=>({prop:r,value:le(e,r,o)}))}function Ze(e,t){let n=JSON.parse(e.data||"{}"),r=new Map(t.map((s)=>[s.id,s])),o={},i={};for(let[s,a]of Object.entries(n)){let d=r.get(s);if(!d)continue;o[d.name]=a,i[d.id]=a}return{id:e.id,database_id:e.database_id,values:o,cells:i}}function dn(e,t){let n=e.find((r)=>r.prop.type==="text"&&typeof r.value==="string"&&r.value);return _("rec",n?String(n.value):"",t)}function Je(e,t){return e.query("SELECT 1 AS ok FROM sqlite_master WHERE type = 'table' AND name = ?").get(t)!=null}function un(e){return Je(e,"meta")&&Je(e,"crdt_changes")}function pn(e,t,n,r){if(r)h(e,"records",t,"order_key",n);else e.query("UPDATE records SET order_key = ? WHERE id = ?").run(n,t)}function et(e,t){return e.query(`SELECT order_key FROM records
14
+ `)}var Kt=new WeakMap;function re(e,t){let n=e.docs.get(t);if(!n)return;e.docs.delete(t);for(let r of n.blockIds)if(e.blockToDoc.get(r)===t)e.blockToDoc.delete(r)}function Be(e,t,n,r,o){let i=Kt.get(e);if(!i)return;if(t==="documents"){re(i,n);return}if(t!=="doc_blocks")return;let s=i.blockToDoc.get(n);if(s)re(i,s);if(r==="doc_id"&&o)try{let a=JSON.parse(o);if(typeof a==="string")re(i,a)}catch{}}var We="intent_receipts";var Vt=["hlc","node_id","dataset","row_id","col","value","txn"];var Jr=Vt.join(", "),M=null,Ue=null;var Yt=null;function te(e,t,n){if(M!==null)return n();M=(Ue?Ue+"/":"")+(t?t+":":"")+P(8);try{return Z(e,n)}finally{M=null}}function m(e,t=null){return(n,...r)=>te(n,t,()=>e(n,...r))}var Jt={databases:{table:"databases",cols:new Set(["name","icon","meta","created_hlc","__deleted"])},properties:{table:"properties",cols:new Set(["database_id","name","type","config","position","__deleted"])},documents:{table:"documents",cols:new Set(["title","body","database_id","parent_id","created_hlc","order_key","__deleted"])},doc_blocks:{table:"doc_blocks",cols:new Set(["doc_id","text","order_key","blank_after","__deleted"])},sites:{table:"sites",cols:new Set(["name","title","created_hlc","visibility","spa","public_grants","__deleted"])},site_channels:{table:"site_channels",cols:new Set(["site_id","audience","hosting","controller_node_id","target_ref","canonical_url","policy_json","desired_state","created_hlc","__deleted"])},site_files:{table:"site_files",cols:new Set(["site_id","path","content_type","encoding","content","created_hlc","__deleted"])},blob_policy:{table:"blob_policy",cols:new Set(["full_nodes","redundancy","__deleted"])},nodes:{table:"nodes",cols:new Set(["label","platform","form","app","first_seen","__deleted"])}},Qt=new Set(["database_id","created_hlc","order_key","__deleted"]),Xt=new Set([We]),Zt=new Set(["records:__deleted","databases:__deleted","properties:__deleted","documents:__deleted","doc_blocks:__deleted","doc_blocks:blank_after","sites:__deleted","sites:spa","site_channels:__deleted","site_files:__deleted","blob_policy:__deleted","nodes:__deleted"]);function qe(e,t,n){if(n!==null)return JSON.parse(n);return Zt.has(`${e}:${t}`)?0:null}function Ge(e){if(e===null||e===void 0)return null;if(typeof e==="boolean")return e?1:0;if(typeof e==="number")return e;if(typeof e==="string")return e;return JSON.stringify(e)}function N(e,t,n){e.query(`INSERT OR IGNORE INTO ${t} (id) VALUES (?)`).run(n)}function en(e,t){return e.query("SELECT 1 AS x FROM doc_blocks WHERE doc_id = ? LIMIT 1").get(t)!=null}function tn(e,t){let n=e.query("SELECT text, blank_after FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(t);N(e,"documents",t),e.query("UPDATE documents SET body = ? WHERE id = ?").run(He(n.map((r)=>({text:r.text,blankAfter:r.blank_after??0}))),t)}function nn(e,t,n,r,o){if(Xt.has(t))return;if(t==="records"){if(Qt.has(r)){N(e,"records",n);let a=qe("records",r,o);e.query(`UPDATE records SET "${r}" = ? WHERE id = ?`).run(Ge(a),n)}else if(N(e,"records",n),o===null)e.query(`UPDATE records SET data = json_remove(data, '$."' || ? || '"') WHERE id = ?`).run(r,n);else e.query(`UPDATE records SET data = json_set(coalesce(data, '{}'), '$."' || ? || '"', json(?)) WHERE id = ?`).run(r,o,n);return}if(t==="databases"&&r.startsWith("meta.")){N(e,"databases",n);let a=r.slice(5);if(o===null)e.query(`UPDATE databases SET meta = json_remove(coalesce(meta, '{}'), '$."' || ? || '"') WHERE id = ?`).run(a,n);else e.query(`UPDATE databases SET meta = json_set(coalesce(meta, '{}'), '$."' || ? || '"', json(?)) WHERE id = ?`).run(a,o,n);return}let i=Jt[t];if(!i||!i.cols.has(r))return;if(t==="documents"&&r==="body"&&en(e,n))return;N(e,i.table,n);let s=qe(t,r,o);if(e.query(`UPDATE ${i.table} SET "${r}" = ? WHERE id = ?`).run(Ge(s),n),t==="doc_blocks"){let a=e.query("SELECT doc_id FROM doc_blocks WHERE id = ?").get(n);if(a?.doc_id)tn(e,a.doc_id)}}function rn(e,t){let n=e.query("INSERT OR IGNORE INTO crdt_changes (hlc, node_id, dataset, row_id, col, value, txn) VALUES (?, ?, ?, ?, ?, ?, ?)").run(t.hlc,t.node_id,t.dataset,t.row_id,t.col,t.value,t.txn??null).changes>0,r=e.query("SELECT MAX(hlc) AS h FROM crdt_changes WHERE dataset = ? AND row_id = ? AND col = ?").get(t.dataset,t.row_id,t.col);if(n)Be(e,t.dataset,t.row_id,t.col,t.value);if(r.h!==t.hlc)return{inserted:n,winner:!1};try{nn(e,t.dataset,t.row_id,t.col,t.value)}catch(o){if(n)e.query("DELETE FROM crdt_changes WHERE dataset = ? AND row_id = ? AND col = ? AND hlc = ?").run(t.dataset,t.row_id,t.col,t.hlc);throw o}return{inserted:n,winner:!0}}function h(e,t,n,r,o){let i=()=>{let s=Yt??$e(e),a={hlc:Fe(e,s),node_id:s,dataset:t,row_id:n,col:r,value:o===void 0?null:JSON.stringify(o),txn:M};return rn(e,a),a};return M!==null?i():Z(e,i)}function je(e,t,n,r){if(!Array.isArray(e))return e;let o=(s)=>!!s&&typeof s==="object"&&!Array.isArray(s),i=(s)=>typeof s==="string"?t.get(s)??s:s;return e.map((s)=>{if(!o(s))return s;let a={...s};for(let c of["group","dateProp","start","end"])if(c in a)a[c]=i(a[c]);if(Array.isArray(s.hidden))a.hidden=s.hidden.map(i);if(Array.isArray(s.sort))a.sort=s.sort.map((c)=>o(c)?{...c,prop:i(c.prop)}:c);if(o(s.filter)&&Array.isArray(s.filter.rules))a.filter={...s.filter,rules:s.filter.rules.map((c)=>{if(!o(c))return c;return{...c,prop:i(c.prop),...typeof c.prop==="string"&&r.has(c.prop)&&Array.isArray(c.value)?{value:c.value.map((l)=>typeof l==="string"?n.get(l)??l:l)}:{}}})};if(o(s.calc))a.calc=Object.fromEntries(Object.entries(s.calc).map(([c,l])=>[t.get(c)??c,l]));return a})}function ze(e){return e.replace(/[^a-zA-Z0-9_]/g,"_")}function Ke(e){return e.replace(/'/g,"''")}function Ve(e,t){return`idx_rec_${ze(e)}_${ze(t)}`}function on(e,t,n){return e.query("SELECT 1 AS ok FROM sqlite_master WHERE type = 'index' AND name = ?").get(Ve(t,n))!=null}function oe(e,t,n){let r=Ve(t,n);e.exec(`CREATE INDEX IF NOT EXISTS "${r}" ON records (data ->> '${Ke(n)}', created_hlc) WHERE database_id = '${Ke(t)}' AND __deleted = 0`)}function ie(e,t,n){if(on(e,t,n.id))return;if(n.type!=="relation"&&n.type!=="doc"){if(e.query("SELECT COUNT(*) AS c FROM records WHERE database_id = ? AND __deleted = 0").get(t).c<2000)return}oe(e,t,n.id)}var Ye=new Set(["text","number","checkbox","select","multi_select","date","relation","doc","url"]),sn=["id","database_id","name","type","config","position"];var Je=sn.join(", ");function se(e,t){if(e==="select"||e==="multi_select"){let n=t?.options;if(!Array.isArray(n)||n.length===0||!n.every((r)=>typeof r==="string"))throw new u("invalid_input",`${e} requires config.options: string[]`)}if(e==="relation"){if(typeof t?.database!=="string")throw new u("invalid_input","relation requires config.database (target database id)")}}function an(e,t){return(e.query("SELECT MAX(position) AS m FROM properties WHERE database_id = ? AND __deleted = 0").get(t).m??0)+1}var Qe=m(function(t,n,r){if(!w(t,n))throw new u("not_found",`no such database: ${n}`);if(!Ye.has(r.type))throw new u("invalid_input",`unknown property type: ${r.type}`);se(r.type,r.config);let o=r.indexed?{...r.config??{},indexed:!0}:r.config??null,i=_("prop",r.name);if(h(t,"properties",i,"database_id",n),h(t,"properties",i,"name",r.name),h(t,"properties",i,"type",r.type),h(t,"properties",i,"config",o),h(t,"properties",i,"position",r.position??an(t,n)),r.type==="relation"||r.type==="doc"||r.indexed===!0)oe(t,n,i);return T(t,i)});function T(e,t){let n=e.query(`SELECT ${Je} FROM properties WHERE id = ? AND __deleted = 0`).get(t);if(!n)return null;return{...n,config:n.config?JSON.parse(n.config):null}}function x(e,t){return e.query(`SELECT ${Je} FROM properties WHERE database_id = ? AND __deleted = 0 ORDER BY position`).all(t).map((r)=>({...r,config:r.config?JSON.parse(r.config):null}))}var io=m(function(t,n,r){let o=T(t,n);if(!o)throw new u("not_found",`no such property: ${n}`);let i=r.type!==void 0&&r.type!==o.type;if(r.type!==void 0&&!Ye.has(r.type))throw new u("invalid_input",`unknown property type: ${r.type}`);let s=r.type??o.type,a;if(r.config!==void 0){let c={...o.config??{}};for(let[l,p]of Object.entries(r.config))if(p===null)delete c[l];else c[l]=p;a=c,se(s,a)}else if(i)se(r.type,o.config??void 0);if(r.name!==void 0)h(t,"properties",n,"name",r.name);if(r.type!==void 0)h(t,"properties",n,"type",r.type);if(a!==void 0)h(t,"properties",n,"config",a);if(r.position!==void 0)h(t,"properties",n,"position",r.position);if(i){let c=t.query("SELECT id FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? IS NOT NULL").all(o.database_id,n);for(let l of c)h(t,"records",l.id,n,null)}return T(t,n)});function Xe(e,t){let n=T(e,t);if(!n)throw new u("not_found",`no such property: ${t}`);if(n.type!=="select"&&n.type!=="multi_select")throw new u("invalid_input",`${n.type} property has no options`);return{prop:n,options:n.config?.options??[]}}var so=m(function(t,n,r,o){let{prop:i,options:s}=Xe(t,n);if(o=o.trim(),!o)throw new u("invalid_input","option name must not be empty");if(!s.includes(r))throw new u("not_found",`no such option: ${r}`);if(o===r)return{property:i,renamed:0};if(s.includes(o))throw new u("conflict",`option already exists: ${o}`);h(t,"properties",n,"config",{...i.config??{},options:s.map((c)=>c===r?o:c)});let a=0;if(i.type==="select"){let c=t.query("SELECT id FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? = ?").all(i.database_id,n,r);for(let l of c)h(t,"records",l.id,n,o);a=c.length}else{let c=t.query("SELECT id, data ->> ? AS v FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? IS NOT NULL").all(n,i.database_id,n);for(let l of c){let p=JSON.parse(l.v);if(!Array.isArray(p)||!p.includes(r))continue;h(t,"records",l.id,n,[...new Set(p.map((f)=>f===r?o:f))]),a++}}return{property:T(t,n),renamed:a}}),ao=m(function(t,n,r){let{prop:o,options:i}=Xe(t,n);if(!i.includes(r))throw new u("not_found",`no such option: ${r}`);if(i.length===1)throw new u("invalid_input","cannot remove the last option; delete the property instead");h(t,"properties",n,"config",{...o.config??{},options:i.filter((a)=>a!==r)});let s=0;if(o.type==="select"){let a=t.query("SELECT id FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? = ?").all(o.database_id,n,r);for(let c of a)h(t,"records",c.id,n,null);s=a.length}else{let a=t.query("SELECT id, data ->> ? AS v FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? IS NOT NULL").all(n,o.database_id,n);for(let c of a){let l=JSON.parse(c.v);if(!Array.isArray(l)||!l.includes(r))continue;h(t,"records",c.id,n,l.filter((p)=>p!==r)),s++}}return{property:T(t,n),cleared:s}}),co=m(function(t,n){let r=T(t,n);if(!r)return!1;h(t,"properties",n,"__deleted",1);let o=t.query("SELECT id FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? IS NOT NULL").all(r.database_id,n);for(let i of o)h(t,"records",i.id,n,void 0);return!0});var cn={db:{table:"databases",nameCol:"name",scoped:!1,noun:"database"},prop:{table:"properties",nameCol:"name",scoped:!0,noun:"property"},rec:{table:"records",nameCol:null,scoped:!0,noun:"record"},doc:{table:"documents",nameCol:"title",scoped:!0,noun:"document"}},ln=["db","prop","rec","doc"];function dn(e){return e!==null&&e!=="blk"}var Ze="{";function et(e,t){return e.query("SELECT id FROM properties WHERE database_id = ? AND type = 'text' AND __deleted = 0 ORDER BY position LIMIT 1").get(t)?.id??null}function un(e,t,n,r){let o=cn[t],i=o.nameCol;if(t==="rec"&&r){let y=et(e,r);if(y)i=`data ->> '${y.replace(/'/g,"''")}'`}let s=i??"''",a=[],c=[];a.push("(id >= ? AND id < ?)"),c.push(n,n+Ze);let l=`${t}_${n}`;if(a.push("(id >= ? AND id < ?)"),c.push(l,l+Ze),i)a.push(`lower(${i}) = lower(?)`),c.push(n);let p=`SELECT id, ${s} AS label FROM ${o.table} WHERE __deleted = 0 AND (${a.join(" OR ")})`;if(o.scoped&&r)p+=" AND database_id = ?",c.push(r);return e.query(p).all(...c).map((y)=>({kind:t,id:y.id,label:y.label??""}))}function ae(e,t,n={}){let r=n.kind?[n.kind]:dn(A(t))?[A(t)]:ln,o=[],i=new Set;for(let s of r)for(let a of un(e,s,t,n.databaseId)){if(i.has(a.id))continue;i.add(a.id),o.push(a)}return o}function ce(e,t){if(t!==null&&e>=t)throw Error(`fracdex: ${e} >= ${t}`);if(e.endsWith("0")||t!==null&&t.endsWith("0"))throw Error(`fracdex: trailing zero in ${e}/${t}`);if(t!==null){let i=0;while((e[i]??"0")===t[i])i++;if(i>0)return t.slice(0,i)+ce(e.slice(i),t.slice(i))}let r=e===""?0:"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".indexOf(e[0]),o=t===null?62:"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".indexOf(t[0]);if(o-r>1)return"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[Math.round(0.5*(r+o))];if(t!==null&&t.length>1)return t.slice(0,1);return"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[r]+ce(e.slice(1),null)}function k(e,t){return ce(e??"",t)}function I(e,t,n){if(n<=0)return[];if(n===1)return[k(e,t)];if(t===null){let i=k(e,null),s=[i];for(let a=1;a<n;a++)i=k(i,null),s.push(i);return s}if(e===null){let i=k(null,t),s=[i];for(let a=1;a<n;a++)i=k(null,i),s.unshift(i);return s}let r=Math.floor(n/2),o=k(e,t);return[...I(e,o,r),o,...I(o,t,n-r-1)]}function tt(e){return typeof e==="string"||typeof e==="number"||typeof e==="boolean"}function pn(e){return typeof e==="boolean"?e?1:0:e}function hn(e,t,n){let r=t.config?.database;if(!r)return n;let o=ae(e,n,{kind:"rec",databaseId:r}),i=o.find((s)=>s.id===n);if(i)return i.id;if(o.length===1)return o[0].id;if(o.length===0){if(A(n)==="rec")return n;throw new u("not_found",`${t.name}: no such record in target database: ${n}`)}throw new u("ambiguous",`${t.name}: ambiguous relation "${n}" (${o.length} matches); use a full id`)}function fn(e,t,n){let r=ae(e,n,{kind:"doc"}),o=r.find((i)=>i.id===n);if(o)return o.id;if(r.length===1)return r[0].id;if(r.length===0){if(A(n)==="doc")return n;throw new u("not_found",`${t.name}: no such document: ${n}`)}throw new u("ambiguous",`${t.name}: ambiguous document "${n}" (${r.length} matches); use a full id`)}function de(e,t,n){switch(t.type){case"text":case"url":case"date":{if(n===null)return null;if(typeof n!=="string")throw new u("invalid_input",`${t.name} expects a string`);return n}case"number":{if(n===null)return null;let r=typeof n==="number"?n:Number(n);if(!Number.isFinite(r))throw new u("invalid_input",`${t.name} expects a number`);return r}case"checkbox":{if(typeof n==="boolean")return n;if(n==="true"||n===1)return!0;if(n==="false"||n===0||n===null)return!1;throw new u("invalid_input",`${t.name} expects a boolean`)}case"select":{if(n===null)return null;let r=String(n);if(!t.config?.options?.includes(r))throw new u("invalid_input",`${t.name}: '${r}' is not an allowed option`);return r}case"multi_select":{let r=n===null?[]:Array.isArray(n)?n:[n],o=t.config?.options??[];for(let i of r)if(!o.includes(String(i)))throw new u("invalid_input",`${t.name}: '${i}' is not an allowed option`);return r.map(String)}case"relation":return(n===null?[]:Array.isArray(n)?n:[n]).map((o)=>hn(e,t,String(o)));case"doc":return(n===null?[]:Array.isArray(n)?n:[n]).map((o)=>fn(e,t,String(o)))}}function gn(e){let t=new Map;for(let n of e){let r=n.name.toLowerCase(),o=t.get(r);if(o)o.push(n);else t.set(r,[n])}return t}function ue(e,t){let n=new Map(e.map((i)=>[i.id,i])),r=gn(e),o=[];for(let[i,s]of Object.entries(t)){let a=n.get(i);if(!a){let c=r.get(i.toLowerCase())??[];if(c.length>1)throw new u("ambiguous",`property name "${i}" matches ${c.length} properties; use a property id`);a=c[0]}if(!a)throw new u("not_found",`unknown property: ${i}`);o.push({prop:a,value:s})}return o}function ot(e,t,n){return ue(x(e,t),n).map(({prop:r,value:o})=>({prop:r,value:de(e,r,o)}))}function it(e,t){let n=JSON.parse(e.data||"{}"),r=new Map(t.map((s)=>[s.id,s])),o={},i={};for(let[s,a]of Object.entries(n)){let c=r.get(s);if(!c)continue;o[c.name]=a,i[c.id]=a}return{id:e.id,database_id:e.database_id,values:o,cells:i}}function mn(e,t){let n=e.find((r)=>r.prop.type==="text"&&typeof r.value==="string"&&r.value);return _("rec",n?String(n.value):"",t)}function nt(e,t){return e.query("SELECT 1 AS ok FROM sqlite_master WHERE type = 'table' AND name = ?").get(t)!=null}function yn(e){return nt(e,"meta")&&nt(e,"crdt_changes")}function bn(e,t,n,r){if(r)h(e,"records",t,"order_key",n);else e.query("UPDATE records SET order_key = ? WHERE id = ?").run(n,t)}function st(e,t){return e.query(`SELECT order_key FROM records
12
15
  WHERE database_id = ? AND __deleted = 0 AND order_key IS NOT NULL
13
- ORDER BY order_key DESC, id DESC LIMIT 1`).get(t)?.order_key??null}function ae(e,t){return e.query(`SELECT id, database_id, created_hlc, order_key FROM records
16
+ ORDER BY order_key DESC, id DESC LIMIT 1`).get(t)?.order_key??null}function le(e,t){return e.query(`SELECT id, database_id, created_hlc, order_key FROM records
14
17
  WHERE database_id = ? AND __deleted = 0
15
- ORDER BY order_key IS NULL, order_key, created_hlc, id`).all(t)}function hn(e,t){let n=ae(e,t),r=I(null,null,n.length);n.forEach((o,i)=>{if(o.order_key!==r[i])h(e,"records",o.id,"order_key",r[i])})}var gn=m(function(t,n){let r=un(t),o=n?[{database_id:n}]:t.query(`SELECT DISTINCT database_id FROM records
18
+ ORDER BY order_key IS NULL, order_key, created_hlc, id`).all(t)}function wn(e,t){let n=le(e,t),r=I(null,null,n.length);n.forEach((o,i)=>{if(o.order_key!==r[i])h(e,"records",o.id,"order_key",r[i])})}var vn=m(function(t,n){let r=yn(t),o=n?[{database_id:n}]:t.query(`SELECT DISTINCT database_id FROM records
16
19
  WHERE database_id IS NOT NULL AND __deleted = 0 AND order_key IS NULL`).all();for(let i of o){let s=t.query(`SELECT id FROM records
17
20
  WHERE database_id = ? AND __deleted = 0 AND order_key IS NULL
18
- ORDER BY created_hlc, id`).all(i.database_id);if(!s.length)continue;let a=et(t,i.database_id),d=I(a,null,s.length);s.forEach((c,p)=>pn(t,c.id,d[p],r))}}),tt=m(function(t,n,r){let o=dn(r,X(n.name,"rec")),i=k(et(t,n.id),null),s=h(t,"records",o,"database_id",n.id);h(t,"records",o,"created_hlc",s.hlc),h(t,"records",o,"order_key",i);for(let{prop:a,value:d}of r)h(t,"records",o,a.id,d);return F(t,o)}),nt=m(function(t,n,r){let o=w(t,n);if(!o)throw new u("not_found",`no such database: ${n}`);return tt(t,o,Xe(t,o.id,r))});function F(e,t){let n=e.query("SELECT id, database_id, data FROM records WHERE id = ? AND __deleted = 0").get(t);if(!n)return null;return Ze(n,T(e,n.database_id))}function Qe(e){return e.replace(/'/g,"''")}function rt(e,t,n={}){let r=T(e,t),o=n.filter&&Object.keys(n.filter).length?ce(r,n.filter).map(({prop:f,value:E})=>({prop:f,value:le(e,f,E)})):[],i=o.filter((f)=>Ye(f.value)),s=o.filter((f)=>!Ye(f.value)),a,d=!1;if(n.sort){let f=n.sort;if(f.startsWith("-"))d=!0,f=f.slice(1);if(f!=="created"&&f!=="created_hlc"){let E=r.find((L)=>L.id===f);if(!E){let L=r.filter((kt)=>kt.name.toLowerCase()===f.toLowerCase());if(L.length>1)throw new u("ambiguous",`sort field "${f}" matches ${L.length} properties; use a property id`);E=L[0]}if(!E)throw new u("not_found",`unknown sort field: ${f}`);a=E}}for(let f of i)re(e,t,f.prop);if(a)re(e,t,a);let c=["database_id = ?","__deleted = 0"],p=[t];for(let f of i)c.push(`data ->> '${Qe(f.prop.id)}' = ?`),p.push(sn(f.value));let g=n.sort==null,y=a?`data ->> '${Qe(a.id)}'`:"created_hlc",b=`SELECT id, database_id, data FROM records WHERE ${c.join(" AND ")} `+(g?"ORDER BY order_key IS NULL, order_key, id":`ORDER BY ${y} ${d?"DESC":"ASC"}, id`);if(n.limit!=null&&s.length===0)b+=" LIMIT ?",p.push(n.limit);let D=e.query(b).all(...p).map((f)=>Ze(f,r));if(s.length){if(D=D.filter((f)=>s.every((E)=>JSON.stringify(f.cells[E.prop.id]??null)===JSON.stringify(E.value))),n.limit!=null)D=D.slice(0,n.limit)}return D}var fo=m(function(t,n,r,o){if(o!=="before"&&o!=="after")throw new u("invalid_input",`unknown move position: ${o}`);let i=t.query("SELECT id, database_id FROM records WHERE id = ? AND __deleted = 0").get(n);if(!i)throw new u("not_found",`no such record: ${n}`);let s=t.query("SELECT id, database_id FROM records WHERE id = ? AND __deleted = 0").get(r);if(!s)throw new u("not_found",`no such record: ${r}`);if(i.database_id!==s.database_id)throw new u("invalid_input","cannot move a record across databases");if(n===r)return F(t,n);gn(t,i.database_id);let a=ae(t,i.database_id).filter((g)=>g.id!==n),d=a.findIndex((g)=>g.id===r);if(d<0)throw new u("not_found",`no such target record: ${r}`);let c=o==="before"?a[d-1]?.order_key??null:a[d].order_key,p=o==="before"?a[d].order_key:a[d+1]?.order_key??null;if(c!==null&&p!==null&&c>=p)hn(t,i.database_id),a=ae(t,i.database_id).filter((g)=>g.id!==n),d=a.findIndex((g)=>g.id===r),c=o==="before"?a[d-1]?.order_key??null:a[d].order_key,p=o==="before"?a[d].order_key:a[d+1]?.order_key??null;return h(t,"records",n,"order_key",k(c,p)),F(t,n)}),ot=m(function(t,n,r){let o=t.query("SELECT id, database_id, data FROM records WHERE id = ? AND __deleted = 0").get(n);if(!o)throw new u("not_found",`no such record: ${n}`);let i=JSON.parse(o.data||"{}");for(let{prop:s,value:a}of r){let d=Object.hasOwn(i,s.id);if(!(a===void 0?!d:d&&JSON.stringify(a)===JSON.stringify(i[s.id])))h(t,"records",n,s.id,a)}return F(t,n)}),it=m(function(t,n,r){let o=t.query("SELECT id, database_id FROM records WHERE id = ? AND __deleted = 0").get(n);if(!o)throw new u("not_found",`no such record: ${n}`);return ot(t,n,Xe(t,o.database_id,r))}),mo=m(function(t,n){if(!t.query("SELECT id FROM records WHERE id = ? AND __deleted = 0").get(n))return!1;return h(t,"records",n,"__deleted",1),!0});var fn=["id","name","icon","meta","created_hlc"];var mn=fn.join(", ");function yn(e){if(!e)return null;let t=e.meta===null?null:JSON.parse(e.meta);return{...e,meta:t&&Object.keys(t).length?t:null}}function at(e,t,n,r){for(let[o,i]of Object.entries(r)){if(o.includes("."))throw new u("invalid_input",`meta key must not contain ".": ${o}`);let s=i===void 0?null:i,a=n&&o in n?n[o]:null;if(JSON.stringify(a??null)===JSON.stringify(s??null))continue;h(e,"databases",t,`meta.${o}`,s===null?void 0:s)}}var bn=m(function(t,n){let r=_("db",n.name),o=h(t,"databases",r,"name",n.name);if(h(t,"databases",r,"created_hlc",o.hlc),n.icon!==void 0)h(t,"databases",r,"icon",n.icon);return w(t,r)}),So=m(function(t,n,r){if(!w(t,n))throw new u("not_found",`no such database: ${n}`);if(r.name!==void 0)h(t,"databases",n,"name",r.name);if(r.icon!==void 0)h(t,"databases",n,"icon",r.icon);if(r.meta!==void 0){if(r.meta!==null&&(typeof r.meta!=="object"||Array.isArray(r.meta)))throw new u("invalid_input","meta must be a JSON object or null");let o=w(t,n).meta,i=r.meta??Object.fromEntries(Object.keys(o??{}).map((s)=>[s,null]));at(t,n,o,i)}return w(t,n)}),Ro=m(function(t,n,r={}){let o=w(t,n);if(!o)throw new u("not_found",`no such database: ${n}`);let i=bn(t,{name:r.name??o.name,icon:r.icon??o.icon??void 0});if(o.meta!==null){let{collapsed:p,...g}=o.meta;at(t,i.id,null,g)}let s=new Map,a=new Set;for(let p of T(t,n)){let g=p.type==="relation"&&p.config?.database===n,y=g?{...p.config,database:i.id}:p.config??void 0,b=je(t,i.id,{name:p.name,type:p.type,config:y,position:p.position});if(s.set(p.id,b.id),g)a.add(b.id)}let d=rt(t,n),c=new Map;for(let p of d){let g={};for(let[y,b]of Object.entries(p.cells)){let R=s.get(y);if(!R)continue;if(a.has(R)&&Array.isArray(b)&&b.length>0)continue;g[R]=b}c.set(p.id,nt(t,i.id,g).id)}for(let p of d){let g={};for(let[y,b]of Object.entries(p.cells)){let R=s.get(y);if(R&&a.has(R)&&Array.isArray(b)&&b.length>0)g[R]=b.map((D)=>c.get(String(D))??D)}if(Object.keys(g).length)it(t,c.get(p.id),g)}return w(t,i.id)});function w(e,t){return yn(e.query(`SELECT ${mn} FROM databases WHERE id = ? AND __deleted = 0`).get(t))}var Do=m(function(t,n){if(!w(t,n))return!1;h(t,"databases",n,"__deleted",1);let r=(o)=>{for(let i of st(t,o,"database_id",n))h(t,o,i,"__deleted",1)};r("properties"),r("records");for(let o of st(t,"documents","database_id",n))h(t,"documents",o,"database_id",null);return t.query("DELETE FROM meta WHERE key = 'current_db' AND value = ?").run(n),!0});function st(e,t,n,r){return e.query(`SELECT id FROM ${t} WHERE ${n} = ? AND __deleted = 0`).all(r).map((o)=>o.id)}var de=["read","create","update"];function wn(e){return typeof e==="string"&&de.includes(e)}function lt(e){let t=new Map;for(let r of e){let o=t.get(r.db)??new Set;for(let i of r.ops)o.add(i);t.set(r.db,o)}let n=[];for(let[r,o]of t){let i=de.filter((s)=>o.has(s));if(i.length)n.push({db:r,ops:i})}return n.sort((r,o)=>r.db<o.db?-1:r.db>o.db?1:0),{v:1,tables:n}}function ct(e){if(typeof e!=="object"||e===null||Array.isArray(e))throw new u("invalid_input","grants must be an object {v:1, tables:[...]}");let t=e;if(t.v!==1)throw new u("invalid_input","grants version must be 1");if(!Array.isArray(t.tables))throw new u("invalid_input","grants.tables must be an array");let n=[];for(let r of t.tables){if(typeof r!=="object"||r===null)throw new u("invalid_input","each grant table must be an object {db, ops}");let{db:o,ops:i}=r;if(typeof o!=="string"||!o)throw new u("invalid_input","grant table `db` must be a database id");if(!Array.isArray(i)||!i.every(wn))throw new u("invalid_input",`grant ops must be a subset of ${de.join("|")} (delete is never grantable)`);n.push({db:o,ops:i})}return lt(n)}function dt(e){return JSON.stringify(lt(e.tables))}var vn=["id","name","title","created_hlc","visibility","spa","public_grants"];var ut=vn.join(", "),En=["id","site_id","path","content_type","encoding","content"];var Fo=En.join(", ");function ue(e){let t=e.trim().toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"");if(!t)throw new u("invalid_input",`invalid site name: ${JSON.stringify(e)}`);return t}function kn(e){let t=[];for(let n of e.split("/")){if(n===""||n===".")continue;if(n==="..")t.pop();else t.push(n)}return t.join("/")}function O(e,t){return e.query(`SELECT ${ut} FROM sites WHERE id = ? AND __deleted = 0`).get(t)}function pt(e,t){let n;try{n=ue(t)}catch{return null}return e.query(`SELECT ${ut} FROM sites WHERE name = ? AND __deleted = 0 ORDER BY created_hlc DESC LIMIT 1`).get(n)}function ht(e){let t=atob(e),n=new Uint8Array(t.length);for(let r=0;r<t.length;r++)n[r]=t.charCodeAt(r);return n}function gt(e){if(e!=="public"&&e!=="private")throw new u("invalid_input",`visibility must be "public" or "private", got ${JSON.stringify(e)}`)}var Ho=m(function(t,n){let r=ue(n.name);if(pt(t,r))throw new u("conflict",`site name already exists: ${r}`);if(n.visibility!==void 0)gt(n.visibility);let o=_("site",r),i=h(t,"sites",o,"name",r);if(h(t,"sites",o,"created_hlc",i.hlc),n.title!==void 0)h(t,"sites",o,"title",n.title);if(n.visibility!==void 0)h(t,"sites",o,"visibility",n.visibility);return O(t,o)}),Bo=m(function(t,n,r){if(!O(t,n))throw new u("not_found",`no such site: ${n}`);if(r.visibility!==void 0)gt(r.visibility);if(r.name!==void 0){let o=ue(r.name),i=pt(t,o);if(i&&i.id!==n)throw new u("conflict",`site name already exists: ${o}`);h(t,"sites",n,"name",o)}if(r.title!==void 0)h(t,"sites",n,"title",r.title);if(r.visibility!==void 0)h(t,"sites",n,"visibility",r.visibility);if(r.spa!==void 0)h(t,"sites",n,"spa",r.spa?1:0);return O(t,n)}),Wo=m(function(t,n,r){if(!O(t,n))throw new u("not_found",`no such site: ${n}`);let o=r==null?null:dt(ct(r));return h(t,"sites",n,"public_grants",o),O(t,n)}),Uo=m(function(t,n){if(!O(t,n))return!1;let r=t.query("SELECT id FROM site_files WHERE site_id = ? AND __deleted = 0").all(n);for(let o of r)h(t,"site_files",o.id,"__deleted",1);return h(t,"sites",n,"__deleted",1),!0});function Sn(e,t,n){return e.query("SELECT id FROM site_files WHERE site_id = ? AND path = ? ORDER BY created_hlc LIMIT 1").get(t,n)?.id??null}var qo=m(function(t,n,r){let o=Sn(t,n,kn(r));if(!o)return!1;let i=t.query("SELECT __deleted AS d FROM site_files WHERE id = ?").get(o);if(!i||i.d)return!1;return h(t,"site_files",o,"__deleted",1),!0});var v=globalThis,S=v.caches,Rn="__MH_SW_VERSION__",H=`mh-shell-${Rn}`,ft="mh-api-v1",Dn="mh-wasm-v1",vt=["/","/webui.js","/webui.css","/manifest.webmanifest","/db-worker.js","/mh-runtime.js"],B=3500,_n=12000;function W(e){return e.ok&&e.headers.get("x-mh-unlock")==null}var pe=null;async function he(){if(pe==null){let e=await Re();if(e==="unknown")return!1;pe=e}return pe==="none"}v.addEventListener("install",(e)=>{e.waitUntil((async()=>{try{let t=await S.open(H);await Promise.allSettled(vt.map(async(n)=>{let r=await fetch(n);if(W(r))await t.put(n,r)})),await v.skipWaiting()}catch(t){try{await(await S.open("mh-debug")).put("/__mh-sw-install-error",new Response(String(t?.stack??t)))}catch{}throw t}})())});v.addEventListener("activate",(e)=>{e.waitUntil((async()=>{for(let t of await S.keys())if(t.startsWith("mh-shell-")&&t!==H)await S.delete(t);await v.clients.claim()})())});function j(e,t){return new Promise((n,r)=>{let o=setTimeout(()=>r(Error("sw: network timeout")),t);fetch(e).then((i)=>{clearTimeout(o),n(i)},(i)=>{clearTimeout(o),r(i)})})}async function mt(e,t,n,r){let o=await S.open(t),i=r??e;try{let s=await j(e,n);if(W(s))await o.put(i,s.clone());return s}catch(s){let a=await o.match(i);if(a)return a;throw s}}function Cn(e,t,n){return new Promise((r,o)=>{let i=new MessageChannel,s=setTimeout(()=>o(Error("sw: rpc timeout")),_n);i.port1.onmessage=(a)=>{clearTimeout(s),r(a.data)},e.postMessage({kind:"mh-rpc",op:t,args:n},[i.port2])})}async function G(e,t,n){let r=new Set,o=[];if(e.clientId){let i=await v.clients.get(e.clientId);if(i)o.push(i)}for(let i of await v.clients.matchAll({type:"window",includeUncontrolled:!0}))if(!o.some((s)=>s.id===i.id))o.push(i);for(let i of o){if(r.has(i.id))continue;r.add(i.id);try{let s=await Cn(i,t,n);if(!s.ok&&s.error?.code==="unavailable")continue;return s}catch{continue}}return null}var xn={invalid_input:400,not_found:404,ambiguous:400,stale:409,conflict:409,auth:401,network:502};function yt(e){if(e.ok)return new Response(JSON.stringify(e.result??null),{headers:{"content-type":"application/json","x-mh-source":"replica"}});let t=e.error?.code;return new Response(JSON.stringify({error:e.error?.message??"replica error",code:t}),{status:t?xn[t]??400:502,headers:{"content-type":"application/json","x-mh-source":"replica"}})}async function bt(e,t){let n=e.request,r=new URL(n.url),o=t??r.pathname,i=null;if(n.method!=="GET"&&(n.headers.get("content-type")??"").includes("json"))i=await n.clone().json().catch(()=>null);if(await he()){let s=V(n.method,o,r.searchParams,i);if(s){let a=await G(e,s.op,s.args);if(a)return yt(a)}return new Response(JSON.stringify({error:"no metahub server (bucket-only mode)",code:"network"}),{status:502,headers:{"content-type":"application/json","x-mh-source":"replica"}})}try{let s=n.method==="GET"?await j(n,B*2):await fetch(n);if(n.method==="GET"&&W(s))await(await S.open(ft)).put(n,s.clone());return s}catch(s){let a=V(n.method,o,r.searchParams,i);if(a){let d=await G(e,a.op,a.args);if(d)return yt(d)}if(n.method==="GET"){let d=await(await S.open(ft)).match(n);if(d)return d}throw s}}async function Tn(e,t){let n=t.content_type.includes("text/html"),r=t.content??"";if(t.encoding==="blob"){let i=await Et(e,r,t.content_type);if(!i)return new Response("blob unavailable offline",{status:404});return new Response(i.body,{status:t.status??200,headers:{"content-type":t.content_type,"x-mh-source":"replica"}})}let o;if(t.encoding==="utf8")o=n&&!t.public?xe(r):r;else o=ht(r);return new Response(o,{status:t.status??200,headers:{"content-type":t.content_type,"x-mh-source":"replica"}})}function An(e,t,n){let r=`${JSON.stringify(e)},${JSON.stringify(t)}`,o=K(n.headers.get("accept-language"));return new Response(`<!doctype html><html lang="${o}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script src="/mh-runtime.js"></script></head><body><p style="font:14px system-ui;color:#888;margin:2em">${Se(o,"正在从本地副本加载…")}</p><script>__mhOfflineBootstrap(${r})</script></body></html>`,{headers:{"content-type":"text/html; charset=utf-8","x-mh-source":"bootstrap"}})}async function wt(e,t){let n=t.pathname.slice(7);if(n==="")return null;let r=n.indexOf("/");if(r===-1)return Response.redirect(`${t.origin}/sites/${n}/`,301);let o=decodeURIComponent(n.slice(0,r)),i=decodeURIComponent(n.slice(r+1)),s=await G(e,"siteFile",[o,i]);if(s?.ok&&s.result)return Tn(e,s.result);if(s?.ok&&s.result==null)return new Response("not found (offline replica)",{status:404});if(e.request.mode==="navigate")return An(o,i,e.request);return null}async function On(e){let t=e.request,n=new URL(t.url);if(await he())return await wt(e,n)??new Response("not found",{status:404});try{return await j(t,B)}catch(r){let o=await wt(e,n);if(o)return o;throw r}}async function Ln(e){let t=e.request,n=new URL(t.url),r=decodeURIComponent(n.pathname.slice(6)),o=r.indexOf("."),i=(o>=0?r.slice(0,o):r).toLowerCase();if(!/^[0-9a-f]{16,64}$/.test(i))return fetch(t);let s=De(r);return await Et(e,i,s,t)??new Response("blob unavailable offline",{status:404})}async function Et(e,t,n,r){let o=await _e(t);if(o)return o;if(!await he())try{let s=r?new Request(r,{cache:"no-store"}):new Request(`${v.location.origin}/blob/${t}`,{cache:"no-store"}),a=await j(s,B*2);if(a.ok){let d=await a.clone().arrayBuffer();if(await Y(d,t))return await J(t,d,a.headers.get("content-type")??n),a}}catch{}let i=await G(e,"blobBytes",[t]);if(i?.ok&&i.result){let s=i.result;if(await Y(s,t))return await J(t,s,n),new Response(s,{headers:{"content-type":n,"x-mh-source":"replica"}})}return null}v.addEventListener("fetch",(e)=>{let t=e.request,n=new URL(t.url);if(n.origin!==v.location.origin)return;if(n.pathname==="/api/changes")return;if(n.pathname.startsWith("/api/")){e.respondWith(bt(e));return}let r=/^\/sites\/[^/]+(\/api\/.*)$/.exec(n.pathname);if(r){e.respondWith(bt(e,r[1]));return}if(t.method!=="GET")return;if(n.pathname.startsWith("/sites/")){e.respondWith(On(e));return}if(n.pathname.startsWith("/blob/")){e.respondWith(Ln(e));return}if(t.mode==="navigate"){if(n.pathname!=="/")return;e.respondWith(mt(t,H,B,"/"));return}if(vt.includes(n.pathname)||n.pathname.startsWith("/icons/")){e.respondWith(mt(t,H,B));return}if(n.pathname.startsWith("/webui-fmt")){e.respondWith((async()=>{let o=await S.open(H),i=await o.match(t);if(i)return i;let s=await fetch(t);if(W(s))await o.put(t,s.clone());return s})());return}if(n.pathname==="/sqlite3.wasm"){e.respondWith((async()=>{let o=await S.open(Dn),i=await o.match(t);if(i)return i;let s=await fetch(t);if(W(s))await o.put(t,s.clone());return s})());return}});
21
+ ORDER BY created_hlc, id`).all(i.database_id);if(!s.length)continue;let a=st(t,i.database_id),c=I(a,null,s.length);s.forEach((l,p)=>bn(t,l.id,c[p],r))}}),at=m(function(t,n,r){let o=mn(r,ee(n.name,"rec")),i=k(st(t,n.id),null),s=h(t,"records",o,"database_id",n.id);h(t,"records",o,"created_hlc",s.hlc),h(t,"records",o,"order_key",i);for(let{prop:a,value:c}of r)h(t,"records",o,a.id,c);return F(t,o)}),ct=m(function(t,n,r){let o=w(t,n);if(!o)throw new u("not_found",`no such database: ${n}`);return at(t,o,ot(t,o.id,r))});function F(e,t){let n=e.query("SELECT id, database_id, data FROM records WHERE id = ? AND __deleted = 0").get(t);if(!n)return null;return it(n,x(e,n.database_id))}function rt(e){return e.replace(/'/g,"''")}function lt(e,t,n={}){let r=x(e,t),o=n.filter&&Object.keys(n.filter).length?ue(r,n.filter).map(({prop:g,value:E})=>({prop:g,value:de(e,g,E)})):[],i=o.filter((g)=>tt(g.value)),s=o.filter((g)=>!tt(g.value)),a,c=!1;if(n.sort){let g=n.sort;if(g.startsWith("-"))c=!0,g=g.slice(1);if(g!=="created"&&g!=="created_hlc"){let E=r.find((L)=>L.id===g);if(!E){let L=r.filter((Ct)=>Ct.name.toLowerCase()===g.toLowerCase());if(L.length>1)throw new u("ambiguous",`sort field "${g}" matches ${L.length} properties; use a property id`);E=L[0]}if(!E)throw new u("not_found",`unknown sort field: ${g}`);a=E}}for(let g of i)ie(e,t,g.prop);if(a)ie(e,t,a);let l=["database_id = ?","__deleted = 0"],p=[t];for(let g of i)l.push(`data ->> '${rt(g.prop.id)}' = ?`),p.push(pn(g.value));let f=n.sort==null,y=a?`data ->> '${rt(a.id)}'`:"created_hlc",b=`SELECT id, database_id, data FROM records WHERE ${l.join(" AND ")} `+(f?"ORDER BY order_key IS NULL, order_key, id":`ORDER BY ${y} ${c?"DESC":"ASC"}, id`);if(n.limit!=null&&s.length===0)b+=" LIMIT ?",p.push(n.limit);let D=e.query(b).all(...p).map((g)=>it(g,r));if(s.length){if(D=D.filter((g)=>s.every((E)=>JSON.stringify(g.cells[E.prop.id]??null)===JSON.stringify(E.value))),n.limit!=null)D=D.slice(0,n.limit)}return D}var So=m(function(t,n,r,o){if(o!=="before"&&o!=="after")throw new u("invalid_input",`unknown move position: ${o}`);let i=t.query("SELECT id, database_id FROM records WHERE id = ? AND __deleted = 0").get(n);if(!i)throw new u("not_found",`no such record: ${n}`);let s=t.query("SELECT id, database_id FROM records WHERE id = ? AND __deleted = 0").get(r);if(!s)throw new u("not_found",`no such record: ${r}`);if(i.database_id!==s.database_id)throw new u("invalid_input","cannot move a record across databases");if(n===r)return F(t,n);vn(t,i.database_id);let a=le(t,i.database_id).filter((f)=>f.id!==n),c=a.findIndex((f)=>f.id===r);if(c<0)throw new u("not_found",`no such target record: ${r}`);let l=o==="before"?a[c-1]?.order_key??null:a[c].order_key,p=o==="before"?a[c].order_key:a[c+1]?.order_key??null;if(l!==null&&p!==null&&l>=p)wn(t,i.database_id),a=le(t,i.database_id).filter((f)=>f.id!==n),c=a.findIndex((f)=>f.id===r),l=o==="before"?a[c-1]?.order_key??null:a[c].order_key,p=o==="before"?a[c].order_key:a[c+1]?.order_key??null;return h(t,"records",n,"order_key",k(l,p)),F(t,n)}),dt=m(function(t,n,r){let o=t.query("SELECT id, database_id, data FROM records WHERE id = ? AND __deleted = 0").get(n);if(!o)throw new u("not_found",`no such record: ${n}`);let i=JSON.parse(o.data||"{}");for(let{prop:s,value:a}of r){let c=Object.hasOwn(i,s.id);if(!(a===void 0?!c:c&&JSON.stringify(a)===JSON.stringify(i[s.id])))h(t,"records",n,s.id,a)}return F(t,n)}),ut=m(function(t,n,r){let o=t.query("SELECT id, database_id FROM records WHERE id = ? AND __deleted = 0").get(n);if(!o)throw new u("not_found",`no such record: ${n}`);return dt(t,n,ot(t,o.database_id,r))}),Ro=m(function(t,n){if(!t.query("SELECT id FROM records WHERE id = ? AND __deleted = 0").get(n))return!1;return h(t,"records",n,"__deleted",1),!0});var En=["id","name","icon","meta","created_hlc"];var kn=En.join(", ");function Sn(e){if(!e)return null;let t=e.meta===null?null:JSON.parse(e.meta);return{...e,meta:t&&Object.keys(t).length?t:null}}function ht(e,t,n,r){for(let[o,i]of Object.entries(r)){if(o.includes("."))throw new u("invalid_input",`meta key must not contain ".": ${o}`);let s=i===void 0?null:i,a=n&&o in n?n[o]:null;if(JSON.stringify(a??null)===JSON.stringify(s??null))continue;h(e,"databases",t,`meta.${o}`,s===null?void 0:s)}}var Rn=m(function(t,n){let r=_("db",n.name),o=h(t,"databases",r,"name",n.name);if(h(t,"databases",r,"created_hlc",o.hlc),n.icon!==void 0)h(t,"databases",r,"icon",n.icon);return w(t,r)}),Lo=m(function(t,n,r){if(!w(t,n))throw new u("not_found",`no such database: ${n}`);if(r.name!==void 0)h(t,"databases",n,"name",r.name);if(r.icon!==void 0)h(t,"databases",n,"icon",r.icon);if(r.meta!==void 0){if(r.meta!==null&&(typeof r.meta!=="object"||Array.isArray(r.meta)))throw new u("invalid_input","meta must be a JSON object or null");let o=w(t,n).meta,i=r.meta??Object.fromEntries(Object.keys(o??{}).map((s)=>[s,null]));ht(t,n,o,i)}return w(t,n)}),Po=m(function(t,n,r={}){let o=w(t,n);if(!o)throw new u("not_found",`no such database: ${n}`);let i=Rn(t,{name:r.name??o.name,icon:r.icon??o.icon??void 0}),s=new Map,a=new Set;for(let p of x(t,n)){let f=p.type==="relation"&&p.config?.database===n,y=f?{...p.config,database:i.id}:p.config??void 0,b=Qe(t,i.id,{name:p.name,type:p.type,config:y,position:p.position});if(s.set(p.id,b.id),f)a.add(b.id)}let c=lt(t,n),l=new Map;for(let p of c){let f={};for(let[y,b]of Object.entries(p.cells)){let R=s.get(y);if(!R)continue;if(a.has(R)&&Array.isArray(b)&&b.length>0)continue;f[R]=b}l.set(p.id,ct(t,i.id,f).id)}for(let p of c){let f={};for(let[y,b]of Object.entries(p.cells)){let R=s.get(y);if(R&&a.has(R)&&Array.isArray(b)&&b.length>0)f[R]=b.map((D)=>l.get(String(D))??D)}if(Object.keys(f).length)ut(t,l.get(p.id),f)}if(o.meta!==null){let{collapsed:p,...f}=o.meta;if("views"in f)f.views=je(f.views,s,l,new Set(x(t,n).filter((y)=>y.type==="relation"&&y.config?.database===n).map((y)=>y.id)));ht(t,i.id,null,f)}return w(t,i.id)});function w(e,t){return Sn(e.query(`SELECT ${kn} FROM databases WHERE id = ? AND __deleted = 0`).get(t))}var No=m(function(t,n){if(!w(t,n))return!1;h(t,"databases",n,"__deleted",1);let r=(o)=>{for(let i of pt(t,o,"database_id",n))h(t,o,i,"__deleted",1)};r("properties"),r("records");for(let o of pt(t,"documents","database_id",n))h(t,"documents",o,"database_id",null);return t.query("DELETE FROM meta WHERE key = 'current_db' AND value = ?").run(n),!0});function pt(e,t,n,r){return e.query(`SELECT id FROM ${t} WHERE ${n} = ? AND __deleted = 0`).all(r).map((o)=>o.id)}var pe=["read","create","update"];function Dn(e){return typeof e==="string"&&pe.includes(e)}function ft(e){let t=new Map;for(let r of e){let o=t.get(r.db)??new Set;for(let i of r.ops)o.add(i);t.set(r.db,o)}let n=[];for(let[r,o]of t){let i=pe.filter((s)=>o.has(s));if(i.length)n.push({db:r,ops:i})}return n.sort((r,o)=>r.db<o.db?-1:r.db>o.db?1:0),{v:1,tables:n}}function gt(e){if(typeof e!=="object"||e===null||Array.isArray(e))throw new u("invalid_input","grants must be an object {v:1, tables:[...]}");let t=e;if(t.v!==1)throw new u("invalid_input","grants version must be 1");if(!Array.isArray(t.tables))throw new u("invalid_input","grants.tables must be an array");let n=[];for(let r of t.tables){if(typeof r!=="object"||r===null)throw new u("invalid_input","each grant table must be an object {db, ops}");let{db:o,ops:i}=r;if(typeof o!=="string"||!o)throw new u("invalid_input","grant table `db` must be a database id");if(!Array.isArray(i)||!i.every(Dn))throw new u("invalid_input",`grant ops must be a subset of ${pe.join("|")} (delete is never grantable)`);n.push({db:o,ops:i})}return ft(n)}function mt(e){return JSON.stringify(ft(e.tables))}var _n=["id","name","title","created_hlc","visibility","spa","public_grants"];var yt=_n.join(", "),xn=["id","site_id","path","content_type","encoding","content"];var Ko=xn.join(", ");function he(e){let t=e.trim().toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"");if(!t)throw new u("invalid_input",`invalid site name: ${JSON.stringify(e)}`);return t}function Cn(e){let t=[];for(let n of e.split("/")){if(n===""||n===".")continue;if(n==="..")t.pop();else t.push(n)}return t.join("/")}function O(e,t){return e.query(`SELECT ${yt} FROM sites WHERE id = ? AND __deleted = 0`).get(t)}function bt(e,t){let n;try{n=he(t)}catch{return null}return e.query(`SELECT ${yt} FROM sites WHERE name = ? AND __deleted = 0 ORDER BY created_hlc DESC LIMIT 1`).get(n)}function wt(e){let t=atob(e),n=new Uint8Array(t.length);for(let r=0;r<t.length;r++)n[r]=t.charCodeAt(r);return n}function vt(e){if(e!=="public"&&e!=="private")throw new u("invalid_input",`visibility must be "public" or "private", got ${JSON.stringify(e)}`)}var Vo=m(function(t,n){let r=he(n.name);if(bt(t,r))throw new u("conflict",`site name already exists: ${r}`);if(n.visibility!==void 0)vt(n.visibility);let o=_("site",r),i=h(t,"sites",o,"name",r);if(h(t,"sites",o,"created_hlc",i.hlc),n.title!==void 0)h(t,"sites",o,"title",n.title);if(n.visibility!==void 0)h(t,"sites",o,"visibility",n.visibility);return O(t,o)}),Yo=m(function(t,n,r){if(!O(t,n))throw new u("not_found",`no such site: ${n}`);if(r.visibility!==void 0)vt(r.visibility);if(r.name!==void 0){let o=he(r.name),i=bt(t,o);if(i&&i.id!==n)throw new u("conflict",`site name already exists: ${o}`);h(t,"sites",n,"name",o)}if(r.title!==void 0)h(t,"sites",n,"title",r.title);if(r.visibility!==void 0)h(t,"sites",n,"visibility",r.visibility);if(r.spa!==void 0)h(t,"sites",n,"spa",r.spa?1:0);return O(t,n)}),Jo=m(function(t,n,r){if(!O(t,n))throw new u("not_found",`no such site: ${n}`);let o=r==null?null:mt(gt(r));return h(t,"sites",n,"public_grants",o),O(t,n)}),Qo=m(function(t,n){if(!O(t,n))return!1;let r=t.query("SELECT id FROM site_files WHERE site_id = ? AND __deleted = 0").all(n);for(let o of r)h(t,"site_files",o.id,"__deleted",1);return h(t,"sites",n,"__deleted",1),!0});function Tn(e,t,n){return e.query("SELECT id FROM site_files WHERE site_id = ? AND path = ? ORDER BY created_hlc LIMIT 1").get(t,n)?.id??null}var Xo=m(function(t,n,r){let o=Tn(t,n,Cn(r));if(!o)return!1;let i=t.query("SELECT __deleted AS d FROM site_files WHERE id = ?").get(o);if(!i||i.d)return!1;return h(t,"site_files",o,"__deleted",1),!0});var v=globalThis,S=v.caches,An="__MH_SW_VERSION__",H=`mh-shell-${An}`,Et="mh-api-v1",On="mh-wasm-v1",_t=["/","/webui.js","/webui.css","/manifest.webmanifest","/icons/icon-32.png","/icons/icon-180.png","/icons/icon-192.png","/icons/icon-512.png","/db-worker.js","/mh-runtime.js"],B=3500,Ln=12000;function W(e){return e.ok&&e.headers.get("x-mh-unlock")==null}var fe=null;async function ge(){if(fe==null){let e=await Te();if(e==="unknown")return!1;fe=e}return fe==="none"}v.addEventListener("install",(e)=>{e.waitUntil((async()=>{try{let t=await S.open(H);await Promise.allSettled(_t.map(async(n)=>{let r=await fetch(n);if(W(r))await t.put(n,r)})),await v.skipWaiting()}catch(t){try{await(await S.open("mh-debug")).put("/__mh-sw-install-error",new Response(String(t?.stack??t)))}catch{}throw t}})())});v.addEventListener("activate",(e)=>{e.waitUntil((async()=>{for(let t of await S.keys())if(t.startsWith("mh-shell-")&&t!==H)await S.delete(t);await v.clients.claim()})())});function j(e,t){return new Promise((n,r)=>{let o=setTimeout(()=>r(Error("sw: network timeout")),t);fetch(e).then((i)=>{clearTimeout(o),n(i)},(i)=>{clearTimeout(o),r(i)})})}async function kt(e,t,n,r){let o=await S.open(t),i=r??e;try{let s=await j(e,n);if(W(s))await o.put(i,s.clone());return s}catch(s){let a=await o.match(i);if(a)return a;throw s}}function Pn(e,t,n){return new Promise((r,o)=>{let i=new MessageChannel,s=setTimeout(()=>o(Error("sw: rpc timeout")),Ln);i.port1.onmessage=(a)=>{clearTimeout(s),r(a.data)},e.postMessage({kind:"mh-rpc",op:t,args:n},[i.port2])})}async function G(e,t,n){let r=new Set,o=[];if(e.clientId){let i=await v.clients.get(e.clientId);if(i)o.push(i)}for(let i of await v.clients.matchAll({type:"window",includeUncontrolled:!0}))if(!o.some((s)=>s.id===i.id))o.push(i);for(let i of o){if(r.has(i.id))continue;r.add(i.id);try{let s=await Pn(i,t,n);if(!s.ok&&s.error?.code==="unavailable")continue;return s}catch{continue}}return null}var Nn={invalid_input:400,not_found:404,ambiguous:400,stale:409,conflict:409,auth:401,network:502};function St(e){if(e.ok)return new Response(JSON.stringify(e.result??null),{headers:{"content-type":"application/json","x-mh-source":"replica"}});let t=e.error?.code;return new Response(JSON.stringify({error:e.error?.message??"replica error",code:t}),{status:t?Nn[t]??400:502,headers:{"content-type":"application/json","x-mh-source":"replica"}})}async function Rt(e,t){let n=e.request,r=new URL(n.url),o=t??r.pathname,i=null;if(n.method!=="GET"&&(n.headers.get("content-type")??"").includes("json"))i=await n.clone().json().catch(()=>null);if(await ge()){let s=J(n.method,o,r.searchParams,i);if(s){let a=await G(e,s.op,s.args);if(a)return St(a)}return new Response(JSON.stringify({error:"no metahub server (bucket-only mode)",code:"network"}),{status:502,headers:{"content-type":"application/json","x-mh-source":"replica"}})}try{let s=n.method==="GET"?await j(n,B*2):await fetch(n);if(n.method==="GET"&&W(s))await(await S.open(Et)).put(n,s.clone());return s}catch(s){let a=J(n.method,o,r.searchParams,i);if(a){let c=await G(e,a.op,a.args);if(c)return St(c)}if(n.method==="GET"){let c=await(await S.open(Et)).match(n);if(c)return c}throw s}}async function $n(e,t){let n=t.content_type.includes("text/html"),r=t.content??"";if(t.encoding==="blob"){let i=await xt(e,r,t.content_type);if(!i)return new Response("blob unavailable offline",{status:404});return new Response(i.body,{status:t.status??200,headers:{"content-type":t.content_type,"x-mh-source":"replica"}})}let o;if(t.encoding==="utf8")o=n&&!t.public?Pe(r):r;else o=wt(r);return new Response(o,{status:t.status??200,headers:{"content-type":t.content_type,"x-mh-source":"replica"}})}function Mn(e,t,n){let r=`${JSON.stringify(e)},${JSON.stringify(t)}`,o=Y(n.headers.get("accept-language"));return new Response(`<!doctype html><html lang="${o}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><style>${ye}html,body{background:var(--bg);color:var(--fg)}</style><script>${be}</script><script src="/mh-runtime.js"></script></head><body><p style="font:14px system-ui;color:var(--muted);margin:2em">${Ce(o,"正在从本地副本加载…")}</p><script>__mhOfflineBootstrap(${r})</script></body></html>`,{headers:{"content-type":"text/html; charset=utf-8","x-mh-source":"bootstrap"}})}async function Dt(e,t){let n=t.pathname.slice(7);if(n==="")return null;let r=n.indexOf("/");if(r===-1)return Response.redirect(`${t.origin}/sites/${n}/`,301);let o=decodeURIComponent(n.slice(0,r)),i=decodeURIComponent(n.slice(r+1)),s=await G(e,"siteFile",[o,i]);if(s?.ok&&s.result)return $n(e,s.result);if(s?.ok&&s.result==null)return new Response("not found (offline replica)",{status:404});if(e.request.mode==="navigate")return Mn(o,i,e.request);return null}async function In(e){let t=e.request,n=new URL(t.url);if(await ge())return await Dt(e,n)??new Response("not found",{status:404});try{return await j(t,B)}catch(r){let o=await Dt(e,n);if(o)return o;throw r}}async function Fn(e){let t=e.request,n=new URL(t.url),r=decodeURIComponent(n.pathname.slice(6)),o=r.indexOf("."),i=(o>=0?r.slice(0,o):r).toLowerCase();if(!/^[0-9a-f]{16,64}$/.test(i))return fetch(t);let s=Ae(r);return await xt(e,i,s,t)??new Response("blob unavailable offline",{status:404})}async function xt(e,t,n,r){let o=await Oe(t);if(o)return o;if(!await ge())try{let s=r?new Request(r,{cache:"no-store"}):new Request(`${v.location.origin}/blob/${t}`,{cache:"no-store"}),a=await j(s,B*2);if(a.ok){let c=await a.clone().arrayBuffer();if(await Q(c,t))return await X(t,c,a.headers.get("content-type")??n),a}}catch{}let i=await G(e,"blobBytes",[t]);if(i?.ok&&i.result){let s=i.result;if(await Q(s,t))return await X(t,s,n),new Response(s,{headers:{"content-type":n,"x-mh-source":"replica"}})}return null}v.addEventListener("fetch",(e)=>{let t=e.request,n=new URL(t.url);if(n.origin!==v.location.origin)return;if(n.pathname==="/api/changes")return;if(n.pathname.startsWith("/api/")){e.respondWith(Rt(e));return}let r=/^\/sites\/[^/]+(\/api\/.*)$/.exec(n.pathname);if(r){e.respondWith(Rt(e,r[1]));return}if(t.method!=="GET")return;if(n.pathname.startsWith("/sites/")){e.respondWith(In(e));return}if(n.pathname.startsWith("/blob/")){e.respondWith(Fn(e));return}if(t.mode==="navigate"){if(n.pathname!=="/")return;e.respondWith(kt(t,H,B,"/"));return}if(_t.includes(n.pathname)||n.pathname.startsWith("/icons/")){e.respondWith(kt(t,H,B));return}if(n.pathname.startsWith("/webui-fmt")){e.respondWith((async()=>{let o=await S.open(H),i=await o.match(t);if(i)return i;let s=await fetch(t);if(W(s))await o.put(t,s.clone());return s})());return}if(n.pathname==="/sqlite3.wasm"){e.respondWith((async()=>{let o=await S.open(On),i=await o.match(t);if(i)return i;let s=await fetch(t);if(W(s))await o.put(t,s.clone());return s})());return}});