database reading
This commit is contained in:
1 parent
68c24dcec5
commit
fc8455e353
418 files changed
+674410
-1561
No files matched your search
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))u(s);new MutationObserver(s=>{for(const l of s)if(l.type==="childList")for(const h of l.addedNodes)h.tagName==="LINK"&&h.rel==="modulepreload"&&u(h)}).observe(document,{childList:!0,subtree:!0});function i(s){const l={};return s.integrity&&(l.integrity=s.integrity),s.referrerPolicy&&(l.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?l.credentials="include":s.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function u(s){if(s.ep)return;s.ep=!0;const l=i(s);fetch(s.href,l)}})();const vt="japanese_app.db";async function wt(){return navigator.storage.getDirectory()}async function gn(r){const i=await(await wt()).getFileHandle(vt,{create:!0}),u=i.createSyncAccessHandle;if(typeof u=="function")try{const l=await u.call(i);try{l.truncate(0),l.write(r,{at:0}),l.flush()}finally{l.close()}return}catch{}const s=await i.createWritable();try{await s.truncate(0);const l=new Uint8Array(r.length);l.set(r),await s.write(l)}finally{await s.close()}}async function vn(){const r=await wt();let n;try{n=await r.getFileHandle(vt)}catch{throw new Error("Локальная база не найдена — сначала скачай сохранение")}const i=await n.getFile();if(i.size===0)throw new Error("Локальная база пуста");return new Uint8Array(await i.arrayBuffer())}async function Se(){try{return(await(await(await wt()).getFileHandle(vt)).getFile()).size>0}catch{return!1}}var wn=ArrayBuffer,L=Uint8Array,Ye=Uint16Array,yn=Int16Array,De=Int32Array,ut=function(r,n,i){if(L.prototype.slice)return L.prototype.slice.call(r,n,i);(n==null||n<0)&&(n=0),(i==null||i>r.length)&&(i=r.length);var u=new L(i-n);return u.set(r.subarray(n,i)),u},$e=function(r,n,i,u){if(L.prototype.fill)return L.prototype.fill.call(r,n,i,u);for((i==null||i<0)&&(i=0),(u==null||u>r.length)&&(u=r.length);i<u;++i)r[i]=n;return r},mn=function(r,n,i,u){if(L.prototype.copyWithin)return L.prototype.copyWithin.call(r,n,i,u);for((i==null||i<0)&&(i=0),(u==null||u>r.length)&&(u=r.length);i<u;)r[n++]=r[i++]},bn=["invalid zstd data","window size too large (>2046MB)","invalid block type","FSE accuracy too high","match distance too far back","unexpected EOF"],I=function(r,n,i){var u=new Error(n||bn[r]);if(u.code=r,Error.captureStackTrace&&Error.captureStackTrace(u,I),!i)throw u;return u},Yt=function(r,n,i){for(var u=0,s=0;u<i;++u)s|=r[n++]<<(u<<3);return s},Un=function(r,n){return(r[n]|r[n+1]<<8|r[n+2]<<16|r[n+3]<<24)>>>0},Cn=function(r,n){var i=r[0]|r[1]<<8|r[2]<<16;if(i==3126568&&r[3]==253){var u=r[4],s=u>>5&1,l=u>>2&1,h=u&3,g=u>>6;u&8&&I(0);var y=6-s,E=h==3?4:h,U=Yt(r,y,E);y+=E;var k=g?1<<g:s,F=Yt(r,y,k)+(g==1&&256),m=F;if(!s){var x=1<<10+(r[5]>>3);m=x+(x>>3)*(r[5]&7)}m>2145386496&&I(1);var w=new L((n==1?F||m:n?0:m)+12);return w[0]=1,w[4]=4,w[8]=8,{b:y+k,y:0,l:0,d:U,w:n&&n!=1?n:w.subarray(12),e:m,o:new De(w.buffer,0,3),u:F,c:l,m:Math.min(131072,m)}}else if((i>>4|r[3]<<20)==25481893)return Un(r,4)+8;I(0)},me=function(r){for(var n=0;1<<n<=r;++n);return n-1},Te=function(r,n,i){var u=(n<<3)+4,s=(r[n]&15)+5;s>i&&I(3);for(var l=1<<s,h=l,g=-1,y=-1,E=-1,U=l,k=new wn(512+(l<<2)),F=new yn(k,0,256),m=new Ye(k,0,256),x=new Ye(k,512,l),w=512+(l<<1),$=new L(k,w,l),D=new L(k,w+l);g<255&&h>0;){var X=me(h+1),J=u>>3,se=(1<<X+1)-1,R=(r[J]|r[J+1]<<8|r[J+2]<<16)>>(u&7)&se,b=(1<<X)-1,V=se-h-1,Z=R&b;if(Z<V?(u+=X,R=Z):(u+=X+1,R>b&&(R-=V)),F[++g]=--R,R==-1?(h+=R,$[--U]=g):h-=R,!R)do{var te=u>>3;y=(r[te]|r[te+1]<<8)>>(u&7)&3,u+=2,g+=y}while(y==3)}(g>255||h)&&I(0);for(var Y=0,re=(l>>1)+(l>>3)+3,ne=l-1,G=0;G<=g;++G){var N=F[G];if(N<1){m[G]=-N;continue}for(E=0;E<N;++E){$[Y]=G;do Y=Y+re≠while(Y>=U)}}for(Y&&I(0),E=0;E<l;++E){var C=m[$[E]]++,S=D[E]=s-me(C);x[E]=(C<<S)-l}return[u+7>>3,{b:s,s:$,n:D,t:x}]},Mn=function(r,n){var i=0,u=-1,s=new L(292),l=r[n],h=s.subarray(0,256),g=s.subarray(256,268),y=new Ye(s.buffer,268);if(l<128){var E=Te(r,n+1,6),U=E[0],k=E[1];n+=l;var F=U<<3,m=r[n];m||I(0);for(var x=0,w=0,$=k.b,D=$,X=(++n<<3)-8+me(m);X-=$,!(X<F);){var J=X>>3;if(x+=(r[J]|r[J+1]<<8)>>(X&7)&(1<<$)-1,h[++u]=k.s[x],X-=D,X<F)break;J=X>>3,w+=(r[J]|r[J+1]<<8)>>(X&7)&(1<<D)-1,h[++u]=k.s[w],$=k.n[x],x=k.t[x],D=k.n[w],w=k.t[w]}++u>255&&I(0)}else{for(u=l-127;i<u;i+=2){var se=r[++n];h[i]=se>>4,h[i+1]=se&15}++n}var R=0;for(i=0;i<u;++i){var b=h[i];b>11&&I(0),R+=b&&1<<b-1}var V=me(R)+1,Z=1<<V,te=Z-R;for(te&te-1&&I(0),h[u++]=me(te)+1,i=0;i<u;++i){var b=h[i];++g[h[i]=b&&V+1-b]}var Y=new L(Z<<1),re=Y.subarray(0,Z),ne=Y.subarray(Z);for(y[V]=0,i=V;i>0;--i){var G=y[i];$e(ne,i,G,y[i-1]=G+g[i]*(1<<V-i))}for(y[0]!=Z&&I(0),i=0;i<u;++i){var N=h[i];if(N){var C=y[N];$e(re,i,C,y[N]=C+(1<<V-N))}}return[n,{n:ne,b:V,s:re}]},Qn=Te(new L([81,16,99,140,49,198,24,99,12,33,196,24,99,102,102,134,70,146,4]),0,6)[1],En=Te(new L([33,20,196,24,99,140,33,132,16,66,8,33,132,16,66,8,33,68,68,68,68,68,68,Line truncated
|
||||
<main class="card" aria-labelledby="home-title">
|
||||
<h2 id="home-title" class="card-title">🏠 Главная</h2>
|
||||
<p class="card-hint">Локальное сохранение и повторения — всё работает офлайн.</p>
|
||||
<div id="home-db-status" class="status-bar" role="status">Проверка локальной базы…</div>
|
||||
<div class="btn-row">
|
||||
<button id="btn-go-sync" class="btn btn-primary" type="button">📡 К синхронизации</button>
|
||||
</div>
|
||||
<div id="home-actions" class="btn-row" hidden>
|
||||
<button id="btn-start" class="btn" type="button">▶ Начать повторение</button>
|
||||
</div>
|
||||
</main>
|
||||
`;const i=r.querySelector("#home-db-status"),u=r.querySelector("#btn-go-sync"),s=r.querySelector("#home-actions"),l=r.querySelector("#btn-start");u.addEventListener("click",()=>n.goSync()),l.addEventListener("click",()=>{i.textContent="Экран повторений — следующий шаг."}),(async()=>{if(!await Se()){i.textContent="Локальной базы нет. Скачай сохранение на странице «Синхронизация».",s.hidden=!0;return}try{const g=await vn(),y=gt(g);i.textContent=y?`Локальная база на месте: ${(g.length/1024).toFixed(1)} КБ, сигнатура SQLite OK.`:"Файл в OPFS есть, но это не похоже на SQLite — перекачай сохранение.",s.hidden=!y}catch(g){i.textContent=`Не удалось прочитать базу: ${g instanceof Error?g.message:String(g)}`,s.hidden=!0}})()}const Bt=24,Yn=/^[A-Za-z0-9]{24}$/,At="japlearn_sync_token";function Dt(r){return r.trim().replace(/\s+/g,"")}function ye(r){return Yn.test(r)}function Bn(){try{return localStorage.getItem(At)??""}catch{return""}}function dt(r){try{localStorage.setItem(At,r)}catch{}}const er="https://learning.micialware.ru";function Dn(r,n=er){return`${n.replace(/\/+$/,"")}/download/${encodeURIComponent(r)}`}async function Gn(r,n,i=er){const u=Dn(r,i);let s;try{s=await fetch(u,{method:"GET",...n})}catch(h){throw new Error(`Нет связи с сервером: ${h instanceof Error?h.message:String(h)}`)}if(!s.ok)throw s.status===404?new Error("Сохранение не найдено (404). Проверь токен."):new Error(`Сервер ответил ${s.status} ${s.statusText}`);const l=await s.arrayBuffer();if(l.byteLength===0)throw new Error("Сервер вернул пустой файл");return new Uint8Array(l)}function jn(r){r.innerHTML=`
|
||||
<main class="card" aria-labelledby="sync-title">
|
||||
<h2 id="sync-title" class="card-title">📡 Синхронизация</h2>
|
||||
<p class="card-hint">Токен сохранения — 24 символа: латинские буквы и цифры.</p>
|
||||
|
||||
<label class="field-label" for="token-input">Токен сохранения</label>
|
||||
<input
|
||||
id="token-input"
|
||||
class="token-input"
|
||||
type="text"
|
||||
inputmode="latin"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
maxlength="24"
|
||||
minlength="24"
|
||||
placeholder="Например: aB3dEf7hJk9mN2pQr5tUvWx1z"
|
||||
aria-describedby="token-error token-counter"
|
||||
/>
|
||||
<div class="token-meta">
|
||||
<span id="token-error" class="token-error" role="alert"></span>
|
||||
<span id="token-counter" class="token-counter" aria-live="polite">0 / 24</span>
|
||||
</div>
|
||||
|
||||
<div class="btn-row">
|
||||
<button id="btn-download" class="btn" type="button" disabled>📥 Скачать</button>
|
||||
<button id="btn-upload" class="btn btn-primary" type="button" disabled>📤 Выгрузить</button>
|
||||
</div>
|
||||
|
||||
<p id="status" class="status-bar" role="status">Введите токен из 24 символов</p>
|
||||
</main>
|
||||
`;const n=r.querySelector("#token-input"),i=r.querySelector("#token-error"),u=r.querySelector("#token-counter"),s=r.querySelector("#btn-download"),l=r.querySelector("#btn-upload"),h=r.querySelector("#status");function g(m){h.textContent=m,console.log(`[JapLearn] ${m}`)}function y(){return Dt(n.value)}function E(m){s.disabled=m||!ye(y()),l.disabled=!0,n.disabled=m,m||Se().then(x=>{!n.disabled&&ye(y())&&(l.disabled=!x)})}function U(){const m=y(),x=m.length;u.textContent=`${x} / ${Bt}`;const w=ye(m),$=x===0;n.classList.toggle("invalid",!$&&!w),n.setAttribute("aria-invalid",String(!$&&!w)),$?i.textContent="":x!==Bt?i.textContent=`Нужно 24 символа, сейчас ${x}`:i.textContent=w?"":"Только латинские буквы и цифры",s.disabled=!w,w?Se().then(D=>{n.disabled||(l.disabled=!D)}):l.disabled=!0}async function k(){const m=y();if(ye(m)){dt(m),E(!0),g("Скачивание сохранения...");try{const x=await Gn(m);g(`Скачано ${(x.length/1024).toFixed(1)} КБ, распаковка...`);const w=await Zn(x);g(`Распаковано ${(w.length/1024).toFixed(1)} КБ, сохранение в OPFS...`),await gn(w),g(`Готово: база ${(w.length/1024).toFixed(1)} КБ сохранена локально (OPFS).`)}catch(x){g(`Ошибка скачивания: ${x instanceof Error?x.message:String(x)}`)}finally{E(!1),U()}}}n.addEventListener("input",()=>{U()}),n.addEventListener("change",()=>{const m=y();n.value=m,ye(m)&&(dt(m),g("Токен сохранён локально. Можно скачивать или выгружать.")),U()}),s.addEventListener("click",()=>{k()}),l.addEventListener("click",()=>{const m=y();ye(m)&&(dt(m),g("Выгрузка — следующий шаг (POST /upload/stream/{id})."))});const F=Bn();F&&(n.value=F),U(),ye(Dt(F))&&Se().then(m=>{g(m?"Токен восстановлен, локальная база на месте.":"Токен восстановлен. Нажми «Скачать», чтобы получить базу."),U()})}const Wn={home:"Главная",sync:"Синхронизация"};function _e(r){const n=document.getElementById(r);if(!n)throw new Error(`[JapLearn] Не найден элемент #${r}`);return n}const tr=_e("burger-btn"),Be=_e("drawer"),rr=_e("drawer-overlay"),An=_e("page-title"),Gt=_e("page");let yt=!1;function qe(r){yt=r,Be.classList.toggle("open",r),rr.hidden=!r,tr.setAttribute("aria-expanded",String(r)),Be.setAttribute("aria-hidden",String(!r)),document.body.classList.toggle("drawer-open",r)}function ea(r){Be.querySelectorAll(".drawer-btn").forEach(n=>{n.classList.toggle("active",n.dataset.page===r)})}function mt(r){An.textContent=Wn[r],ea(r),r==="home"?Sn(Gt,{goSync:()=>mt("sync")}):jn(Gt)}tr.addEventListener("click",()=>{qe(!yt)});rr.addEventListener("click",()=>{qe(!1)});document.addEventListener("keydown",r=>{r.key==="Escape"&&yt&&qe(!1)});Be.querySelectorAll(".drawer-btn").forEach(r=>{r.addEventListener("click",()=>{const n=r.dataset.page;(n==="home"||n==="sync")&&mt(n),qe(!1)})});qe(!1);mt("home");console.log("[JapLearn] crossOriginIsolated =",window.crossOriginIsolated);
|
||||
Reference in new issue
Block a user