book-index-ui 0.2.21 → 0.2.22

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.
package/dist/index.d.ts CHANGED
@@ -382,6 +382,24 @@ declare interface CatalogDivision {
382
382
  categories: CatalogCategory[];
383
383
  }
384
384
 
385
+ /**
386
+ * 文件名清洗:保留 ASCII 字母数字 + CJK 全范围。
387
+ *
388
+ * **必须与 Python 端 storage.py 的 _clean_name 保持一致**,否则同一个
389
+ * Work/Book 在两侧算路径会差字符 → find_file_by_id 静默 miss。
390
+ *
391
+ * 范围(与 Python 一致):
392
+ * - U+3400-U+4DBF CJK 扩展 A
393
+ * - U+4E00-U+9FFF CJK 基本(注意:旧版只到 U+9FA5)
394
+ * - U+F900-U+FAFF CJK 兼容汉字(U+F98C 歷 等历史字形)
395
+ * - U+20000-U+3134F SMP CJK 扩展 B-G
396
+ *
397
+ * 历史扫描 book-index-draft 发现 557 个文件名含 CJK 兼容字(U+F900+),
398
+ * 旧版正则只覆盖到 U+9FA5,会漏掉这些字 — 造成 TS 端算路径与 Python
399
+ * 写出的路径不一致。
400
+ */
401
+ export declare function cleanName(name: string): string;
402
+
385
403
  export declare const CollatedEdition: default_2.FC<CollatedEditionProps>;
386
404
 
387
405
  /** 整理本索引(卷列表) */
@@ -2103,6 +2121,9 @@ declare interface SectionProps {
2103
2121
  children: default_2.ReactNode;
2104
2122
  }
2105
2123
 
2124
+ /** Deterministic hash — identical results in Python (h*31+ord(c)) & 0xFFFFFFFF */
2125
+ export declare function shardOf(id: string, n?: number): number;
2126
+
2106
2127
  export declare const SmartBidInput: default_2.FC<SmartBidInputProps>;
2107
2128
 
2108
2129
  declare interface SmartBidInputProps {
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { e as tn, a as _n } from "./storage-entry-Ysz_rfdh.js";
2
- import { B as ml, b as yl, c as vl, d as Sl, f as kl, C as wl, G as _l, I as zl, g as Il, L as Tl, M as Cl, S as Rl, h as Ll, i as Bl, j as El, k as Wl, l as $l, m as Pl, n as Hl, o as Ml, p as Ol, q as Al, s as Dl, v as jl } from "./storage-entry-Ysz_rfdh.js";
1
+ import { e as tn, a as _n } from "./storage-entry-Cn1-iEHv.js";
2
+ import { B as ml, b as yl, c as vl, d as Sl, f as kl, C as wl, G as _l, I as zl, g as Il, L as Tl, M as Cl, S as Rl, h as Ll, i as Bl, j as El, k as Wl, l as $l, m as Pl, n as Hl, o as Ml, p as Ol, q as Al, r as Dl, s as jl, t as Fl, v as Nl } from "./storage-entry-Cn1-iEHv.js";
3
3
  import { jsx as t, jsxs as i, Fragment as A } from "react/jsx-runtime";
4
4
  import at, { createContext as nn, useMemo as le, useContext as Ke, useState as z, useEffect as P, useCallback as F, useRef as q } from "react";
5
5
  import { createPortal as zn } from "react-dom";
@@ -8971,23 +8971,25 @@ export {
8971
8971
  El as base58Decode,
8972
8972
  Wl as buildId,
8973
8973
  fl as buildLineageGraph,
8974
- $l as decodeId,
8975
- Pl as decodeIdString,
8974
+ $l as cleanName,
8975
+ Pl as decodeId,
8976
+ Hl as decodeIdString,
8976
8977
  ci as detailToEditor,
8977
8978
  cl as editorToDetail,
8978
- Hl as encodeId,
8979
- Ml as extractIdFromUrl,
8979
+ Ml as encodeId,
8980
+ Ol as extractIdFromUrl,
8980
8981
  tn as extractStatus,
8981
8982
  _n as extractType,
8982
8983
  Je as formatTemplate,
8983
- Ol as normalizeCatalog,
8984
- Al as parseId,
8984
+ Al as normalizeCatalog,
8985
+ Dl as parseId,
8985
8986
  Rr as parseSourceString,
8986
- Dl as smartDecode,
8987
+ jl as shardOf,
8988
+ Fl as smartDecode,
8987
8989
  Lr as stringifySources,
8988
8990
  be as useBidUrl,
8989
8991
  G as useConvert,
8990
8992
  D as useT,
8991
8993
  ul as validateLineageGraph,
8992
- jl as validateResource
8994
+ Nl as validateResource
8993
8995
  };
@@ -0,0 +1 @@
1
+ "use strict";var dt=Object.create;var K=Object.defineProperty;var ft=Object.getOwnPropertyDescriptor;var yt=Object.getOwnPropertyNames;var gt=Object.getPrototypeOf,mt=Object.prototype.hasOwnProperty;var pt=(r,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of yt(t))!mt.call(r,n)&&n!==e&&K(r,n,{get:()=>t[n],enumerable:!(s=ft(t,n))||s.enumerable});return r};var _t=(r,t,e)=>(e=r!=null?dt(gt(r)):{},pt(t||!r||!r.__esModule?K(e,"default",{value:r,enumerable:!0}):e,r));const wt={official:0,draft:1},bt={0:"official",1:"draft"},kt={book:0,collection:2,work:3,entity:4},It={0:"book",2:"collection",3:"work",4:"entity"},et=62n,st=59n,nt=19n,ot=8n,it=(1n<<40n)-1n,xt=(1n<<3n)-1n,Et=(1n<<11n)-1n,St=(1n<<8n)-1n,U="0123456789abcdefghijklmnopqrstuvwxyz",rt=new Map;for(let r=0;r<U.length;r++)rt.set(U[r],BigInt(r));function F(r){if(r===0n)return U[0];let t="";for(;r>0n;)t=U[Number(r%36n)]+t,r=r/36n;return t}function H(r){let t=0n;for(const e of r){const s=rt.get(e);if(s===void 0)throw new Error(`Invalid Base36 character: ${e}`);t=t*36n+s}return t}const X="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",at=new Map;for(let r=0;r<X.length;r++)at.set(X[r],BigInt(r));function ct(r){let t=0n;for(const e of r){const s=at.get(e);if(s===void 0)throw new Error(`Invalid Base58 character: ${e}`);t=t*58n+s}return t}function k(r){return/[A-Z]/.test(r)?ct(r):H(r)}const vt=F,$t=H;function $(r){const t=Number(r>>et&1n),e=Number(r>>st&xt),s=r>>nt&it,n=Number(r>>ot&Et),o=Number(r&St);return{status:bt[t]??"draft",type:It[e]??"book",timestamp:s,machineId:n,sequence:o}}function N(r,t,e,s,n){return BigInt(wt[r])<<et|BigInt(kt[t])<<st|(e&it)<<nt|BigInt(s)<<ot|BigInt(n)}function j(r){return $(k(r))}function lt(r){return j(r).type}function At(r){return j(r).status}const O={book:"Book",collection:"Collection",work:"Work",entity:"Entity"},Ct={Book:"book",Collection:"collection",Work:"work",Entity:"entity"},L=16;function D(r,t=L){let e=0;for(let s=0;s<r.length;s++)e=Math.imul(e,31)+r.charCodeAt(s)>>>0;return e%t}function b(...r){const t=r.join("/");return t.startsWith("//")?"//"+t.slice(2).replace(/\/+/g,"/"):t.replace(/\/+/g,"/")}function ht(r){return r.replace(/[^㐀-䶿一-鿿豈-﫿a-zA-Z0-9\u{20000}-\u{3134f}]/gu,"")||"Undefined"}class q{constructor(t,e){this.fs=t,this.officialRoot=b(e,"book-index"),this.draftRoot=b(e,"book-index-draft")}getRootByStatus(t){return t==="draft"?this.draftRoot:this.officialRoot}getRootById(t){const e=k(t),s=$(e);return this.getRootByStatus(s.status)}getPath(t,e,s){const n=this.getRootById(e),o=e.padEnd(3,"_").substring(0,3),[i,a,c]=[o[0],o[1],o[2]],l=O[t];return b(n,l,i,a,c,`${e}-${ht(s)}.json`)}async saveItem(t,e,s){const n=s.title||s.书名||"未命名",o=s.edition||"",i=o?`${n}${o}`:n,a=this.getPath(t,e,i),c=await this.findFileById(e);if(c&&c!==a)try{await this.fs.deleteFile(c)}catch{}const l=a.substring(0,a.lastIndexOf("/"));await this.fs.mkdir(l),s.id=e,s.type=t,await this.fs.writeFile(a,JSON.stringify(s,Pt,2));const h=this.getRootById(e),u=a.substring(h.length+1);return await this.updateIndexEntry(h,s,t,u),a}async updateIndexEntry(t,e,s,n){const o=e.id||"";if(!o)return;const i=`${s}s`,a=await this.loadShard(t,i,o);let c="";const l=e.authors;if(Array.isArray(l)&&l.length>0){const y=l[0];c=typeof y=="object"&&y!==null?y.name||"":String(y)}else typeof l=="string"&&(c=l);let h="";const u=e.publication_info;typeof u=="object"&&u!==null?h=u.year||"":typeof u=="string"&&(h=u);let d="";const f=e.current_location;typeof f=="object"&&f!==null?d=f.name||"":typeof f=="string"&&(d=f);const m=Array.isArray(e.additional_titles)?e.additional_titles.map(y=>typeof y=="string"?y:y==null?void 0:y.book_title).filter(Boolean):void 0,I=Array.isArray(e.attached_texts)?e.attached_texts.map(y=>typeof y=="string"?y:y==null?void 0:y.book_title).filter(Boolean):void 0;let S;const x=e.juan_count;typeof x=="number"?S=x:typeof x=="object"&&x!==null&&(S=x.number||void 0);const C=typeof e.measure_info=="string"?e.measure_info:"";let v=!1,E=!1;const B=e.resources;if(Array.isArray(B))for(const y of B){if(typeof y!="object"||y===null)continue;const _=y.types;if(Array.isArray(_))_.includes("text")&&(v=!0),_.includes("image")&&(E=!0);else{const A=y.type;(A==="text"||A==="text+image")&&(v=!0),(A==="image"||A==="text+image")&&(E=!0)}}const p={id:o,title:e.title||"未命名",type:O[s],path:n,author:c,year:h,holder:d};m&&m.length>0&&(p.additional_titles=m),I&&I.length>0&&(p.attached_texts=I),S&&(p.juan_count=S),C&&(p.measure_info=C);const P=typeof e.edition=="string"?e.edition:"";P&&(p.edition=P),v&&(p.has_text=!0),E&&(p.has_image=!0),a[o]=p,await this.saveShard(t,i,o,a)}async deleteItem(t){const e=await this.findFileById(t);if(!e)return!1;const s=k(t),n=$(s),o=this.getRootByStatus(n.status),i=`${n.type}s`,a=await this.loadShard(o,i,t);return a[t]&&(delete a[t],await this.saveShard(o,i,t,a)),await this.fs.deleteFile(e),!0}async findFileById(t){const e=t.padEnd(3,"_").substring(0,3),[s,n,o]=[e[0],e[1],e[2]];for(const i of[this.officialRoot,this.draftRoot])for(const a of["Book","Collection","Work"]){const c=b(i,a,s,n,o);if(await this.fs.exists(c))try{const h=(await this.fs.readdir(c)).find(u=>u.startsWith(`${t}-`)&&u.endsWith(".json"));if(h)return b(c,h)}catch{}}return null}async loadMetadata(t){try{const e=await this.fs.readFile(t);return JSON.parse(e)}catch{return{}}}async getItem(t){const e=await this.findFileById(t);if(!e)return null;const s=await this.loadMetadata(e);return Object.keys(s).length>0?s:null}async loadEntries(t,e){const s=e?[this.getRootByStatus(e)]:[this.officialRoot,this.draftRoot],n=[],o=`${t}s`;for(const i of s){const a=await this.loadAllShards(i,o);for(const[c,l]of Object.entries(a))n.push({id:c,title:l.title,type:t,author:l.author||void 0,dynasty:l.dynasty||void 0,role:l.role||void 0,path:b(i,l.path),additional_titles:l.additional_titles,attached_texts:l.attached_texts,edition:l.edition,juan_count:l.juan_count,has_text:l.has_text,has_image:l.has_image})}return n}async searchEntries(t,e,s){const n=await this.loadEntries(e,s);return W(n,t)}async searchAll(t,e=5,s){const n=["work","book","collection"],o=await Promise.all(n.map(i=>this.searchEntries(t,i,s)));return{works:o[0].slice(0,e),books:o[1].slice(0,e),collections:o[2].slice(0,e),totalWorks:o[0].length,totalBooks:o[1].length,totalCollections:o[2].length}}async rebuildIndex(t){var n,o;const e=this.getRootByStatus(t),s={books:Object.fromEntries(Array.from({length:L},(i,a)=>[a,{}])),collections:{0:{}},works:Object.fromEntries(Array.from({length:L},(i,a)=>[a,{}]))};for(const i of["Book","Collection","Work"]){const a=b(e,i);if(!await this.fs.exists(a))continue;const l=`${Ct[i]}s`,h=await this.fs.glob(a,"**/*.json");for(const u of h)if(!u.includes("/index/"))try{const d=await this.loadMetadata(u);let f=d.id||d.ID||"";if(!f){const g=u.substring(u.lastIndexOf("/")+1);g.includes("-")&&(f=g.split("-")[0])}if(!f)continue;const m=u.substring(e.length+1);let I="";const S=d.authors;if(Array.isArray(S)&&S.length>0){const g=S[0];I=typeof g=="object"&&g!==null?g.name||"":String(g)}const x=Array.isArray(d.additional_titles)?d.additional_titles.map(g=>typeof g=="string"?g:g==null?void 0:g.book_title).filter(Boolean):void 0,C=Array.isArray(d.attached_texts)?d.attached_texts.map(g=>typeof g=="string"?g:g==null?void 0:g.book_title).filter(Boolean):void 0;let v;const E=d.juan_count;typeof E=="number"?v=E:typeof E=="object"&&E!==null&&(v=E.number||void 0);const B=typeof d.measure_info=="string"?d.measure_info:"";let p=!1,P=!1;const y=d.resources;if(Array.isArray(y))for(const g of y){if(typeof g!="object"||g===null)continue;const M=g.types;if(Array.isArray(M))M.includes("text")&&(p=!0),M.includes("image")&&(P=!0);else{const R=g.type;(R==="text"||R==="text+image")&&(p=!0),(R==="image"||R==="text+image")&&(P=!0)}}const _={id:f,title:d.title||"未命名",type:i,path:m,author:I,year:typeof d.publication_info=="object"&&((n=d.publication_info)==null?void 0:n.year)||"",holder:typeof d.current_location=="object"&&((o=d.current_location)==null?void 0:o.name)||""};x&&x.length>0&&(_.additional_titles=x),C&&C.length>0&&(_.attached_texts=C),v&&(_.juan_count=v),B&&(_.measure_info=B);const A=typeof d.edition=="string"?d.edition:"";A&&(_.edition=A),p&&(_.has_text=!0),P&&(_.has_image=!0);const ut=l==="collections"?0:D(f);s[l][ut][f]=_}catch{}}for(const[i,a]of Object.entries(s))for(const[c,l]of Object.entries(a)){const h=this.shardPath(e,i,Number(c)),u=h.substring(0,h.lastIndexOf("/"));await this.fs.mkdir(u),await this.fs.writeFile(h,JSON.stringify(l,null,2))}}getAssetDir(t){const e=this.getRootById(t),s=k(t),o=$(s).type,i=t.padEnd(3,"_").substring(0,3),[a,c,l]=[i[0],i[1],i[2]],h=O[o];return b(e,h,a,c,l,t)}async initAssetDir(t){const e=this.getAssetDir(t);return await this.fs.mkdir(e),e}async hasAssetDir(t){const e=this.getAssetDir(t);return this.fs.exists(e)}shardPath(t,e,s){return e==="collections"?b(t,"index","collections.json"):b(t,"index",e,`${s.toString(16)}.json`)}async loadShard(t,e,s){const n=D(s),o=this.shardPath(t,e,n);try{if(!await this.fs.exists(o))return{};const i=await this.fs.readFile(o);return JSON.parse(i)}catch{return{}}}async saveShard(t,e,s,n){const o=D(s),i=this.shardPath(t,e,o),a=i.substring(0,i.lastIndexOf("/"));await this.fs.mkdir(a),await this.fs.writeFile(i,JSON.stringify(n,null,2))}async loadAllShards(t,e){const s={};if(e==="collections"){const n=this.shardPath(t,e,0);try{if(await this.fs.exists(n)){const o=await this.fs.readFile(n);Object.assign(s,JSON.parse(o))}}catch{}return s}for(let n=0;n<L;n++){const o=this.shardPath(t,e,n);try{if(await this.fs.exists(o)){const i=await this.fs.readFile(o);Object.assign(s,JSON.parse(i))}}catch{}}return s}}function J(r,t){const e=t.toLowerCase();let s=0;const n=r.title.toLowerCase();n===e?s=200:n.startsWith(e)?s=150:n.includes(e)&&(s=100);const o=[...r.additional_titles||[],...r.attached_texts||[]];for(const l of o){const h=l.toLowerCase();h===e?s=Math.max(s,120):h.startsWith(e)?s=Math.max(s,90):h.includes(e)&&(s=Math.max(s,60))}let i=0;if(r.author){const l=r.author.toLowerCase();l===e?i=80:l.includes(e)&&(i=50)}let a=0;r.dynasty&&r.dynasty.toLowerCase().includes(e)&&(a=30);let c=s;return c===0&&(c=Math.max(i,a)),c===0?0:(c+=Math.max(0,20-n.length),r.type==="work"?c=Math.round(c*1.05):r.type==="collection"&&(c=Math.round(c*1.02)),r.has_text&&(c+=3),r.has_image&&(c+=2),c)}function Pt(r,t){return t===null?void 0:t}function W(r,t){const e=r.map(s=>({entry:s,score:J(s,t)})).filter(s=>s.score>0);return e.sort((s,n)=>n.score!==s.score?n.score-s.score:s.entry.title.length-n.entry.title.length),e.map(s=>s.entry)}function V(r,t,e,s){const n=r.map(o=>{const i=J(o,t);let a=0;const c=s[o.id];if(c&&e){const l={...o,title:c.t??o.title,author:c.a??o.author,additional_titles:c.at??o.additional_titles,attached_texts:c.axt??o.attached_texts};a=J(l,e)}return{entry:o,score:Math.max(i,a)}}).filter(o=>o.score>0);return n.sort((o,i)=>i.score!==o.score?i.score-o.score:o.entry.title.length-i.entry.title.length),n.map(o=>o.entry)}function jt(r){const t=r.volumes??[];let e,s;if(t.length===0)e=[];else if(typeof t[0]=="number")e=t;else{s=[],e=[];for(const o of t){const i=o.volume;e.push(i);const a={};for(const[c,l]of Object.entries(o))c==="volume"||c==="status"||c==="file"||typeof l=="string"&&(c.includes("url")||c.includes("id"))&&(a[c]=l);s.push({volume:i,status:o.status,urls:Object.keys(a).length>0?a:void 0,file:o.file})}}const n={title:r.title??"",book_id:r.book_id??null,work_id:r.work_id??null,volumes:e,section:r.section,sub_items:r.sub_items,edition:r.edition,expected_volumes:r.expected_volumes,found_volumes:r.found_volumes,missing_volumes:r.missing_vols??r.missing_volumes};s&&(n.volume_details=s);for(const o of Object.keys(n))n[o]===void 0&&delete n[o];return n}function Tt(r){const t={total_books:r.total_books??0};return r.processed_volumes!=null&&(t.processed_volumes=r.processed_volumes),r.matched_works!=null&&(t.matched_works=r.matched_works),r.unmatched_works!=null&&(t.unmatched_works=r.unmatched_works),r.total_found_volumes!=null&&(t.total_found_volumes=r.total_found_volumes),t}function z(r){var n;const t=r,e=(t.books??[]).map(jt),s={collection_id:t.collection_id??"",title:t.title??"",total_volumes:t.total_volumes??0,stats:Tt(t.stats??{}),books:e};return t.source&&(s.source=t.source),t.resource_id&&(s.resource_id=t.resource_id),t.resource_name&&(s.resource_name=t.resource_name),(n=t.sections)!=null&&n.length&&(s.sections=t.sections),t.volume_index&&Object.keys(t.volume_index).length>0&&(s.volume_index=t.volume_index),s}const Bt="https://raw.githubusercontent.com",Lt=["https://fastly.jsdelivr.net/gh","https://cdn.jsdelivr.net/gh"],Ft=5e3;class Rt{constructor(t){this.cache=null,this.cacheLoading=null,this.pathMap=new Map,this.searchSCache=null,this.t2sConverter=null,this.config={org:t.org,repos:t.repos,baseUrl:t.baseUrl??Bt,cdnUrls:t.cdnUrls??Lt,timeout:t.timeout??Ft}}async ensureLoaded(){if(this.cache)return this.cache;if(this.cacheLoading)return this.cacheLoading;this.cacheLoading=(async()=>{const t=[];for(const s of[!0,!1])try{const n=s?this.config.repos.draft:this.config.repos.official,o=await this.fetchIndex(n),i=this.parseIndexResponse(o,s);t.push(...i)}catch(n){console.warn(`Failed to fetch ${s?"draft":"official"} index:`,n)}const e=new Map;for(const s of t)e.set(s.id,s);return this.cache=Array.from(e.values()),this.cache})();try{return await this.cacheLoading}finally{this.cache||(this.cacheLoading=null)}}async fetchFileWithFallback(t,e){const s=`${this.config.baseUrl}/${this.config.org}/${t}/main/${encodeURI(e)}`;try{return await this.fetchJson(s)}catch{}for(const n of this.config.cdnUrls){const o=`${n}/${this.config.org}/${t}@main/${encodeURI(e)}`;try{return await this.fetchJson(o)}catch{continue}}throw new Error(`Failed to fetch ${e} for ${t} from all sources`)}async probeIndex(t){const e="index/collections.json",s=`${this.config.baseUrl}/${this.config.org}/${t}/main/${encodeURI(e)}`;try{if((await fetch(s,{method:"HEAD",signal:AbortSignal.timeout(this.config.timeout)})).ok)return!0}catch{}for(const i of this.config.cdnUrls){const a=`${i}/${this.config.org}/${t}@main/${encodeURI(e)}`;try{if((await fetch(a,{method:"HEAD",signal:AbortSignal.timeout(this.config.timeout)})).ok)return!0}catch{continue}}const n="index/works/0.json",o=`${this.config.baseUrl}/${this.config.org}/${t}/main/${encodeURI(n)}`;try{if((await fetch(o,{method:"HEAD",signal:AbortSignal.timeout(this.config.timeout)})).ok)return!0}catch{}for(const i of this.config.cdnUrls){const a=`${i}/${this.config.org}/${t}@main/${encodeURI(n)}`;try{if((await fetch(a,{method:"HEAD",signal:AbortSignal.timeout(this.config.timeout)})).ok)return!0}catch{continue}}return!1}async fetchIndex(t){if(!await this.probeIndex(t))return{books:{},collections:{},works:{}};const s={books:{},collections:{},works:{}};try{const o=await this.fetchFileWithFallback(t,"index/collections.json");s.collections=o}catch{}const n=[];for(const o of["books","works"])for(let i=0;i<L;i++){const a=`index/${o}/${i.toString(16)}.json`;n.push(this.fetchFileWithFallback(t,a).then(c=>{Object.assign(s[o],c)}).catch(()=>{}))}return await Promise.all(n),s}async fetchJson(t){const e=await fetch(t,{cache:"no-store",signal:AbortSignal.timeout(this.config.timeout)});if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);return e.json()}parseIndexResponse(t,e){var o,i;const s=[],n=[["books","book"],["collections","collection"],["works","work"],["entities","entity"]];for(const[a,c]of n){const l=t[a];if(l)for(const h of Object.values(l)){const u=h,d=c==="entity"?u.primary_name||h.title||h.name||h.id:h.title||h.name||h.id;s.push({id:h.id,title:d,type:c,isDraft:e,author:h.author,dynasty:h.dynasty,role:h.role,path:h.path,additional_titles:(o=u.additional_titles)==null?void 0:o.map(f=>typeof f=="string"?f:f==null?void 0:f.book_title).filter(Boolean),attached_texts:(i=u.attached_texts)==null?void 0:i.map(f=>typeof f=="string"?f:f==null?void 0:f.book_title).filter(Boolean),edition:u.edition,juan_count:u.juan_count,has_text:u.has_text,has_image:u.has_image,has_collated:u.has_collated,subtype:u.subtype,primary_name:u.primary_name,birth_year:u.birth_year,death_year:u.death_year,cbdb_id:u.cbdb_id}),this.pathMap.set(h.id,{path:h.path,isDraft:e})}}return s}async ensureSearchSBuilt(){var n,o;if(this.searchSCache)return{searchS:this.searchSCache,converter:this.t2sConverter||null};if(this.t2sConverter===!1)return{searchS:{},converter:null};try{const i=await import("opencc-js");this.t2sConverter=i.Converter({from:"tw",to:"cn"})}catch{return this.t2sConverter=!1,this.searchSCache={},{searchS:{},converter:null}}const t=await this.ensureLoaded(),e=this.t2sConverter,s={};for(const i of t){const a={},c=e(i.title);if(c!==i.title&&(a.t=c),i.author){const l=e(i.author);l!==i.author&&(a.a=l)}if((n=i.additional_titles)!=null&&n.length){const l=i.additional_titles.map(e);l.some((h,u)=>h!==i.additional_titles[u])&&(a.at=l)}if((o=i.attached_texts)!=null&&o.length){const l=i.attached_texts.map(e);l.some((h,u)=>h!==i.attached_texts[u])&&(a.axt=l)}Object.keys(a).length>0&&(s[i.id]=a)}return this.searchSCache=s,{searchS:s,converter:e}}async loadEntries(t,e){let n=(await this.ensureLoaded()).filter(u=>u.type===t);const o=e.sortBy||"title",i=e.sortOrder||"asc";n.sort((u,d)=>{const f=String(u[o]??""),m=String(d[o]??""),I=f.localeCompare(m,"zh");return i==="asc"?I:-I});const a=e.page||1,c=e.pageSize||50,l=(a-1)*c;return{entries:n.slice(l,l+c),total:n.length,page:a,pageSize:c}}async search(t,e,s){const n=await this.ensureLoaded(),{searchS:o,converter:i}=await this.ensureSearchSBuilt(),a=n.filter(m=>m.type===e),c=i?i(t):void 0,h=Object.keys(o).length>0?V(a,t,c,o):W(a,t),u=s.page||1,d=s.pageSize||50,f=(u-1)*d;return{entries:h.slice(f,f+d),total:h.length,page:u,pageSize:d}}async searchAll(t,e=5){const s=await this.ensureLoaded(),{searchS:n,converter:o}=await this.ensureSearchSBuilt(),i=["work","book","collection"],a=o?o(t):void 0,c=Object.keys(n).length>0,l=i.map(h=>{const u=s.filter(d=>d.type===h);return c?V(u,t,a,n):W(u,t)});return{works:l[0].slice(0,e),books:l[1].slice(0,e),collections:l[2].slice(0,e),totalWorks:l[0].length,totalBooks:l[1].length,totalCollections:l[2].length}}async getItem(t){const e=await this.ensureLoaded(),s=this.pathMap.get(t);let n=null;if(s){const o=s.isDraft?this.config.repos.draft:this.config.repos.official;if(n=await this.fetchItemByPath(o,s.path),n){const i=e.find(a=>a.id===t);i!=null&&i.has_collated&&(n.has_collated=!0)}}else n=await this.findItemById(t);return n&&n.type==="entity"&&!n.title&&n.primary_name&&(n.title=n.primary_name),n}async getEntry(t){const s=(await this.ensureLoaded()).find(o=>o.id===t);if(s)return s;const n=await this.findItemById(t);return n?this.buildEntryFromItem(t,n):null}async getAllEntries(){return this.ensureLoaded()}async saveItem(){throw new Error("GithubStorage 为只读模式,不支持保存")}async deleteItem(){throw new Error("GithubStorage 为只读模式,不支持删除")}async generateId(){throw new Error("GithubStorage 为只读模式,不支持生成 ID")}async fetchItemByPath(t,e){let s=null;const n=`${this.config.baseUrl}/${this.config.org}/${t}/main/${encodeURI(e)}`;try{s=await this.fetchJson(n)}catch{}if(!s)for(const o of this.config.cdnUrls){const i=`${o}/${this.config.org}/${t}@main/${encodeURI(e)}`;try{s=await this.fetchJson(i);break}catch{continue}}return s}async findItemById(t){const e={book:"Book",collection:"Collection",work:"Work",entity:"Entity"};let s,n;try{const h=$(k(t));s=h.type,n=h.status}catch{return null}const o=n==="draft"?this.config.repos.draft:this.config.repos.official,i=e[s],a=t.padEnd(3,"_").substring(0,3),c=`${i}/${a[0]}/${a[1]}/${a[2]}`,l=`https://api.github.com/repos/${this.config.org}/${o}/contents/${c}`;try{const h=await fetch(l,{signal:AbortSignal.timeout(this.config.timeout)});if(!h.ok)return null;const d=(await h.json()).find(m=>m.name.startsWith(`${t}-`)&&m.name.endsWith(".json"));if(!d)return null;const f=await this.fetchItemByPath(o,d.path);return f&&this.pathMap.set(t,{path:d.path,isDraft:n==="draft"}),f}catch{return null}}buildEntryFromItem(t,e){const s=(e.type||"").toLowerCase(),n=s==="work"?"work":s==="collection"?"collection":"book";let o;const i=e.authors;if(Array.isArray(i)&&i.length>0){const l=i[0];o=typeof l=="object"&&l!==null?l.name||"":String(l)}let a=!0;try{a=$(k(t)).status==="draft"}catch{}const c=this.pathMap.get(t);return{id:t,title:e.title||e.书名||t,type:n,isDraft:a,author:o,dynasty:e.dynasty,role:e.role,path:(c==null?void 0:c.path)||"",juan_count:e.n_juan,has_collated:e.has_collated}}async fetchFile(t,e){const s=`${this.config.baseUrl}/${this.config.org}/${t}/main/${encodeURI(e)}`;try{return await this.fetchJson(s)}catch{}for(const n of this.config.cdnUrls){const o=`${n}/${this.config.org}/${t}@main/${encodeURI(e)}`;try{return await this.fetchJson(o)}catch{continue}}return null}async resolveItemPath(t){await this.ensureLoaded();let e=this.pathMap.get(t);if(!e&&(await this.findItemById(t),e=this.pathMap.get(t),!e))return null;const s=e.isDraft?this.config.repos.draft:this.config.repos.official,n=e.path.substring(0,e.path.lastIndexOf("/"));return{repo:s,dir:n}}async getCollectionCatalogs(t){const e=await this.resolveItemPath(t);if(!e)return null;const s=await this.getItem(t);if(!s)return null;const n=s.resources||[];if(n.length===0)return null;const o=[];for(const i of n){const a=`${e.dir}/${t}/${i.id}/volume_book_mapping.json`,c=await this.fetchFile(e.repo,a);c&&o.push({resource_id:i.id,short_name:i.short_name,data:z(c)})}return o.length>0?o:null}async getCollectionCatalog(t){var s;const e=await this.getCollectionCatalogs(t);return((s=e==null?void 0:e[0])==null?void 0:s.data)??null}async getCollatedEditionIndex(t){const e=await this.resolveItemPath(t);if(!e)return null;const s=`${e.dir}/${t}/collated_edition/collated_edition_index.json`;return this.fetchFile(e.repo,s)}async getCollatedJuan(t,e){if(e.includes("..")||!e.endsWith(".json"))return null;const s=await this.resolveItemPath(t);if(!s)return null;const n=`${s.dir}/${t}/collated_edition/${e}`;return this.fetchFile(s.repo,n)}async getLineageGraph(t){const e=await this.resolveItemPath(t);if(!e)return null;const s=`${e.dir}/${t}/lineage_graph.json`;return this.fetchFile(e.repo,s)}async getCatalogProgress(){return this.fetchFile(this.config.repos.draft,"resource-catalog.json")}async getCollectionProgress(){return this.fetchFile(this.config.repos.draft,"resource-collection.json")}async getResourceProgress(){return this.fetchFile(this.config.repos.draft,"resource.json")}async getSiteProgress(){return this.fetchFile(this.config.repos.draft,"resource-site.json")}async getRecommended(){return this.fetchFile(this.config.repos.draft,"recommended.json")}clearCache(){this.cache=null,this.pathMap.clear(),this.searchSCache=null,this.t2sConverter=null}}const Q=255;class Y{constructor(t){if(this.lastTimestamp=-1,this.lastStatus=null,this.sequence=0,t<0||t>2047)throw new Error("Machine ID must be between 0 and 2047");this.machineId=t}nextId(t,e){let s=this._getCurrentTimestamp(t);if(s<this.lastTimestamp&&t===this.lastStatus)throw new Error("Clock moved backwards. Refusing to generate ID.");s===this.lastTimestamp&&t===this.lastStatus?(this.sequence=this.sequence+1&Q,this.sequence===0&&(s=this._tilNextUnit(this.lastTimestamp,t))):this.sequence=0,this.lastTimestamp=s,this.lastStatus=t;const n=N(t,e,BigInt(s),this.machineId,this.sequence);return F(n)}nextIdRaw(t,e){let s=this._getCurrentTimestamp(t);if(s<this.lastTimestamp&&t===this.lastStatus)throw new Error("Clock moved backwards. Refusing to generate ID.");return s===this.lastTimestamp&&t===this.lastStatus?(this.sequence=this.sequence+1&Q,this.sequence===0&&(s=this._tilNextUnit(this.lastTimestamp,t))):this.sequence=0,this.lastTimestamp=s,this.lastStatus=t,N(t,e,BigInt(s),this.machineId,this.sequence)}_getCurrentTimestamp(t){const e=Date.now();return t==="draft"?e:Math.floor(e/1e3)}_tilNextUnit(t,e){let s=this._getCurrentTimestamp(e);for(;s<=t;)s=this._getCurrentTimestamp(e);return s}}class Dt{constructor(t){this.recentEntities=[],this.storage=new q(t.fs,t.workspaceRoot),this.idGen=new Y(t.machineId??0),this.fs=t.fs,this.workspaceRoot=t.workspaceRoot}async loadEntries(t,e){const s=await this.storage.loadEntries(t);return this.paginate(s,e)}async search(t,e,s){const n=await this.storage.searchEntries(t,e);return this.paginate(n,s)}async searchAll(t,e){return this.storage.searchAll(t,e)}async getItem(t){const e=await this.storage.getItem(t);return e&&e.type==="entity"&&!e.title&&e.primary_name&&(e.title=e.primary_name),e}async saveItem(t){const e=t.id;if(!e)throw new Error("metadata.id is required");const s=t.type||this.extractTypeFromId(e),n=await this.storage.saveItem(s,e,t);return{id:e,path:n}}async deleteItem(t){if(!await this.storage.deleteItem(t))throw new Error(`Item not found: ${t}`)}async generateId(t,e){return this.idGen.nextId(e,t)}async getEntry(t){const e=await this.storage.getItem(t);if(!e)return null;const s=e.type||this.extractTypeFromId(t);return{id:t,title:e.title||"未命名",type:s,author:this.extractAuthor(e),dynasty:this.extractYear(e)}}async getAllEntries(){const t=["book","collection","work"],e=[];for(const s of t){const n=await this.storage.loadEntries(s);e.push(...n)}return e}async getRelations(t){const e=await this.storage.getItem(t);if(!e)return null;const s={},n=e.type||this.extractTypeFromId(t);if(n==="book"){if(e.work_id){const o=await this.resolveEntity(e.work_id);o&&(s.belongsToWork={...o,type:"work"})}if(e.contained_in&&Array.isArray(e.contained_in)&&e.contained_in.length>0){const o=e.contained_in[0],i=typeof o=="string"?o:o.id;if(i){const a=await this.resolveEntity(i);a&&(s.belongsToCollection={...a,type:"collection"})}}}else if(n==="collection"){if(e.books&&Array.isArray(e.books)){const o=await Promise.all(e.books.map(i=>this.resolveEntity(i)));s.containedBooks=o.filter(i=>i!==null).map(i=>({...i,type:"book"}))}}else if(n==="work"){if(e.parent_work&&typeof e.parent_work=="object"){const o=e.parent_work;s.parentWork={id:o.id,title:o.title,type:"work"}}if(e.books&&Array.isArray(e.books)){const o=await Promise.all(e.books.map(i=>this.resolveEntity(i)));s.containedBooks=o.filter(i=>i!==null).map(i=>({...i,type:"book"}))}}return s}async linkEntity(t,e,s){const n=await this.storage.getItem(t);if(!n)throw new Error(`Source not found: ${t}`);const o=n.type||this.extractTypeFromId(t);switch(e){case"belongsToWork":case"work_id":n.work_id=s;break;case"belongsToCollection":case"contained_in":Array.isArray(n.contained_in)||(n.contained_in=[]);{const i=n.contained_in;i.some(c=>(typeof c=="string"?c:c.id)===s)||i.push({id:s})}break;case"parentWork":case"parent_work":{const i=await this.storage.getItem(s);n.parent_work={id:s,title:(i==null?void 0:i.title)||""};break}case"containedBooks":case"books":Array.isArray(n.books)||(n.books=[]),n.books.includes(s)||n.books.push(s);break;default:throw new Error(`Unknown relation field: ${e}`)}await this.storage.saveItem(o,t,n)}async unlinkEntity(t,e){const s=await this.storage.getItem(t);if(!s)throw new Error(`Source not found: ${t}`);const n=s.type||this.extractTypeFromId(t);switch(e){case"belongsToWork":case"work_id":delete s.work_id;break;case"belongsToCollection":case"contained_in":s.contained_in=[];break;case"parentWork":case"parent_work":delete s.parent_work;break;case"containedBooks":case"books":s.books=[];break;default:throw new Error(`Unknown relation field: ${e}`)}await this.storage.saveItem(n,t,s)}async createAndLink(t,e,s){const n=this.idGen.nextId("draft",s.type),o={id:n,type:s.type,title:s.title,...s.inheritData||{}};return await this.storage.saveItem(s.type,n,o),await this.linkEntity(t,e,n),{id:n}}async searchEntities(t,e){const s=e&&e!=="all"?[e]:["book","collection","work"],n=[];for(const o of s){const i=await this.storage.searchEntries(t,o);for(const a of i)n.push({id:a.id,title:a.title,type:a.type,author:a.author,dynasty:a.dynasty})}return n}async getRecentEntities(){return this.recentEntities}async addRecentEntity(t){this.recentEntities=[t,...this.recentEntities.filter(e=>e.id!==t.id)].slice(0,20)}async getCollectionCatalog(t){const e=await this.storage.findFileById(t);if(!e)return null;const n=e.substring(0,e.lastIndexOf("/"))+"/volume_book_mapping.json";try{const o=await this.storage.loadMetadata(n);return Object.keys(o).length===0?null:o}catch{return null}}async getRecommended(){const t=this.workspaceRoot+"/book-index-draft/recommended.json";try{const e=await this.fs.readFile(t);return JSON.parse(e)}catch{return null}}getAssetDir(t){return this.storage.getAssetDir(t)}async initAssetDir(t){return this.storage.initAssetDir(t)}async hasAssetDir(t){return this.storage.hasAssetDir(t)}getBookIndexStorage(){return this.storage}async rebuildIndex(t){await this.storage.rebuildIndex(t)}extractTypeFromId(t){try{const e=k(t);return $(e).type}catch{return"book"}}extractAuthor(t){const e=t.authors;if(Array.isArray(e)&&e.length>0){const s=e[0];return typeof s=="object"&&s!==null?s.name||"":String(s)}}extractYear(t){const e=t.publication_info;if(typeof e=="object"&&e!==null)return e.year||void 0}async resolveEntity(t){const e=await this.storage.getItem(t);return e?{id:t,title:e.title||"未命名"}:null}paginate(t,e){const s=e.page??1,n=e.pageSize??50;if(e.sortBy){const c=e.sortBy,l=e.sortOrder==="desc"?-1:1;t.sort((h,u)=>{const d=h[c]??"",f=u[c]??"";return d<f?-l:d>f?l:0})}const o=t.length,i=(s-1)*n;return{entries:t.slice(i,i+n),total:o,page:s,pageSize:n}}}const Ut="/data",Mt=1e4;class Ot{constructor(t={}){this.chunkCache=new Map,this.chunkLoading=new Map,this.manifest=null,this.manifestLoading=null,this.tiyaoCache=new Map,this.tiyaoLoading=new Map,this.metaCache=null,this.metaLoading=null,this.version=void 0,this.versionPromise=null,this.basePath=t.basePath??Ut,this.timeout=t.timeout??Mt}async ensureVersion(){return this.version!==void 0?this.version:this.versionPromise?this.versionPromise:(this.versionPromise=(async()=>{try{const t=await fetch(`${this.basePath}/version.json`,{cache:"no-cache",signal:AbortSignal.timeout(this.timeout)});if(!t.ok)return null;const e=await t.json(),s=e==null?void 0:e.commitId;return!s||s==="unknown"?null:s.slice(0,12)}catch{return null}})(),this.version=await this.versionPromise,this.version)}async fetchJson(t){const e=await this.ensureVersion(),s=e?`${t}${t.includes("?")?"&":"?"}v=${e}`:t,n=await fetch(s,{cache:"no-cache",signal:AbortSignal.timeout(this.timeout)});if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);return n.json()}async loadManifest(){if(this.manifest)return this.manifest;if(this.manifestLoading)return this.manifestLoading;this.manifestLoading=(async()=>{try{this.manifest=await this.fetchJson(`${this.basePath}/chunks/_manifest.json`)}catch{this.manifest=[]}return this.manifest})();try{return await this.manifestLoading}finally{this.manifest||(this.manifestLoading=null)}}async resolvePrefix(t){const e=await this.loadManifest();let s=0,n=e.length-1,o=null;for(;s<=n;){const i=s+n>>1;e[i]<=t?(o=e[i],s=i+1):n=i-1}if(o&&t.startsWith(o))return o;for(const i of e)if(t.startsWith(i))return i;return null}async loadChunk(t){if(this.chunkCache.has(t))return this.chunkCache.get(t);const e=this.chunkLoading.get(t);if(e)return e;const s=(async()=>{try{const n=await this.fetchJson(`${this.basePath}/chunks/${t}.json`);return this.chunkCache.set(t,n),n}catch{return this.chunkCache.set(t,{}),{}}})();this.chunkLoading.set(t,s);try{return await s}finally{this.chunkLoading.delete(t)}}async loadChunkForId(t){const e=await this.resolvePrefix(t);return e?this.loadChunk(e):{}}async loadTiyaoGroup(t,e){const s=`${t}-${e}`;if(this.tiyaoCache.has(s))return this.tiyaoCache.get(s);const n=this.tiyaoLoading.get(s);if(n)return n;const o=a=>String(a).padStart(3,"0"),i=(async()=>{const a=await this.fetchJson(`${this.basePath}/tiyao/juan-${o(t)}-${o(e)}.json`);return this.tiyaoCache.set(s,a),a})();this.tiyaoLoading.set(s,i);try{return await i}finally{this.tiyaoLoading.delete(s)}}async getCounts(){if(this.metaCache)return this.metaCache;if(this.metaLoading){const t=await this.metaLoading;if(t)return t;throw new Error("BundleStorage: meta.json 加载失败")}this.metaLoading=(async()=>{const t=await this.fetchJson(`${this.basePath}/meta.json`);if(!t||typeof t.works!="number")throw new Error("BundleStorage: meta.json 格式无效");return this.metaCache=t,t})();try{const t=await this.metaLoading;if(!t)throw new Error("BundleStorage: meta.json 加载失败");return t}finally{this.metaLoading=null}}async getResourceCounts(){return(await this.getCounts()).resourceCounts??{hasText:0,hasImage:0}}async getSubtypeStats(){return(await this.getCounts()).subtypeStats??{}}async loadEntries(t,e){throw new Error("BundleStorage.loadEntries 已废弃:请使用 worker 搜索")}async search(t,e,s){throw new Error("BundleStorage.search 已废弃:请使用 worker 搜索")}async searchAll(t,e=5){throw new Error("BundleStorage.searchAll 已废弃:请使用 worker 搜索")}async getItem(t){try{const s=(await this.loadChunkForId(t))[t]||null;return s&&s.type==="entity"&&!s.title&&s.primary_name&&(s.title=s.primary_name),s}catch{return null}}async getEntry(t){var e,s;try{const o=(await this.loadChunkForId(t))[t];if(!o)return null;const i=lt(t),a=i==="entity"?o.primary_name||o.title||o.name||t:o.title||o.name||t;return{id:t,title:a,type:i,isDraft:!0,author:o.author,dynasty:o.dynasty,role:o.role,additional_titles:(e=o.additional_titles)==null?void 0:e.map(c=>typeof c=="string"?c:c==null?void 0:c.book_title).filter(Boolean),attached_texts:(s=o.attached_texts)==null?void 0:s.map(c=>typeof c=="string"?c:c==null?void 0:c.book_title).filter(Boolean),edition:o.edition,juan_count:o.juan_count,has_text:o.has_text,has_image:o.has_image,has_collated:o.has_collated,subtype:o.subtype,primary_name:o.primary_name,birth_year:o.birth_year,death_year:o.death_year,cbdb_id:o.cbdb_id}}catch{return null}}async getAllEntries(){throw new Error("BundleStorage.getAllEntries 已废弃:请使用 getEntry / worker 搜索")}async saveItem(){throw new Error("BundleStorage 为只读模式,不支持保存")}async deleteItem(){throw new Error("BundleStorage 为只读模式,不支持删除")}async generateId(){throw new Error("BundleStorage 为只读模式,不支持生成 ID")}async getCollectionCatalogs(t){const e=await this.getItem(t);if(!e)return null;const s=e.resources||[];if(s.length===0)return null;const n=[];for(const o of s)try{const i=await this.fetchJson(`${this.basePath}/items/${t}/${o.id}/volume_book_mapping.json`);i&&n.push({resource_id:o.id,short_name:o.short_name,data:z(i)})}catch{}return n.length>0?n:null}async getCollectionCatalog(t){var s;const e=await this.getCollectionCatalogs(t);return((s=e==null?void 0:e[0])==null?void 0:s.data)??null}async getCollatedEditionIndex(t){try{return await this.fetchJson(`${this.basePath}/items/${t}/collated_edition/collated_edition_index.json`)}catch{return null}}async getCollatedJuan(t,e){if(e.includes("..")||!e.endsWith(".json"))return null;try{return await this.fetchJson(`${this.basePath}/items/${t}/collated_edition/${e}`)}catch{return null}}async getCollatedJuanText(t,e){if(e.includes("..")||!e.endsWith(".json"))return null;const s=e.replace(/\.json$/,".md"),n=await this.ensureVersion(),o=`${this.basePath}/items/${t}/collated_edition/text/${s}`,i=n?`${o}?v=${n}`:o;try{const a=await fetch(i,{cache:"no-cache"});return a.ok?await a.text():null}catch{return null}}async getLineageGraph(t){try{return await this.fetchJson(`${this.basePath}/items/${t}/lineage_graph.json`)}catch{return null}}async getCatalogProgress(){try{return await this.fetchJson(`${this.basePath}/resource-catalog.json`)}catch{return null}}async getCollectionProgress(){try{return await this.fetchJson(`${this.basePath}/resource-collection.json`)}catch{return null}}async getResourceProgress(){try{return await this.fetchJson(`${this.basePath}/resource.json`)}catch{return null}}async getSiteProgress(){try{return await this.fetchJson(`${this.basePath}/resource-site.json`)}catch{return null}}async getRecommended(){try{return await this.fetchJson(`${this.basePath}/recommended.json`)}catch{return null}}clearCache(){this.chunkCache.clear(),this.chunkLoading.clear(),this.manifest=null,this.manifestLoading=null,this.tiyaoCache.clear(),this.tiyaoLoading.clear(),this.metaCache=null,this.metaLoading=null,this.version=void 0,this.versionPromise=null}}class T extends Error{constructor(t){super(t),this.name="BookIndexError"}}class Nt extends T{constructor(t){super(t),this.name="StorageError"}}class Jt extends T{constructor(t){super(t),this.name="IdGenerationError"}}class Wt extends T{constructor(t){super(t),this.name="ConfigError"}}class Gt extends T{constructor(t){super(t),this.name="MigrationError"}}class Ht{constructor(t,e,s=1){this.storage=new q(t,e),this.idGen=new Y(s)}generateId(t="book",e="draft"){return this.idGen.nextIdRaw(e,t)}encodeId(t){return F(t)}decodeId(t){return k(t)}async saveItem(t,e,s="draft"){let n=t.id||t.ID;if(n)try{const i=j(n);e||(e=i.type)}catch{throw new T(`Invalid ID format: ${n}`)}else e||(e=t.type||"book"),n=this.idGen.nextId(s,e),t.id=n;return await this.storage.saveItem(e,n,t)}async getItem(t){return this.storage.getItem(t)}async findItemPath(t){return this.storage.findFileById(t)}async updateField(t,e,s){const n=await this.storage.findFileById(t);if(!n)return!1;try{const o=await this.storage.loadMetadata(n),i={基本信息:null,介绍:"description",资源:"resources",收藏历史:"history",其他版本:"related_books"},a=e in i?i[e]:e;if(a===null)return!1;if(a==="description"&&typeof s=="string"){const l=o[a]||{};o[a]={text:s,sources:l.sources||[]}}else o[a]=s;const c=j(t);return await this.storage.saveItem(c.type,t,o),!0}catch{return!1}}async deleteItem(t){return this.storage.deleteItem(t)}async rebuildIndices(){await this.storage.rebuildIndex("official"),await this.storage.rebuildIndex("draft")}getAssetDir(t){return this.storage.getAssetDir(t)}async initAssetDir(t){return this.storage.initAssetDir(t)}async hasAssetDir(t){return this.storage.hasAssetDir(t)}getStorage(){return this.storage}}const qt={wikisource:"wikisource",shidianguji:"shidianguji",archive:"archive",ctext:"ctext",nlc:"nlc","read.nlc":"nlc","db.sido":"sido","guji.artx":"guji-artx","digital.library":"digital-library"},Z=new Set(["text","image","text+image","physical"]),tt=new Set(["text","image","physical"]),zt=new Set(["catalog","search"]),Yt=new Set(["com","org","net","cn","edu","gov","io","jp","tw","hk"]);function Kt(r){if(!r)return"";try{const e=new URL(r).hostname;for(const[n,o]of Object.entries(qt))if(e.includes(n))return o;const s=e.split(".");if(s.length>=2){const n=s.filter(o=>!Yt.has(o)&&o.length>2);return n.length>0?n[n.length-1]:s[s.length-2]}return e}catch{return""}}function Xt(r){const t=[];r.name||t.push("name is required");let e=!1;if(r.types!==void 0)if(!Array.isArray(r.types)||r.types.length===0)t.push("types must be a non-empty array when present");else{for(const s of r.types)tt.has(s)||t.push(`invalid types atom '${s}', must be one of ${[...tt].join(", ")}`);e=r.types.length===1&&r.types[0]==="physical"}else r.type!==void 0?(Z.has(r.type)||t.push(`invalid type '${r.type}', must be one of ${[...Z].join(", ")}`),e=r.type==="physical"):t.push("either type or types is required");return r.root_type&&!zt.has(r.root_type)&&t.push(`invalid root_type '${r.root_type}'`),!e&&!r.url&&t.push("url is required for non-physical resources"),t}const w=class w{constructor(t,e=""){if(this._type=null,this.title=e,typeof t=="string"){t.startsWith(w.PREFIX)&&(t=t.slice(w.PREFIX.length)),this.idStr=t;try{this.idInt=k(t)}catch{this.idInt=0n}}else this.idInt=t,this.idStr=F(t);if(this.idInt>0n)try{const s=j(this.idStr);this._type=s.type}catch{}}get type(){return this._type}getIcon(){return this._type===null?"":this._type==="book"?"📖 ":this._type==="collection"?"📚 ":this._type==="work"?"📜 ":""}render(t=!1){return`[${t?this.getIcon():""}${this.title}](${w.PREFIX}${this.idStr})`}static parseFromLink(t){const e=t.match(/\[(.*?)\]\((.*?)\)/);if(e){const s=e[1],n=e[2];if(n.startsWith(w.PREFIX)){const o=n.slice(w.PREFIX.length);return new w(o,s)}}return null}static isBidLink(t){return t.startsWith(w.PREFIX)}};w.PROTOCOL="bid:\\\\",w.PREFIX="bid:\\\\";let G=w;exports.BidLink=G;exports.BookIndexError=T;exports.BookIndexManager=Ht;exports.BookIndexStorage=q;exports.BundleStorage=Ot;exports.ConfigError=Wt;exports.GithubStorage=Rt;exports.IdGenerationError=Jt;exports.IdGenerator=Y;exports.LocalStorage=Dt;exports.MigrationError=Gt;exports.StorageError=Nt;exports.base36Decode=H;exports.base36Encode=F;exports.base58Decode=ct;exports.buildId=N;exports.cleanName=ht;exports.decodeId=$t;exports.decodeIdString=j;exports.encodeId=vt;exports.extractIdFromUrl=Kt;exports.extractStatus=At;exports.extractType=lt;exports.normalizeCatalog=z;exports.parseId=$;exports.shardOf=D;exports.smartDecode=k;exports.validateResource=Xt;
@@ -69,7 +69,10 @@ function b(...r) {
69
69
  return t.startsWith("//") ? "//" + t.slice(2).replace(/\/+/g, "/") : t.replace(/\/+/g, "/");
70
70
  }
71
71
  function _t(r) {
72
- return r.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, "") || "Undefined";
72
+ return r.replace(
73
+ /[^㐀-䶿一-鿿豈-﫿a-zA-Z0-9\u{20000}-\u{3134f}]/gu,
74
+ ""
75
+ ) || "Undefined";
73
76
  }
74
77
  class ot {
75
78
  constructor(t, e) {
@@ -1770,12 +1773,14 @@ export {
1770
1773
  U as i,
1771
1774
  gt as j,
1772
1775
  q as k,
1773
- jt as l,
1774
- B as m,
1775
- Pt as n,
1776
- Nt as o,
1777
- it as p,
1778
- A as q,
1779
- v as s,
1776
+ _t as l,
1777
+ jt as m,
1778
+ B as n,
1779
+ Pt as o,
1780
+ Nt as p,
1781
+ it as q,
1782
+ A as r,
1783
+ O as s,
1784
+ v as t,
1780
1785
  Jt as v
1781
1786
  };
package/dist/storage.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("./storage-entry-D6KFK-rC.cjs");exports.BidLink=r.BidLink;exports.BookIndexError=r.BookIndexError;exports.BookIndexManager=r.BookIndexManager;exports.BookIndexStorage=r.BookIndexStorage;exports.BundleStorage=r.BundleStorage;exports.ConfigError=r.ConfigError;exports.GithubStorage=r.GithubStorage;exports.IdGenerationError=r.IdGenerationError;exports.IdGenerator=r.IdGenerator;exports.LocalStorage=r.LocalStorage;exports.MigrationError=r.MigrationError;exports.StorageError=r.StorageError;exports.extractIdFromUrl=r.extractIdFromUrl;exports.validateResource=r.validateResource;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("./storage-entry-C7sHSk4U.cjs");exports.BidLink=r.BidLink;exports.BookIndexError=r.BookIndexError;exports.BookIndexManager=r.BookIndexManager;exports.BookIndexStorage=r.BookIndexStorage;exports.BundleStorage=r.BundleStorage;exports.ConfigError=r.ConfigError;exports.GithubStorage=r.GithubStorage;exports.IdGenerationError=r.IdGenerationError;exports.IdGenerator=r.IdGenerator;exports.LocalStorage=r.LocalStorage;exports.MigrationError=r.MigrationError;exports.StorageError=r.StorageError;exports.extractIdFromUrl=r.extractIdFromUrl;exports.validateResource=r.validateResource;
package/dist/storage.js CHANGED
@@ -1,4 +1,4 @@
1
- import { B as o, b as e, c as s, d as t, f as n, C as d, G as g, I as i, g as I, L as B, M as S, S as x, o as E, v as c } from "./storage-entry-Ysz_rfdh.js";
1
+ import { B as o, b as e, c as s, d as t, f as n, C as d, G as g, I as i, g as I, L as B, M as S, S as x, p as E, v as c } from "./storage-entry-Cn1-iEHv.js";
2
2
  export {
3
3
  o as BidLink,
4
4
  e as BookIndexError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "book-index-ui",
3
- "version": "0.2.21",
3
+ "version": "0.2.22",
4
4
  "description": "React components for browsing and editing ancient book index metadata",
5
5
  "author": "Li Shaodong",
6
6
  "repository": {
@@ -43,6 +43,9 @@
43
43
  "build:app": "vite build",
44
44
  "preview": "vite preview",
45
45
  "prepublishOnly": "npm run build:lib",
46
+ "test": "vitest run",
47
+ "test:watch": "vitest",
48
+ "test:ui": "vitest --ui",
46
49
  "test:e2e": "playwright test",
47
50
  "test:e2e:mobile": "playwright test --project=mobile"
48
51
  },
@@ -67,17 +70,23 @@
67
70
  "devDependencies": {
68
71
  "@dagrejs/dagre": "^3.0.0",
69
72
  "@playwright/test": "^1.59.1",
73
+ "@testing-library/jest-dom": "^6.9.1",
74
+ "@testing-library/react": "^16.3.2",
75
+ "@testing-library/user-event": "^14.6.1",
70
76
  "@types/react": "^19.0.0",
71
77
  "@types/react-dom": "^19.0.0",
72
78
  "@vitejs/plugin-react": "^4.3.0",
79
+ "@vitest/ui": "^4.1.5",
73
80
  "@xyflow/react": "^12.10.2",
81
+ "jsdom": "^29.1.1",
74
82
  "minisearch": "^7.2.0",
75
83
  "opencc-js": "^1.0.5",
76
84
  "react": "^19.0.0",
77
85
  "react-dom": "^19.0.0",
78
86
  "typescript": "^5.6.0",
79
87
  "vite": "^6.0.0",
80
- "vite-plugin-dts": "^4.0.0"
88
+ "vite-plugin-dts": "^4.0.0",
89
+ "vitest": "^4.1.5"
81
90
  },
82
91
  "license": "Apache-2.0"
83
92
  }
@@ -1 +0,0 @@
1
- "use strict";var ut=Object.create;var K=Object.defineProperty;var dt=Object.getOwnPropertyDescriptor;var ft=Object.getOwnPropertyNames;var yt=Object.getPrototypeOf,gt=Object.prototype.hasOwnProperty;var mt=(r,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of ft(t))!gt.call(r,n)&&n!==e&&K(r,n,{get:()=>t[n],enumerable:!(s=dt(t,n))||s.enumerable});return r};var pt=(r,t,e)=>(e=r!=null?ut(yt(r)):{},mt(t||!r||!r.__esModule?K(e,"default",{value:r,enumerable:!0}):e,r));const _t={official:0,draft:1},wt={0:"official",1:"draft"},bt={book:0,collection:2,work:3,entity:4},kt={0:"book",2:"collection",3:"work",4:"entity"},et=62n,st=59n,nt=19n,ot=8n,it=(1n<<40n)-1n,It=(1n<<3n)-1n,xt=(1n<<11n)-1n,Et=(1n<<8n)-1n,D="0123456789abcdefghijklmnopqrstuvwxyz",rt=new Map;for(let r=0;r<D.length;r++)rt.set(D[r],BigInt(r));function F(r){if(r===0n)return D[0];let t="";for(;r>0n;)t=D[Number(r%36n)]+t,r=r/36n;return t}function H(r){let t=0n;for(const e of r){const s=rt.get(e);if(s===void 0)throw new Error(`Invalid Base36 character: ${e}`);t=t*36n+s}return t}const X="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",at=new Map;for(let r=0;r<X.length;r++)at.set(X[r],BigInt(r));function ct(r){let t=0n;for(const e of r){const s=at.get(e);if(s===void 0)throw new Error(`Invalid Base58 character: ${e}`);t=t*58n+s}return t}function k(r){return/[A-Z]/.test(r)?ct(r):H(r)}const St=F,vt=H;function $(r){const t=Number(r>>et&1n),e=Number(r>>st&It),s=r>>nt&it,n=Number(r>>ot&xt),o=Number(r&Et);return{status:wt[t]??"draft",type:kt[e]??"book",timestamp:s,machineId:n,sequence:o}}function N(r,t,e,s,n){return BigInt(_t[r])<<et|BigInt(bt[t])<<st|(e&it)<<nt|BigInt(s)<<ot|BigInt(n)}function j(r){return $(k(r))}function lt(r){return j(r).type}function $t(r){return j(r).status}const M={book:"Book",collection:"Collection",work:"Work",entity:"Entity"},At={Book:"book",Collection:"collection",Work:"work",Entity:"entity"},L=16;function O(r,t=L){let e=0;for(let s=0;s<r.length;s++)e=Math.imul(e,31)+r.charCodeAt(s)>>>0;return e%t}function b(...r){const t=r.join("/");return t.startsWith("//")?"//"+t.slice(2).replace(/\/+/g,"/"):t.replace(/\/+/g,"/")}function Ct(r){return r.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g,"")||"Undefined"}class q{constructor(t,e){this.fs=t,this.officialRoot=b(e,"book-index"),this.draftRoot=b(e,"book-index-draft")}getRootByStatus(t){return t==="draft"?this.draftRoot:this.officialRoot}getRootById(t){const e=k(t),s=$(e);return this.getRootByStatus(s.status)}getPath(t,e,s){const n=this.getRootById(e),o=e.padEnd(3,"_").substring(0,3),[i,a,c]=[o[0],o[1],o[2]],l=M[t];return b(n,l,i,a,c,`${e}-${Ct(s)}.json`)}async saveItem(t,e,s){const n=s.title||s.书名||"未命名",o=s.edition||"",i=o?`${n}${o}`:n,a=this.getPath(t,e,i),c=await this.findFileById(e);if(c&&c!==a)try{await this.fs.deleteFile(c)}catch{}const l=a.substring(0,a.lastIndexOf("/"));await this.fs.mkdir(l),s.id=e,s.type=t,await this.fs.writeFile(a,JSON.stringify(s,Pt,2));const h=this.getRootById(e),u=a.substring(h.length+1);return await this.updateIndexEntry(h,s,t,u),a}async updateIndexEntry(t,e,s,n){const o=e.id||"";if(!o)return;const i=`${s}s`,a=await this.loadShard(t,i,o);let c="";const l=e.authors;if(Array.isArray(l)&&l.length>0){const y=l[0];c=typeof y=="object"&&y!==null?y.name||"":String(y)}else typeof l=="string"&&(c=l);let h="";const u=e.publication_info;typeof u=="object"&&u!==null?h=u.year||"":typeof u=="string"&&(h=u);let d="";const f=e.current_location;typeof f=="object"&&f!==null?d=f.name||"":typeof f=="string"&&(d=f);const m=Array.isArray(e.additional_titles)?e.additional_titles.map(y=>typeof y=="string"?y:y==null?void 0:y.book_title).filter(Boolean):void 0,I=Array.isArray(e.attached_texts)?e.attached_texts.map(y=>typeof y=="string"?y:y==null?void 0:y.book_title).filter(Boolean):void 0;let S;const x=e.juan_count;typeof x=="number"?S=x:typeof x=="object"&&x!==null&&(S=x.number||void 0);const C=typeof e.measure_info=="string"?e.measure_info:"";let v=!1,E=!1;const B=e.resources;if(Array.isArray(B))for(const y of B){if(typeof y!="object"||y===null)continue;const _=y.types;if(Array.isArray(_))_.includes("text")&&(v=!0),_.includes("image")&&(E=!0);else{const A=y.type;(A==="text"||A==="text+image")&&(v=!0),(A==="image"||A==="text+image")&&(E=!0)}}const p={id:o,title:e.title||"未命名",type:M[s],path:n,author:c,year:h,holder:d};m&&m.length>0&&(p.additional_titles=m),I&&I.length>0&&(p.attached_texts=I),S&&(p.juan_count=S),C&&(p.measure_info=C);const P=typeof e.edition=="string"?e.edition:"";P&&(p.edition=P),v&&(p.has_text=!0),E&&(p.has_image=!0),a[o]=p,await this.saveShard(t,i,o,a)}async deleteItem(t){const e=await this.findFileById(t);if(!e)return!1;const s=k(t),n=$(s),o=this.getRootByStatus(n.status),i=`${n.type}s`,a=await this.loadShard(o,i,t);return a[t]&&(delete a[t],await this.saveShard(o,i,t,a)),await this.fs.deleteFile(e),!0}async findFileById(t){const e=t.padEnd(3,"_").substring(0,3),[s,n,o]=[e[0],e[1],e[2]];for(const i of[this.officialRoot,this.draftRoot])for(const a of["Book","Collection","Work"]){const c=b(i,a,s,n,o);if(await this.fs.exists(c))try{const h=(await this.fs.readdir(c)).find(u=>u.startsWith(`${t}-`)&&u.endsWith(".json"));if(h)return b(c,h)}catch{}}return null}async loadMetadata(t){try{const e=await this.fs.readFile(t);return JSON.parse(e)}catch{return{}}}async getItem(t){const e=await this.findFileById(t);if(!e)return null;const s=await this.loadMetadata(e);return Object.keys(s).length>0?s:null}async loadEntries(t,e){const s=e?[this.getRootByStatus(e)]:[this.officialRoot,this.draftRoot],n=[],o=`${t}s`;for(const i of s){const a=await this.loadAllShards(i,o);for(const[c,l]of Object.entries(a))n.push({id:c,title:l.title,type:t,author:l.author||void 0,dynasty:l.dynasty||void 0,role:l.role||void 0,path:b(i,l.path),additional_titles:l.additional_titles,attached_texts:l.attached_texts,edition:l.edition,juan_count:l.juan_count,has_text:l.has_text,has_image:l.has_image})}return n}async searchEntries(t,e,s){const n=await this.loadEntries(e,s);return W(n,t)}async searchAll(t,e=5,s){const n=["work","book","collection"],o=await Promise.all(n.map(i=>this.searchEntries(t,i,s)));return{works:o[0].slice(0,e),books:o[1].slice(0,e),collections:o[2].slice(0,e),totalWorks:o[0].length,totalBooks:o[1].length,totalCollections:o[2].length}}async rebuildIndex(t){var n,o;const e=this.getRootByStatus(t),s={books:Object.fromEntries(Array.from({length:L},(i,a)=>[a,{}])),collections:{0:{}},works:Object.fromEntries(Array.from({length:L},(i,a)=>[a,{}]))};for(const i of["Book","Collection","Work"]){const a=b(e,i);if(!await this.fs.exists(a))continue;const l=`${At[i]}s`,h=await this.fs.glob(a,"**/*.json");for(const u of h)if(!u.includes("/index/"))try{const d=await this.loadMetadata(u);let f=d.id||d.ID||"";if(!f){const g=u.substring(u.lastIndexOf("/")+1);g.includes("-")&&(f=g.split("-")[0])}if(!f)continue;const m=u.substring(e.length+1);let I="";const S=d.authors;if(Array.isArray(S)&&S.length>0){const g=S[0];I=typeof g=="object"&&g!==null?g.name||"":String(g)}const x=Array.isArray(d.additional_titles)?d.additional_titles.map(g=>typeof g=="string"?g:g==null?void 0:g.book_title).filter(Boolean):void 0,C=Array.isArray(d.attached_texts)?d.attached_texts.map(g=>typeof g=="string"?g:g==null?void 0:g.book_title).filter(Boolean):void 0;let v;const E=d.juan_count;typeof E=="number"?v=E:typeof E=="object"&&E!==null&&(v=E.number||void 0);const B=typeof d.measure_info=="string"?d.measure_info:"";let p=!1,P=!1;const y=d.resources;if(Array.isArray(y))for(const g of y){if(typeof g!="object"||g===null)continue;const U=g.types;if(Array.isArray(U))U.includes("text")&&(p=!0),U.includes("image")&&(P=!0);else{const R=g.type;(R==="text"||R==="text+image")&&(p=!0),(R==="image"||R==="text+image")&&(P=!0)}}const _={id:f,title:d.title||"未命名",type:i,path:m,author:I,year:typeof d.publication_info=="object"&&((n=d.publication_info)==null?void 0:n.year)||"",holder:typeof d.current_location=="object"&&((o=d.current_location)==null?void 0:o.name)||""};x&&x.length>0&&(_.additional_titles=x),C&&C.length>0&&(_.attached_texts=C),v&&(_.juan_count=v),B&&(_.measure_info=B);const A=typeof d.edition=="string"?d.edition:"";A&&(_.edition=A),p&&(_.has_text=!0),P&&(_.has_image=!0);const ht=l==="collections"?0:O(f);s[l][ht][f]=_}catch{}}for(const[i,a]of Object.entries(s))for(const[c,l]of Object.entries(a)){const h=this.shardPath(e,i,Number(c)),u=h.substring(0,h.lastIndexOf("/"));await this.fs.mkdir(u),await this.fs.writeFile(h,JSON.stringify(l,null,2))}}getAssetDir(t){const e=this.getRootById(t),s=k(t),o=$(s).type,i=t.padEnd(3,"_").substring(0,3),[a,c,l]=[i[0],i[1],i[2]],h=M[o];return b(e,h,a,c,l,t)}async initAssetDir(t){const e=this.getAssetDir(t);return await this.fs.mkdir(e),e}async hasAssetDir(t){const e=this.getAssetDir(t);return this.fs.exists(e)}shardPath(t,e,s){return e==="collections"?b(t,"index","collections.json"):b(t,"index",e,`${s.toString(16)}.json`)}async loadShard(t,e,s){const n=O(s),o=this.shardPath(t,e,n);try{if(!await this.fs.exists(o))return{};const i=await this.fs.readFile(o);return JSON.parse(i)}catch{return{}}}async saveShard(t,e,s,n){const o=O(s),i=this.shardPath(t,e,o),a=i.substring(0,i.lastIndexOf("/"));await this.fs.mkdir(a),await this.fs.writeFile(i,JSON.stringify(n,null,2))}async loadAllShards(t,e){const s={};if(e==="collections"){const n=this.shardPath(t,e,0);try{if(await this.fs.exists(n)){const o=await this.fs.readFile(n);Object.assign(s,JSON.parse(o))}}catch{}return s}for(let n=0;n<L;n++){const o=this.shardPath(t,e,n);try{if(await this.fs.exists(o)){const i=await this.fs.readFile(o);Object.assign(s,JSON.parse(i))}}catch{}}return s}}function J(r,t){const e=t.toLowerCase();let s=0;const n=r.title.toLowerCase();n===e?s=200:n.startsWith(e)?s=150:n.includes(e)&&(s=100);const o=[...r.additional_titles||[],...r.attached_texts||[]];for(const l of o){const h=l.toLowerCase();h===e?s=Math.max(s,120):h.startsWith(e)?s=Math.max(s,90):h.includes(e)&&(s=Math.max(s,60))}let i=0;if(r.author){const l=r.author.toLowerCase();l===e?i=80:l.includes(e)&&(i=50)}let a=0;r.dynasty&&r.dynasty.toLowerCase().includes(e)&&(a=30);let c=s;return c===0&&(c=Math.max(i,a)),c===0?0:(c+=Math.max(0,20-n.length),r.type==="work"?c=Math.round(c*1.05):r.type==="collection"&&(c=Math.round(c*1.02)),r.has_text&&(c+=3),r.has_image&&(c+=2),c)}function Pt(r,t){return t===null?void 0:t}function W(r,t){const e=r.map(s=>({entry:s,score:J(s,t)})).filter(s=>s.score>0);return e.sort((s,n)=>n.score!==s.score?n.score-s.score:s.entry.title.length-n.entry.title.length),e.map(s=>s.entry)}function V(r,t,e,s){const n=r.map(o=>{const i=J(o,t);let a=0;const c=s[o.id];if(c&&e){const l={...o,title:c.t??o.title,author:c.a??o.author,additional_titles:c.at??o.additional_titles,attached_texts:c.axt??o.attached_texts};a=J(l,e)}return{entry:o,score:Math.max(i,a)}}).filter(o=>o.score>0);return n.sort((o,i)=>i.score!==o.score?i.score-o.score:o.entry.title.length-i.entry.title.length),n.map(o=>o.entry)}function jt(r){const t=r.volumes??[];let e,s;if(t.length===0)e=[];else if(typeof t[0]=="number")e=t;else{s=[],e=[];for(const o of t){const i=o.volume;e.push(i);const a={};for(const[c,l]of Object.entries(o))c==="volume"||c==="status"||c==="file"||typeof l=="string"&&(c.includes("url")||c.includes("id"))&&(a[c]=l);s.push({volume:i,status:o.status,urls:Object.keys(a).length>0?a:void 0,file:o.file})}}const n={title:r.title??"",book_id:r.book_id??null,work_id:r.work_id??null,volumes:e,section:r.section,sub_items:r.sub_items,edition:r.edition,expected_volumes:r.expected_volumes,found_volumes:r.found_volumes,missing_volumes:r.missing_vols??r.missing_volumes};s&&(n.volume_details=s);for(const o of Object.keys(n))n[o]===void 0&&delete n[o];return n}function Tt(r){const t={total_books:r.total_books??0};return r.processed_volumes!=null&&(t.processed_volumes=r.processed_volumes),r.matched_works!=null&&(t.matched_works=r.matched_works),r.unmatched_works!=null&&(t.unmatched_works=r.unmatched_works),r.total_found_volumes!=null&&(t.total_found_volumes=r.total_found_volumes),t}function z(r){var n;const t=r,e=(t.books??[]).map(jt),s={collection_id:t.collection_id??"",title:t.title??"",total_volumes:t.total_volumes??0,stats:Tt(t.stats??{}),books:e};return t.source&&(s.source=t.source),t.resource_id&&(s.resource_id=t.resource_id),t.resource_name&&(s.resource_name=t.resource_name),(n=t.sections)!=null&&n.length&&(s.sections=t.sections),t.volume_index&&Object.keys(t.volume_index).length>0&&(s.volume_index=t.volume_index),s}const Bt="https://raw.githubusercontent.com",Lt=["https://fastly.jsdelivr.net/gh","https://cdn.jsdelivr.net/gh"],Ft=5e3;class Rt{constructor(t){this.cache=null,this.cacheLoading=null,this.pathMap=new Map,this.searchSCache=null,this.t2sConverter=null,this.config={org:t.org,repos:t.repos,baseUrl:t.baseUrl??Bt,cdnUrls:t.cdnUrls??Lt,timeout:t.timeout??Ft}}async ensureLoaded(){if(this.cache)return this.cache;if(this.cacheLoading)return this.cacheLoading;this.cacheLoading=(async()=>{const t=[];for(const s of[!0,!1])try{const n=s?this.config.repos.draft:this.config.repos.official,o=await this.fetchIndex(n),i=this.parseIndexResponse(o,s);t.push(...i)}catch(n){console.warn(`Failed to fetch ${s?"draft":"official"} index:`,n)}const e=new Map;for(const s of t)e.set(s.id,s);return this.cache=Array.from(e.values()),this.cache})();try{return await this.cacheLoading}finally{this.cache||(this.cacheLoading=null)}}async fetchFileWithFallback(t,e){const s=`${this.config.baseUrl}/${this.config.org}/${t}/main/${encodeURI(e)}`;try{return await this.fetchJson(s)}catch{}for(const n of this.config.cdnUrls){const o=`${n}/${this.config.org}/${t}@main/${encodeURI(e)}`;try{return await this.fetchJson(o)}catch{continue}}throw new Error(`Failed to fetch ${e} for ${t} from all sources`)}async probeIndex(t){const e="index/collections.json",s=`${this.config.baseUrl}/${this.config.org}/${t}/main/${encodeURI(e)}`;try{if((await fetch(s,{method:"HEAD",signal:AbortSignal.timeout(this.config.timeout)})).ok)return!0}catch{}for(const i of this.config.cdnUrls){const a=`${i}/${this.config.org}/${t}@main/${encodeURI(e)}`;try{if((await fetch(a,{method:"HEAD",signal:AbortSignal.timeout(this.config.timeout)})).ok)return!0}catch{continue}}const n="index/works/0.json",o=`${this.config.baseUrl}/${this.config.org}/${t}/main/${encodeURI(n)}`;try{if((await fetch(o,{method:"HEAD",signal:AbortSignal.timeout(this.config.timeout)})).ok)return!0}catch{}for(const i of this.config.cdnUrls){const a=`${i}/${this.config.org}/${t}@main/${encodeURI(n)}`;try{if((await fetch(a,{method:"HEAD",signal:AbortSignal.timeout(this.config.timeout)})).ok)return!0}catch{continue}}return!1}async fetchIndex(t){if(!await this.probeIndex(t))return{books:{},collections:{},works:{}};const s={books:{},collections:{},works:{}};try{const o=await this.fetchFileWithFallback(t,"index/collections.json");s.collections=o}catch{}const n=[];for(const o of["books","works"])for(let i=0;i<L;i++){const a=`index/${o}/${i.toString(16)}.json`;n.push(this.fetchFileWithFallback(t,a).then(c=>{Object.assign(s[o],c)}).catch(()=>{}))}return await Promise.all(n),s}async fetchJson(t){const e=await fetch(t,{cache:"no-store",signal:AbortSignal.timeout(this.config.timeout)});if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);return e.json()}parseIndexResponse(t,e){var o,i;const s=[],n=[["books","book"],["collections","collection"],["works","work"],["entities","entity"]];for(const[a,c]of n){const l=t[a];if(l)for(const h of Object.values(l)){const u=h,d=c==="entity"?u.primary_name||h.title||h.name||h.id:h.title||h.name||h.id;s.push({id:h.id,title:d,type:c,isDraft:e,author:h.author,dynasty:h.dynasty,role:h.role,path:h.path,additional_titles:(o=u.additional_titles)==null?void 0:o.map(f=>typeof f=="string"?f:f==null?void 0:f.book_title).filter(Boolean),attached_texts:(i=u.attached_texts)==null?void 0:i.map(f=>typeof f=="string"?f:f==null?void 0:f.book_title).filter(Boolean),edition:u.edition,juan_count:u.juan_count,has_text:u.has_text,has_image:u.has_image,has_collated:u.has_collated,subtype:u.subtype,primary_name:u.primary_name,birth_year:u.birth_year,death_year:u.death_year,cbdb_id:u.cbdb_id}),this.pathMap.set(h.id,{path:h.path,isDraft:e})}}return s}async ensureSearchSBuilt(){var n,o;if(this.searchSCache)return{searchS:this.searchSCache,converter:this.t2sConverter||null};if(this.t2sConverter===!1)return{searchS:{},converter:null};try{const i=await import("opencc-js");this.t2sConverter=i.Converter({from:"tw",to:"cn"})}catch{return this.t2sConverter=!1,this.searchSCache={},{searchS:{},converter:null}}const t=await this.ensureLoaded(),e=this.t2sConverter,s={};for(const i of t){const a={},c=e(i.title);if(c!==i.title&&(a.t=c),i.author){const l=e(i.author);l!==i.author&&(a.a=l)}if((n=i.additional_titles)!=null&&n.length){const l=i.additional_titles.map(e);l.some((h,u)=>h!==i.additional_titles[u])&&(a.at=l)}if((o=i.attached_texts)!=null&&o.length){const l=i.attached_texts.map(e);l.some((h,u)=>h!==i.attached_texts[u])&&(a.axt=l)}Object.keys(a).length>0&&(s[i.id]=a)}return this.searchSCache=s,{searchS:s,converter:e}}async loadEntries(t,e){let n=(await this.ensureLoaded()).filter(u=>u.type===t);const o=e.sortBy||"title",i=e.sortOrder||"asc";n.sort((u,d)=>{const f=String(u[o]??""),m=String(d[o]??""),I=f.localeCompare(m,"zh");return i==="asc"?I:-I});const a=e.page||1,c=e.pageSize||50,l=(a-1)*c;return{entries:n.slice(l,l+c),total:n.length,page:a,pageSize:c}}async search(t,e,s){const n=await this.ensureLoaded(),{searchS:o,converter:i}=await this.ensureSearchSBuilt(),a=n.filter(m=>m.type===e),c=i?i(t):void 0,h=Object.keys(o).length>0?V(a,t,c,o):W(a,t),u=s.page||1,d=s.pageSize||50,f=(u-1)*d;return{entries:h.slice(f,f+d),total:h.length,page:u,pageSize:d}}async searchAll(t,e=5){const s=await this.ensureLoaded(),{searchS:n,converter:o}=await this.ensureSearchSBuilt(),i=["work","book","collection"],a=o?o(t):void 0,c=Object.keys(n).length>0,l=i.map(h=>{const u=s.filter(d=>d.type===h);return c?V(u,t,a,n):W(u,t)});return{works:l[0].slice(0,e),books:l[1].slice(0,e),collections:l[2].slice(0,e),totalWorks:l[0].length,totalBooks:l[1].length,totalCollections:l[2].length}}async getItem(t){const e=await this.ensureLoaded(),s=this.pathMap.get(t);let n=null;if(s){const o=s.isDraft?this.config.repos.draft:this.config.repos.official;if(n=await this.fetchItemByPath(o,s.path),n){const i=e.find(a=>a.id===t);i!=null&&i.has_collated&&(n.has_collated=!0)}}else n=await this.findItemById(t);return n&&n.type==="entity"&&!n.title&&n.primary_name&&(n.title=n.primary_name),n}async getEntry(t){const s=(await this.ensureLoaded()).find(o=>o.id===t);if(s)return s;const n=await this.findItemById(t);return n?this.buildEntryFromItem(t,n):null}async getAllEntries(){return this.ensureLoaded()}async saveItem(){throw new Error("GithubStorage 为只读模式,不支持保存")}async deleteItem(){throw new Error("GithubStorage 为只读模式,不支持删除")}async generateId(){throw new Error("GithubStorage 为只读模式,不支持生成 ID")}async fetchItemByPath(t,e){let s=null;const n=`${this.config.baseUrl}/${this.config.org}/${t}/main/${encodeURI(e)}`;try{s=await this.fetchJson(n)}catch{}if(!s)for(const o of this.config.cdnUrls){const i=`${o}/${this.config.org}/${t}@main/${encodeURI(e)}`;try{s=await this.fetchJson(i);break}catch{continue}}return s}async findItemById(t){const e={book:"Book",collection:"Collection",work:"Work",entity:"Entity"};let s,n;try{const h=$(k(t));s=h.type,n=h.status}catch{return null}const o=n==="draft"?this.config.repos.draft:this.config.repos.official,i=e[s],a=t.padEnd(3,"_").substring(0,3),c=`${i}/${a[0]}/${a[1]}/${a[2]}`,l=`https://api.github.com/repos/${this.config.org}/${o}/contents/${c}`;try{const h=await fetch(l,{signal:AbortSignal.timeout(this.config.timeout)});if(!h.ok)return null;const d=(await h.json()).find(m=>m.name.startsWith(`${t}-`)&&m.name.endsWith(".json"));if(!d)return null;const f=await this.fetchItemByPath(o,d.path);return f&&this.pathMap.set(t,{path:d.path,isDraft:n==="draft"}),f}catch{return null}}buildEntryFromItem(t,e){const s=(e.type||"").toLowerCase(),n=s==="work"?"work":s==="collection"?"collection":"book";let o;const i=e.authors;if(Array.isArray(i)&&i.length>0){const l=i[0];o=typeof l=="object"&&l!==null?l.name||"":String(l)}let a=!0;try{a=$(k(t)).status==="draft"}catch{}const c=this.pathMap.get(t);return{id:t,title:e.title||e.书名||t,type:n,isDraft:a,author:o,dynasty:e.dynasty,role:e.role,path:(c==null?void 0:c.path)||"",juan_count:e.n_juan,has_collated:e.has_collated}}async fetchFile(t,e){const s=`${this.config.baseUrl}/${this.config.org}/${t}/main/${encodeURI(e)}`;try{return await this.fetchJson(s)}catch{}for(const n of this.config.cdnUrls){const o=`${n}/${this.config.org}/${t}@main/${encodeURI(e)}`;try{return await this.fetchJson(o)}catch{continue}}return null}async resolveItemPath(t){await this.ensureLoaded();let e=this.pathMap.get(t);if(!e&&(await this.findItemById(t),e=this.pathMap.get(t),!e))return null;const s=e.isDraft?this.config.repos.draft:this.config.repos.official,n=e.path.substring(0,e.path.lastIndexOf("/"));return{repo:s,dir:n}}async getCollectionCatalogs(t){const e=await this.resolveItemPath(t);if(!e)return null;const s=await this.getItem(t);if(!s)return null;const n=s.resources||[];if(n.length===0)return null;const o=[];for(const i of n){const a=`${e.dir}/${t}/${i.id}/volume_book_mapping.json`,c=await this.fetchFile(e.repo,a);c&&o.push({resource_id:i.id,short_name:i.short_name,data:z(c)})}return o.length>0?o:null}async getCollectionCatalog(t){var s;const e=await this.getCollectionCatalogs(t);return((s=e==null?void 0:e[0])==null?void 0:s.data)??null}async getCollatedEditionIndex(t){const e=await this.resolveItemPath(t);if(!e)return null;const s=`${e.dir}/${t}/collated_edition/collated_edition_index.json`;return this.fetchFile(e.repo,s)}async getCollatedJuan(t,e){if(e.includes("..")||!e.endsWith(".json"))return null;const s=await this.resolveItemPath(t);if(!s)return null;const n=`${s.dir}/${t}/collated_edition/${e}`;return this.fetchFile(s.repo,n)}async getLineageGraph(t){const e=await this.resolveItemPath(t);if(!e)return null;const s=`${e.dir}/${t}/lineage_graph.json`;return this.fetchFile(e.repo,s)}async getCatalogProgress(){return this.fetchFile(this.config.repos.draft,"resource-catalog.json")}async getCollectionProgress(){return this.fetchFile(this.config.repos.draft,"resource-collection.json")}async getResourceProgress(){return this.fetchFile(this.config.repos.draft,"resource.json")}async getSiteProgress(){return this.fetchFile(this.config.repos.draft,"resource-site.json")}async getRecommended(){return this.fetchFile(this.config.repos.draft,"recommended.json")}clearCache(){this.cache=null,this.pathMap.clear(),this.searchSCache=null,this.t2sConverter=null}}const Q=255;class Y{constructor(t){if(this.lastTimestamp=-1,this.lastStatus=null,this.sequence=0,t<0||t>2047)throw new Error("Machine ID must be between 0 and 2047");this.machineId=t}nextId(t,e){let s=this._getCurrentTimestamp(t);if(s<this.lastTimestamp&&t===this.lastStatus)throw new Error("Clock moved backwards. Refusing to generate ID.");s===this.lastTimestamp&&t===this.lastStatus?(this.sequence=this.sequence+1&Q,this.sequence===0&&(s=this._tilNextUnit(this.lastTimestamp,t))):this.sequence=0,this.lastTimestamp=s,this.lastStatus=t;const n=N(t,e,BigInt(s),this.machineId,this.sequence);return F(n)}nextIdRaw(t,e){let s=this._getCurrentTimestamp(t);if(s<this.lastTimestamp&&t===this.lastStatus)throw new Error("Clock moved backwards. Refusing to generate ID.");return s===this.lastTimestamp&&t===this.lastStatus?(this.sequence=this.sequence+1&Q,this.sequence===0&&(s=this._tilNextUnit(this.lastTimestamp,t))):this.sequence=0,this.lastTimestamp=s,this.lastStatus=t,N(t,e,BigInt(s),this.machineId,this.sequence)}_getCurrentTimestamp(t){const e=Date.now();return t==="draft"?e:Math.floor(e/1e3)}_tilNextUnit(t,e){let s=this._getCurrentTimestamp(e);for(;s<=t;)s=this._getCurrentTimestamp(e);return s}}class Dt{constructor(t){this.recentEntities=[],this.storage=new q(t.fs,t.workspaceRoot),this.idGen=new Y(t.machineId??0),this.fs=t.fs,this.workspaceRoot=t.workspaceRoot}async loadEntries(t,e){const s=await this.storage.loadEntries(t);return this.paginate(s,e)}async search(t,e,s){const n=await this.storage.searchEntries(t,e);return this.paginate(n,s)}async searchAll(t,e){return this.storage.searchAll(t,e)}async getItem(t){const e=await this.storage.getItem(t);return e&&e.type==="entity"&&!e.title&&e.primary_name&&(e.title=e.primary_name),e}async saveItem(t){const e=t.id;if(!e)throw new Error("metadata.id is required");const s=t.type||this.extractTypeFromId(e),n=await this.storage.saveItem(s,e,t);return{id:e,path:n}}async deleteItem(t){if(!await this.storage.deleteItem(t))throw new Error(`Item not found: ${t}`)}async generateId(t,e){return this.idGen.nextId(e,t)}async getEntry(t){const e=await this.storage.getItem(t);if(!e)return null;const s=e.type||this.extractTypeFromId(t);return{id:t,title:e.title||"未命名",type:s,author:this.extractAuthor(e),dynasty:this.extractYear(e)}}async getAllEntries(){const t=["book","collection","work"],e=[];for(const s of t){const n=await this.storage.loadEntries(s);e.push(...n)}return e}async getRelations(t){const e=await this.storage.getItem(t);if(!e)return null;const s={},n=e.type||this.extractTypeFromId(t);if(n==="book"){if(e.work_id){const o=await this.resolveEntity(e.work_id);o&&(s.belongsToWork={...o,type:"work"})}if(e.contained_in&&Array.isArray(e.contained_in)&&e.contained_in.length>0){const o=e.contained_in[0],i=typeof o=="string"?o:o.id;if(i){const a=await this.resolveEntity(i);a&&(s.belongsToCollection={...a,type:"collection"})}}}else if(n==="collection"){if(e.books&&Array.isArray(e.books)){const o=await Promise.all(e.books.map(i=>this.resolveEntity(i)));s.containedBooks=o.filter(i=>i!==null).map(i=>({...i,type:"book"}))}}else if(n==="work"){if(e.parent_work&&typeof e.parent_work=="object"){const o=e.parent_work;s.parentWork={id:o.id,title:o.title,type:"work"}}if(e.books&&Array.isArray(e.books)){const o=await Promise.all(e.books.map(i=>this.resolveEntity(i)));s.containedBooks=o.filter(i=>i!==null).map(i=>({...i,type:"book"}))}}return s}async linkEntity(t,e,s){const n=await this.storage.getItem(t);if(!n)throw new Error(`Source not found: ${t}`);const o=n.type||this.extractTypeFromId(t);switch(e){case"belongsToWork":case"work_id":n.work_id=s;break;case"belongsToCollection":case"contained_in":Array.isArray(n.contained_in)||(n.contained_in=[]);{const i=n.contained_in;i.some(c=>(typeof c=="string"?c:c.id)===s)||i.push({id:s})}break;case"parentWork":case"parent_work":{const i=await this.storage.getItem(s);n.parent_work={id:s,title:(i==null?void 0:i.title)||""};break}case"containedBooks":case"books":Array.isArray(n.books)||(n.books=[]),n.books.includes(s)||n.books.push(s);break;default:throw new Error(`Unknown relation field: ${e}`)}await this.storage.saveItem(o,t,n)}async unlinkEntity(t,e){const s=await this.storage.getItem(t);if(!s)throw new Error(`Source not found: ${t}`);const n=s.type||this.extractTypeFromId(t);switch(e){case"belongsToWork":case"work_id":delete s.work_id;break;case"belongsToCollection":case"contained_in":s.contained_in=[];break;case"parentWork":case"parent_work":delete s.parent_work;break;case"containedBooks":case"books":s.books=[];break;default:throw new Error(`Unknown relation field: ${e}`)}await this.storage.saveItem(n,t,s)}async createAndLink(t,e,s){const n=this.idGen.nextId("draft",s.type),o={id:n,type:s.type,title:s.title,...s.inheritData||{}};return await this.storage.saveItem(s.type,n,o),await this.linkEntity(t,e,n),{id:n}}async searchEntities(t,e){const s=e&&e!=="all"?[e]:["book","collection","work"],n=[];for(const o of s){const i=await this.storage.searchEntries(t,o);for(const a of i)n.push({id:a.id,title:a.title,type:a.type,author:a.author,dynasty:a.dynasty})}return n}async getRecentEntities(){return this.recentEntities}async addRecentEntity(t){this.recentEntities=[t,...this.recentEntities.filter(e=>e.id!==t.id)].slice(0,20)}async getCollectionCatalog(t){const e=await this.storage.findFileById(t);if(!e)return null;const n=e.substring(0,e.lastIndexOf("/"))+"/volume_book_mapping.json";try{const o=await this.storage.loadMetadata(n);return Object.keys(o).length===0?null:o}catch{return null}}async getRecommended(){const t=this.workspaceRoot+"/book-index-draft/recommended.json";try{const e=await this.fs.readFile(t);return JSON.parse(e)}catch{return null}}getAssetDir(t){return this.storage.getAssetDir(t)}async initAssetDir(t){return this.storage.initAssetDir(t)}async hasAssetDir(t){return this.storage.hasAssetDir(t)}getBookIndexStorage(){return this.storage}async rebuildIndex(t){await this.storage.rebuildIndex(t)}extractTypeFromId(t){try{const e=k(t);return $(e).type}catch{return"book"}}extractAuthor(t){const e=t.authors;if(Array.isArray(e)&&e.length>0){const s=e[0];return typeof s=="object"&&s!==null?s.name||"":String(s)}}extractYear(t){const e=t.publication_info;if(typeof e=="object"&&e!==null)return e.year||void 0}async resolveEntity(t){const e=await this.storage.getItem(t);return e?{id:t,title:e.title||"未命名"}:null}paginate(t,e){const s=e.page??1,n=e.pageSize??50;if(e.sortBy){const c=e.sortBy,l=e.sortOrder==="desc"?-1:1;t.sort((h,u)=>{const d=h[c]??"",f=u[c]??"";return d<f?-l:d>f?l:0})}const o=t.length,i=(s-1)*n;return{entries:t.slice(i,i+n),total:o,page:s,pageSize:n}}}const Ut="/data",Mt=1e4;class Ot{constructor(t={}){this.chunkCache=new Map,this.chunkLoading=new Map,this.manifest=null,this.manifestLoading=null,this.tiyaoCache=new Map,this.tiyaoLoading=new Map,this.metaCache=null,this.metaLoading=null,this.version=void 0,this.versionPromise=null,this.basePath=t.basePath??Ut,this.timeout=t.timeout??Mt}async ensureVersion(){return this.version!==void 0?this.version:this.versionPromise?this.versionPromise:(this.versionPromise=(async()=>{try{const t=await fetch(`${this.basePath}/version.json`,{cache:"no-cache",signal:AbortSignal.timeout(this.timeout)});if(!t.ok)return null;const e=await t.json(),s=e==null?void 0:e.commitId;return!s||s==="unknown"?null:s.slice(0,12)}catch{return null}})(),this.version=await this.versionPromise,this.version)}async fetchJson(t){const e=await this.ensureVersion(),s=e?`${t}${t.includes("?")?"&":"?"}v=${e}`:t,n=await fetch(s,{cache:"no-cache",signal:AbortSignal.timeout(this.timeout)});if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);return n.json()}async loadManifest(){if(this.manifest)return this.manifest;if(this.manifestLoading)return this.manifestLoading;this.manifestLoading=(async()=>{try{this.manifest=await this.fetchJson(`${this.basePath}/chunks/_manifest.json`)}catch{this.manifest=[]}return this.manifest})();try{return await this.manifestLoading}finally{this.manifest||(this.manifestLoading=null)}}async resolvePrefix(t){const e=await this.loadManifest();let s=0,n=e.length-1,o=null;for(;s<=n;){const i=s+n>>1;e[i]<=t?(o=e[i],s=i+1):n=i-1}if(o&&t.startsWith(o))return o;for(const i of e)if(t.startsWith(i))return i;return null}async loadChunk(t){if(this.chunkCache.has(t))return this.chunkCache.get(t);const e=this.chunkLoading.get(t);if(e)return e;const s=(async()=>{try{const n=await this.fetchJson(`${this.basePath}/chunks/${t}.json`);return this.chunkCache.set(t,n),n}catch{return this.chunkCache.set(t,{}),{}}})();this.chunkLoading.set(t,s);try{return await s}finally{this.chunkLoading.delete(t)}}async loadChunkForId(t){const e=await this.resolvePrefix(t);return e?this.loadChunk(e):{}}async loadTiyaoGroup(t,e){const s=`${t}-${e}`;if(this.tiyaoCache.has(s))return this.tiyaoCache.get(s);const n=this.tiyaoLoading.get(s);if(n)return n;const o=a=>String(a).padStart(3,"0"),i=(async()=>{const a=await this.fetchJson(`${this.basePath}/tiyao/juan-${o(t)}-${o(e)}.json`);return this.tiyaoCache.set(s,a),a})();this.tiyaoLoading.set(s,i);try{return await i}finally{this.tiyaoLoading.delete(s)}}async getCounts(){if(this.metaCache)return this.metaCache;if(this.metaLoading){const t=await this.metaLoading;if(t)return t;throw new Error("BundleStorage: meta.json 加载失败")}this.metaLoading=(async()=>{const t=await this.fetchJson(`${this.basePath}/meta.json`);if(!t||typeof t.works!="number")throw new Error("BundleStorage: meta.json 格式无效");return this.metaCache=t,t})();try{const t=await this.metaLoading;if(!t)throw new Error("BundleStorage: meta.json 加载失败");return t}finally{this.metaLoading=null}}async getResourceCounts(){return(await this.getCounts()).resourceCounts??{hasText:0,hasImage:0}}async getSubtypeStats(){return(await this.getCounts()).subtypeStats??{}}async loadEntries(t,e){throw new Error("BundleStorage.loadEntries 已废弃:请使用 worker 搜索")}async search(t,e,s){throw new Error("BundleStorage.search 已废弃:请使用 worker 搜索")}async searchAll(t,e=5){throw new Error("BundleStorage.searchAll 已废弃:请使用 worker 搜索")}async getItem(t){try{const s=(await this.loadChunkForId(t))[t]||null;return s&&s.type==="entity"&&!s.title&&s.primary_name&&(s.title=s.primary_name),s}catch{return null}}async getEntry(t){var e,s;try{const o=(await this.loadChunkForId(t))[t];if(!o)return null;const i=lt(t),a=i==="entity"?o.primary_name||o.title||o.name||t:o.title||o.name||t;return{id:t,title:a,type:i,isDraft:!0,author:o.author,dynasty:o.dynasty,role:o.role,additional_titles:(e=o.additional_titles)==null?void 0:e.map(c=>typeof c=="string"?c:c==null?void 0:c.book_title).filter(Boolean),attached_texts:(s=o.attached_texts)==null?void 0:s.map(c=>typeof c=="string"?c:c==null?void 0:c.book_title).filter(Boolean),edition:o.edition,juan_count:o.juan_count,has_text:o.has_text,has_image:o.has_image,has_collated:o.has_collated,subtype:o.subtype,primary_name:o.primary_name,birth_year:o.birth_year,death_year:o.death_year,cbdb_id:o.cbdb_id}}catch{return null}}async getAllEntries(){throw new Error("BundleStorage.getAllEntries 已废弃:请使用 getEntry / worker 搜索")}async saveItem(){throw new Error("BundleStorage 为只读模式,不支持保存")}async deleteItem(){throw new Error("BundleStorage 为只读模式,不支持删除")}async generateId(){throw new Error("BundleStorage 为只读模式,不支持生成 ID")}async getCollectionCatalogs(t){const e=await this.getItem(t);if(!e)return null;const s=e.resources||[];if(s.length===0)return null;const n=[];for(const o of s)try{const i=await this.fetchJson(`${this.basePath}/items/${t}/${o.id}/volume_book_mapping.json`);i&&n.push({resource_id:o.id,short_name:o.short_name,data:z(i)})}catch{}return n.length>0?n:null}async getCollectionCatalog(t){var s;const e=await this.getCollectionCatalogs(t);return((s=e==null?void 0:e[0])==null?void 0:s.data)??null}async getCollatedEditionIndex(t){try{return await this.fetchJson(`${this.basePath}/items/${t}/collated_edition/collated_edition_index.json`)}catch{return null}}async getCollatedJuan(t,e){if(e.includes("..")||!e.endsWith(".json"))return null;try{return await this.fetchJson(`${this.basePath}/items/${t}/collated_edition/${e}`)}catch{return null}}async getCollatedJuanText(t,e){if(e.includes("..")||!e.endsWith(".json"))return null;const s=e.replace(/\.json$/,".md"),n=await this.ensureVersion(),o=`${this.basePath}/items/${t}/collated_edition/text/${s}`,i=n?`${o}?v=${n}`:o;try{const a=await fetch(i,{cache:"no-cache"});return a.ok?await a.text():null}catch{return null}}async getLineageGraph(t){try{return await this.fetchJson(`${this.basePath}/items/${t}/lineage_graph.json`)}catch{return null}}async getCatalogProgress(){try{return await this.fetchJson(`${this.basePath}/resource-catalog.json`)}catch{return null}}async getCollectionProgress(){try{return await this.fetchJson(`${this.basePath}/resource-collection.json`)}catch{return null}}async getResourceProgress(){try{return await this.fetchJson(`${this.basePath}/resource.json`)}catch{return null}}async getSiteProgress(){try{return await this.fetchJson(`${this.basePath}/resource-site.json`)}catch{return null}}async getRecommended(){try{return await this.fetchJson(`${this.basePath}/recommended.json`)}catch{return null}}clearCache(){this.chunkCache.clear(),this.chunkLoading.clear(),this.manifest=null,this.manifestLoading=null,this.tiyaoCache.clear(),this.tiyaoLoading.clear(),this.metaCache=null,this.metaLoading=null,this.version=void 0,this.versionPromise=null}}class T extends Error{constructor(t){super(t),this.name="BookIndexError"}}class Nt extends T{constructor(t){super(t),this.name="StorageError"}}class Jt extends T{constructor(t){super(t),this.name="IdGenerationError"}}class Wt extends T{constructor(t){super(t),this.name="ConfigError"}}class Gt extends T{constructor(t){super(t),this.name="MigrationError"}}class Ht{constructor(t,e,s=1){this.storage=new q(t,e),this.idGen=new Y(s)}generateId(t="book",e="draft"){return this.idGen.nextIdRaw(e,t)}encodeId(t){return F(t)}decodeId(t){return k(t)}async saveItem(t,e,s="draft"){let n=t.id||t.ID;if(n)try{const i=j(n);e||(e=i.type)}catch{throw new T(`Invalid ID format: ${n}`)}else e||(e=t.type||"book"),n=this.idGen.nextId(s,e),t.id=n;return await this.storage.saveItem(e,n,t)}async getItem(t){return this.storage.getItem(t)}async findItemPath(t){return this.storage.findFileById(t)}async updateField(t,e,s){const n=await this.storage.findFileById(t);if(!n)return!1;try{const o=await this.storage.loadMetadata(n),i={基本信息:null,介绍:"description",资源:"resources",收藏历史:"history",其他版本:"related_books"},a=e in i?i[e]:e;if(a===null)return!1;if(a==="description"&&typeof s=="string"){const l=o[a]||{};o[a]={text:s,sources:l.sources||[]}}else o[a]=s;const c=j(t);return await this.storage.saveItem(c.type,t,o),!0}catch{return!1}}async deleteItem(t){return this.storage.deleteItem(t)}async rebuildIndices(){await this.storage.rebuildIndex("official"),await this.storage.rebuildIndex("draft")}getAssetDir(t){return this.storage.getAssetDir(t)}async initAssetDir(t){return this.storage.initAssetDir(t)}async hasAssetDir(t){return this.storage.hasAssetDir(t)}getStorage(){return this.storage}}const qt={wikisource:"wikisource",shidianguji:"shidianguji",archive:"archive",ctext:"ctext",nlc:"nlc","read.nlc":"nlc","db.sido":"sido","guji.artx":"guji-artx","digital.library":"digital-library"},Z=new Set(["text","image","text+image","physical"]),tt=new Set(["text","image","physical"]),zt=new Set(["catalog","search"]),Yt=new Set(["com","org","net","cn","edu","gov","io","jp","tw","hk"]);function Kt(r){if(!r)return"";try{const e=new URL(r).hostname;for(const[n,o]of Object.entries(qt))if(e.includes(n))return o;const s=e.split(".");if(s.length>=2){const n=s.filter(o=>!Yt.has(o)&&o.length>2);return n.length>0?n[n.length-1]:s[s.length-2]}return e}catch{return""}}function Xt(r){const t=[];r.name||t.push("name is required");let e=!1;if(r.types!==void 0)if(!Array.isArray(r.types)||r.types.length===0)t.push("types must be a non-empty array when present");else{for(const s of r.types)tt.has(s)||t.push(`invalid types atom '${s}', must be one of ${[...tt].join(", ")}`);e=r.types.length===1&&r.types[0]==="physical"}else r.type!==void 0?(Z.has(r.type)||t.push(`invalid type '${r.type}', must be one of ${[...Z].join(", ")}`),e=r.type==="physical"):t.push("either type or types is required");return r.root_type&&!zt.has(r.root_type)&&t.push(`invalid root_type '${r.root_type}'`),!e&&!r.url&&t.push("url is required for non-physical resources"),t}const w=class w{constructor(t,e=""){if(this._type=null,this.title=e,typeof t=="string"){t.startsWith(w.PREFIX)&&(t=t.slice(w.PREFIX.length)),this.idStr=t;try{this.idInt=k(t)}catch{this.idInt=0n}}else this.idInt=t,this.idStr=F(t);if(this.idInt>0n)try{const s=j(this.idStr);this._type=s.type}catch{}}get type(){return this._type}getIcon(){return this._type===null?"":this._type==="book"?"📖 ":this._type==="collection"?"📚 ":this._type==="work"?"📜 ":""}render(t=!1){return`[${t?this.getIcon():""}${this.title}](${w.PREFIX}${this.idStr})`}static parseFromLink(t){const e=t.match(/\[(.*?)\]\((.*?)\)/);if(e){const s=e[1],n=e[2];if(n.startsWith(w.PREFIX)){const o=n.slice(w.PREFIX.length);return new w(o,s)}}return null}static isBidLink(t){return t.startsWith(w.PREFIX)}};w.PROTOCOL="bid:\\\\",w.PREFIX="bid:\\\\";let G=w;exports.BidLink=G;exports.BookIndexError=T;exports.BookIndexManager=Ht;exports.BookIndexStorage=q;exports.BundleStorage=Ot;exports.ConfigError=Wt;exports.GithubStorage=Rt;exports.IdGenerationError=Jt;exports.IdGenerator=Y;exports.LocalStorage=Dt;exports.MigrationError=Gt;exports.StorageError=Nt;exports.base36Decode=H;exports.base36Encode=F;exports.base58Decode=ct;exports.buildId=N;exports.decodeId=vt;exports.decodeIdString=j;exports.encodeId=St;exports.extractIdFromUrl=Kt;exports.extractStatus=$t;exports.extractType=lt;exports.normalizeCatalog=z;exports.parseId=$;exports.smartDecode=k;exports.validateResource=Xt;