From b5b4254d205c724c7dc0d917d1db18be61d68e65 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Tue, 15 Sep 2026 16:34:13 +0300 Subject: [PATCH] history display and repetition bug fix --- ...{index-DkBF5-AQ.css => index-B1EUgva9.css} | 2 +- dist/assets/index-Ban37OPN.js | 111 --------------- dist/assets/index-Bk0XDs7i.js | 126 +++++++++++++++++ dist/index.html | 4 +- src/db-types.ts | 104 +++++++++++++- src/deck-page.ts | 8 +- src/history-page.ts | 131 ++++++++++++++++++ src/home-page.ts | 2 +- src/main.ts | 13 +- src/review-page.ts | 83 ++++++++--- src/style.css | 37 +++++ 11 files changed, 480 insertions(+), 141 deletions(-) rename dist/assets/{index-DkBF5-AQ.css => index-B1EUgva9.css} (80%) delete mode 100644 dist/assets/index-Ban37OPN.js create mode 100644 dist/assets/index-Bk0XDs7i.js create mode 100644 src/history-page.ts diff --git a/dist/assets/index-DkBF5-AQ.css b/dist/assets/index-B1EUgva9.css similarity index 80% rename from dist/assets/index-DkBF5-AQ.css rename to dist/assets/index-B1EUgva9.css index a10cdae..4d82558 100644 --- a/dist/assets/index-DkBF5-AQ.css +++ b/dist/assets/index-B1EUgva9.css @@ -1 +1 @@ -:root{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;line-height:1.5;color:#1e1b2e;background:#f4f1fa}*{box-sizing:border-box}body{margin:0;min-height:100dvh;display:flex;justify-content:center;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)}#app{width:100%;max-width:480px;padding:24px 16px 32px;display:flex;flex-direction:column;gap:16px}.app-header h1{margin:0;font-size:1.75rem;letter-spacing:.02em}.app-subtitle{margin:4px 0 0;color:#6d6390}.top-bar{display:flex;align-items:center;gap:12px}.burger-btn{width:44px;height:44px;border:2px solid #d9d2f0;border-radius:12px;background:#fff;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;touch-action:manipulation;flex:none}.burger-btn span{display:block;width:20px;height:2px;border-radius:2px;background:#4c446b}.burger-btn[aria-expanded=true]{border-color:#7c3aed}.top-bar-title h1{margin:0;font-size:1.4rem;line-height:1.2}.top-bar-title p{margin:0;font-size:.9rem;color:#6d6390}.top-bar-spacer{flex:1}.app-content{display:flex;flex-direction:column;gap:16px}.drawer-overlay{position:fixed;inset:0;background:#1e1b2e73;z-index:40}.drawer-overlay[hidden]{display:none}.drawer{position:fixed;top:0;left:0;bottom:0;width:min(300px,85vw);background:#fff;z-index:50;transform:translate(-105%);transition:transform .22s ease;display:flex;flex-direction:column;gap:8px;padding:20px 14px;box-shadow:8px 0 32px #1e1b2e2e}.drawer.open{transform:translate(0)}.drawer-header{padding:4px 8px 14px;border-bottom:1px solid #eee9fb;margin-bottom:8px}.drawer-logo{font-size:1.3rem;font-weight:800;color:#7c3aed}.drawer-subtitle{font-size:.9rem;color:#6d6390}.drawer-btn{border:none;background:transparent;text-align:left;font-size:1.05rem;font-weight:600;color:#1e1b2e;padding:13px 12px;border-radius:12px;cursor:pointer;touch-action:manipulation}.drawer-btn.active{background:#f1eafe;color:#6d28d9}.drawer-btn:active{background:#e9defc}body.drawer-open{overflow:hidden}.card{background:#fff;border-radius:16px;padding:20px 16px;box-shadow:0 8px 24px #7c3aed1f;display:flex;flex-direction:column;gap:12px}.card-title{margin:0;font-size:1.25rem}.card-hint{margin:0;color:#6d6390;font-size:.95rem}.field-label{font-weight:600;font-size:.95rem}.token-input{width:100%;padding:14px 12px;font-size:1.05rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.04em;border:2px solid #d9d2f0;border-radius:12px;outline:none}.token-input:focus{border-color:#7c3aed}.token-input.invalid{border-color:#dc2626;background:#fef2f2}.token-meta{display:flex;justify-content:space-between;align-items:baseline;gap:8px;min-height:1.4em}.token-error{color:#dc2626;font-size:.9rem}.token-counter{color:#6d6390;font-variant-numeric:tabular-nums;white-space:nowrap}.btn-row{display:flex;gap:12px}.btn{flex:1;padding:14px 12px;font-size:1rem;font-weight:600;border-radius:12px;border:2px solid #7c3aed;background:#fff;color:#7c3aed;cursor:pointer;touch-action:manipulation}.btn-primary{background:#7c3aed;color:#fff}.btn:disabled{opacity:.4;cursor:not-allowed}.btn:not(:disabled):active{transform:scale(.98)}.status-bar{margin:0;padding:10px 12px;border-radius:10px;background:#f4f1fa;color:#4c446b;font-size:.95rem;word-break:break-word}.status-bar[hidden]{display:none}.db-banner{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:10px;background:#fff7e6;color:#7a5b00;font-size:.85rem}.db-banner[hidden]{display:none}.db-banner-text{flex:1;min-width:0}.db-banner-link{flex:none;border:none;background:transparent;padding:4px;font:inherit;font-weight:700;color:#7c3aed;cursor:pointer;touch-action:manipulation}.db-banner-link[hidden]{display:none}.words-toolbar{display:flex;gap:8px}.words-search{flex:1;min-width:0;padding:12px;font-size:1rem;border:2px solid #d9d2f0;border-radius:12px;outline:none;background:#fff;color:inherit}.words-search:focus{border-color:#7c3aed}.words-group{flex:none;max-width:42%;padding:12px 8px;font-size:.95rem;font-weight:600;color:#4c446b;border:2px solid #d9d2f0;border-radius:12px;background:#fff;outline:none}.words-group:focus{border-color:#7c3aed}.words-list{display:flex;flex-direction:column}.word-row-wrap{border-bottom:1px solid #eee9fb}.word-row-wrap:last-child{border-bottom:none}.word-row{display:flex;align-items:baseline;gap:8px;width:100%;padding:11px 4px;border:none;background:transparent;font:inherit;color:inherit;text-align:left;cursor:pointer;touch-action:manipulation;border-radius:8px}.word-row:disabled{cursor:default}.word-row:not(:disabled):active{background:#f4f1fa}.word-row.open{background:#f4f1fa}.word-main{flex:1;min-width:0;overflow-wrap:break-word}.word-key{font-weight:700}.word-sep{color:#6d6390}.word-value{color:#4c446b}.word-tag{flex:none;max-width:38%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.8rem;color:#6d6390;text-align:right}.word-dot{flex:none;width:6px;height:6px;border-radius:50%;background:#7c3aed;opacity:.55;align-self:center}.word-extra[hidden]{display:none}.word-extra-list{margin:0 0 10px;padding:10px 12px;background:#f4f1fa;border-radius:10px;display:flex;flex-direction:column;gap:8px}.word-extra-row dt{font-size:.8rem;font-weight:700;color:#6d6390}.word-extra-row dd{margin:2px 0 0;word-break:break-word}.empty-val{opacity:.5}button.word-row-wrap{border:none;border-bottom:1px solid #eee9fb}button.word-row-wrap:last-child{border-bottom:none}button.word-row-wrap:not(:disabled):hover{background:#f4f1fa}.btn-ghost{align-self:flex-start;flex:none;border:2px solid #d9d2f0;background:#fff;color:#4c446b;box-shadow:none}.btn-ghost:active{background:#f1eafe;border-color:#7c3aed}.deck-form{display:flex;flex-direction:column;gap:8px;margin-top:12px}.deck-form .field-label{margin-top:4px}.deck-form .btn-row{margin-top:8px}.deck-form .card-hint:empty{display:none}.deck-sub{margin:16px 0 8px;font-size:1rem;color:#4c446b}.deck-row{display:flex;align-items:stretch;gap:4px}.deck-name{flex:1;min-width:0}.deck-play{flex:none;align-self:center;width:44px;height:44px;border:2px solid #d9d2f0;border-radius:12px;background:#f1eafe;color:#6d28d9;font-size:1rem;cursor:pointer;touch-action:manipulation}.deck-play:active{background:#e9defc;border-color:#7c3aed}#review-body{display:flex;flex-direction:column;gap:12px;margin-top:12px}.review-card{display:block;width:100%;border:2px solid #d9d2f0;border-radius:14px;background:#fbfaff;padding:20px 16px;font:inherit;color:inherit;text-align:center;cursor:pointer;touch-action:manipulation}.review-card:active{border-color:#7c3aed}.review-side{display:flex;flex-direction:column;gap:8px}.review-line{margin:0;overflow-wrap:break-word}.review-front .review-line:first-child{font-size:1.5rem;font-weight:800}.review-hr{border:none;border-top:1px solid #eee9fb;margin:14px 0}.review-tap{margin:12px 0 0;font-size:.85rem;color:#6d6390}.review-grades{display:grid;grid-template-columns:1fr 1fr;gap:8px}.review-grade{margin:0}.grade-fail{background:#fee2e2;color:#991b1b;box-shadow:none}.grade-hard{background:#ffedd5;color:#9a3412;box-shadow:none}.grade-good{background:#dcfce7;color:#166534;box-shadow:none}.grade-easy{background:#dbeafe;color:#1e40af;box-shadow:none}.review-grade:active{filter:brightness(.95)} +:root{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;line-height:1.5;color:#1e1b2e;background:#f4f1fa}*{box-sizing:border-box}body{margin:0;min-height:100dvh;display:flex;justify-content:center;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)}#app{width:100%;max-width:480px;padding:24px 16px 32px;display:flex;flex-direction:column;gap:16px}.app-header h1{margin:0;font-size:1.75rem;letter-spacing:.02em}.app-subtitle{margin:4px 0 0;color:#6d6390}.top-bar{display:flex;align-items:center;gap:12px}.burger-btn{width:44px;height:44px;border:2px solid #d9d2f0;border-radius:12px;background:#fff;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;touch-action:manipulation;flex:none}.burger-btn span{display:block;width:20px;height:2px;border-radius:2px;background:#4c446b}.burger-btn[aria-expanded=true]{border-color:#7c3aed}.top-bar-title h1{margin:0;font-size:1.4rem;line-height:1.2}.top-bar-title p{margin:0;font-size:.9rem;color:#6d6390}.top-bar-spacer{flex:1}.app-content{display:flex;flex-direction:column;gap:16px}.drawer-overlay{position:fixed;inset:0;background:#1e1b2e73;z-index:40}.drawer-overlay[hidden]{display:none}.drawer{position:fixed;top:0;left:0;bottom:0;width:min(300px,85vw);background:#fff;z-index:50;transform:translate(-105%);transition:transform .22s ease;display:flex;flex-direction:column;gap:8px;padding:20px 14px;box-shadow:8px 0 32px #1e1b2e2e}.drawer.open{transform:translate(0)}.drawer-header{padding:4px 8px 14px;border-bottom:1px solid #eee9fb;margin-bottom:8px}.drawer-logo{font-size:1.3rem;font-weight:800;color:#7c3aed}.drawer-subtitle{font-size:.9rem;color:#6d6390}.drawer-btn{border:none;background:transparent;text-align:left;font-size:1.05rem;font-weight:600;color:#1e1b2e;padding:13px 12px;border-radius:12px;cursor:pointer;touch-action:manipulation}.drawer-btn.active{background:#f1eafe;color:#6d28d9}.drawer-btn:active{background:#e9defc}body.drawer-open{overflow:hidden}.card{background:#fff;border-radius:16px;padding:20px 16px;box-shadow:0 8px 24px #7c3aed1f;display:flex;flex-direction:column;gap:12px}.card-title{margin:0;font-size:1.25rem}.card-hint{margin:0;color:#6d6390;font-size:.95rem}.field-label{font-weight:600;font-size:.95rem}.token-input{width:100%;padding:14px 12px;font-size:1.05rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.04em;border:2px solid #d9d2f0;border-radius:12px;outline:none}.token-input:focus{border-color:#7c3aed}.token-input.invalid{border-color:#dc2626;background:#fef2f2}.token-meta{display:flex;justify-content:space-between;align-items:baseline;gap:8px;min-height:1.4em}.token-error{color:#dc2626;font-size:.9rem}.token-counter{color:#6d6390;font-variant-numeric:tabular-nums;white-space:nowrap}.btn-row{display:flex;gap:12px}.btn{flex:1;padding:14px 12px;font-size:1rem;font-weight:600;border-radius:12px;border:2px solid #7c3aed;background:#fff;color:#7c3aed;cursor:pointer;touch-action:manipulation}.btn-primary{background:#7c3aed;color:#fff}.btn:disabled{opacity:.4;cursor:not-allowed}.btn:not(:disabled):active{transform:scale(.98)}.status-bar{margin:0;padding:10px 12px;border-radius:10px;background:#f4f1fa;color:#4c446b;font-size:.95rem;word-break:break-word}.status-bar[hidden]{display:none}.db-banner{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:10px;background:#fff7e6;color:#7a5b00;font-size:.85rem}.db-banner[hidden]{display:none}.db-banner-text{flex:1;min-width:0}.db-banner-link{flex:none;border:none;background:transparent;padding:4px;font:inherit;font-weight:700;color:#7c3aed;cursor:pointer;touch-action:manipulation}.db-banner-link[hidden]{display:none}.words-toolbar{display:flex;gap:8px}.words-search{flex:1;min-width:0;padding:12px;font-size:1rem;border:2px solid #d9d2f0;border-radius:12px;outline:none;background:#fff;color:inherit}.words-search:focus{border-color:#7c3aed}.words-group{flex:none;max-width:42%;padding:12px 8px;font-size:.95rem;font-weight:600;color:#4c446b;border:2px solid #d9d2f0;border-radius:12px;background:#fff;outline:none}.words-group:focus{border-color:#7c3aed}.words-list{display:flex;flex-direction:column}.word-row-wrap{border-bottom:1px solid #eee9fb}.word-row-wrap:last-child{border-bottom:none}.word-row{display:flex;align-items:baseline;gap:8px;width:100%;padding:11px 4px;border:none;background:transparent;font:inherit;color:inherit;text-align:left;cursor:pointer;touch-action:manipulation;border-radius:8px}.word-row:disabled{cursor:default}.word-row:not(:disabled):active{background:#f4f1fa}.word-row.open{background:#f4f1fa}.word-main{flex:1;min-width:0;overflow-wrap:break-word}.word-key{font-weight:700}.word-sep{color:#6d6390}.word-value{color:#4c446b}.word-tag{flex:none;max-width:38%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.8rem;color:#6d6390;text-align:right}.word-dot{flex:none;width:6px;height:6px;border-radius:50%;background:#7c3aed;opacity:.55;align-self:center}.word-extra[hidden]{display:none}.word-extra-list{margin:0 0 10px;padding:10px 12px;background:#f4f1fa;border-radius:10px;display:flex;flex-direction:column;gap:8px}.word-extra-row dt{font-size:.8rem;font-weight:700;color:#6d6390}.word-extra-row dd{margin:2px 0 0;word-break:break-word}.empty-val{opacity:.5}button.word-row-wrap{border:none;border-bottom:1px solid #eee9fb}button.word-row-wrap:last-child{border-bottom:none}button.word-row-wrap:not(:disabled):hover{background:#f4f1fa}.btn-ghost{align-self:flex-start;flex:none;border:2px solid #d9d2f0;background:#fff;color:#4c446b;box-shadow:none}.btn-ghost:active{background:#f1eafe;border-color:#7c3aed}.deck-form{display:flex;flex-direction:column;gap:8px;margin-top:12px}.deck-form .field-label{margin-top:4px}.deck-form .btn-row{margin-top:8px}.deck-form .card-hint:empty{display:none}.deck-sub{margin:16px 0 8px;font-size:1rem;color:#4c446b}.deck-row{display:flex;align-items:stretch;gap:4px}.deck-name{flex:1;min-width:0}.deck-play{flex:none;align-self:center;width:44px;height:44px;border:2px solid #d9d2f0;border-radius:12px;background:#f1eafe;color:#6d28d9;font-size:1rem;cursor:pointer;touch-action:manipulation}.deck-play:active{background:#e9defc;border-color:#7c3aed}.hist-row-wrap{padding:9px 4px;border-bottom:1px solid #eee9fb}.hist-row-wrap:last-child{border-bottom:none}.hist-main{overflow-wrap:break-word}.hist-meta{display:flex;align-items:baseline;gap:8px;margin-top:4px}.hist-grade{flex:none;padding:2px 10px;border-radius:999px;font-size:.8rem;font-weight:700}.hist-grade.grade-fail,.hist-grade.grade-hard,.hist-grade.grade-good,.hist-grade.grade-easy{border:none}#review-body{display:flex;flex-direction:column;gap:12px;margin-top:12px}.review-card{display:block;width:100%;border:2px solid #d9d2f0;border-radius:14px;background:#fbfaff;padding:20px 16px;font:inherit;color:inherit;text-align:center;cursor:pointer;touch-action:manipulation}.review-card:active{border-color:#7c3aed}.review-side{display:flex;flex-direction:column;gap:8px}.review-line{margin:0;overflow-wrap:break-word}.review-front .review-line:first-child{font-size:1.5rem;font-weight:800}.review-hr{border:none;border-top:1px solid #eee9fb;margin:14px 0}.review-tap{margin:12px 0 0;font-size:.85rem;color:#6d6390}.review-grades{display:grid;grid-template-columns:1fr 1fr;gap:8px}.review-grade{margin:0}.grade-fail{background:#fee2e2;color:#991b1b;box-shadow:none}.grade-hard{background:#ffedd5;color:#9a3412;box-shadow:none}.grade-good{background:#dcfce7;color:#166534;box-shadow:none}.grade-easy{background:#dbeafe;color:#1e40af;box-shadow:none}.review-grade:active{filter:brightness(.95)} diff --git a/dist/assets/index-Ban37OPN.js b/dist/assets/index-Ban37OPN.js deleted file mode 100644 index 6794e9b..0000000 --- a/dist/assets/index-Ban37OPN.js +++ /dev/null @@ -1,111 +0,0 @@ -(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))o(s);new MutationObserver(s=>{for(const c of s)if(c.type==="childList")for(const u of c.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&o(u)}).observe(document,{childList:!0,subtree:!0});function a(s){const c={};return s.integrity&&(c.integrity=s.integrity),s.referrerPolicy&&(c.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?c.credentials="include":s.crossOrigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function o(s){if(s.ep)return;s.ep=!0;const c=a(s);fetch(s.href,c)}})();const Se=new Worker(new URL("/assets/db-worker-DFFhVJkA.js",import.meta.url),{type:"module"});let dr=0,je=null;const We=new Map;function _n(){return je||(je=new Promise((e,n)=>{Se.onmessage=o=>{const s=o.data;if(s.type==="ready")return;const c=We.get(s.id);c&&(We.delete(s.id),s.type=="success"?c.resolve(s.data):c.reject(new Error(s.error??s.message??"Неизвестная ошибка Worker")))},Se.onerror=o=>{console.error("[SQLite Worker] Ошибка:",o),n(new Error("Ошибка Worker"))};const a=++dr;We.set(a,{resolve:e,reject:n}),Se.postMessage({type:"start",id:a})}),je)}function fr(e,n){return new Promise((a,o)=>{if(!Se){o(new Error("Worker не создан"));return}const s=++dr;We.set(s,{resolve:a,reject:o}),Se.postMessage({type:e,id:s,...n})})}async function pe(e,n){return await fr("query",{sql:e,params:n??[]})}async function Pn(e,n){await fr("command",{sql:e,params:n??[]})}function Sn(e){if(e==null)return[];const n=String(e).trim();return n?n.split(/[,\s;]+/).map(a=>a.trim()).filter(a=>a.length>0):[]}function Rn(e){const n=new Map;if(e==null)return n;const a=String(e);if(!a)return n;const o=a.split("");for(let s=0;s+10?`WHERE ${n.join(" AND ")}`:"",params:a}}async function In(e={}){const{clause:n,params:a}=hr(e),s=(await pe(`SELECT COUNT(*) as cnt FROM words ${n}`,a))[0]?.cnt;return typeof s=="number"?s:Number(s??0)}async function er(e={}){const{clause:n,params:a}=hr(e),o=Math.max(1,Math.min(e.limit??50,500)),s=Math.max(0,e.offset??0);return(await pe(`SELECT id, key, value, tags, more, group_id FROM words ${n} ORDER BY id LIMIT ? OFFSET ?`,[...a,o,s])).map(pr)}async function Jn(){return(await pe("SELECT id, name FROM word_groups ORDER BY id")).map(n=>({id:Number(n.id),name:String(n.name??"")}))}async function On(){return(await pe("SELECT id, name, forward, backward, filter FROM decks ORDER BY id")).map(n=>({id:Number(n.id),name:String(n.name??""),forward:String(n.forward??""),backward:String(n.backward??""),filter:String(n.filter??"")}))}async function Kn(e,n){return await Pn("UPDATE decks SET forward = ?, backward = ?, filter = ? WHERE id = ?",[n.forward,n.backward,n.filter,e]),Et(e)}async function Et(e){const a=(await pe("SELECT id, name, forward, backward, filter FROM decks WHERE id = ? LIMIT 1",[e]))[0];return a?{id:Number(a.id),name:String(a.name??""),forward:String(a.forward??""),backward:String(a.backward??""),filter:String(a.filter??"")}:null}async function Xn(){const e=await pe("SELECT deck_id, COUNT(*) as cnt FROM states WHERE deck_id IS NOT NULL GROUP BY deck_id"),n=new Map;for(const a of e){if(a.deck_id==null)continue;const o=a.cnt;n.set(Number(a.deck_id),typeof o=="number"?o:Number(o??0))}return n}async function zn(e){const a=(await pe("SELECT COUNT(DISTINCT word_id) as cnt FROM states WHERE deck_id = ?",[e]))[0]?.cnt;return typeof a=="number"?a:Number(a??0)}async function tr(e){const n=new Map,a=[...new Set(e.filter(s=>Number.isFinite(s)))];if(a.length===0)return n;const o=400;for(let s=0;s"?").join(","),g=await pe(`SELECT id, key, value, tags, more, group_id FROM words WHERE id IN (${u})`,c);for(const y of g.map(pr))n.set(y.id,y)}return n}function Dn(e,n){const a=new Map;a.set("key",e.key),a.set("value",e.value),a.set("tags",e.tagsList.join(", "));for(const o of n){const s=e.more.get(o.id)??"";if(s)for(const c of[o.identifier,o.name]){const u=c.trim().toLowerCase();!u||a.has(u)||a.set(u,s)}}return a}function rr(e,n){const a=[];for(const o of e.split(/\s+/)){const s=o.trim().toLowerCase();if(!s)continue;const c=(n.get(s)??"").trim();c&&a.push(c)}return a}async function nr(e,n){const o=(await pe("SELECT DISTINCT word_id as word_id FROM states WHERE deck_id = ?",[e])).map(s=>Number(s.word_id)).filter(s=>Number.isFinite(s));for(let s=o.length-1;s>0;s--){const c=Math.floor(Math.random()*(s+1));[o[s],o[c]]=[o[s],o[c]]}return o}async function gr(){return(await pe("SELECT id, name, identifier FROM custom_fields ORDER BY id")).map(n=>({id:Number(n.id),name:String(n.name??""),identifier:String(n.identifier??"")}))}const Ut="japanese_app.db";async function $t(){return navigator.storage.getDirectory()}async function Bn(e){e[18]=1,e[19]=1;const a=await(await $t()).getFileHandle(Ut,{create:!0}),o=a.createSyncAccessHandle;if(typeof o=="function")try{const c=await o.call(a);try{c.truncate(0),c.write(e,{at:0}),c.flush()}finally{c.close()}return}catch{}const s=await a.createWritable();try{await s.truncate(0);const c=new Uint8Array(e.length);c.set(e),await s.write(c)}finally{await s.close()}}async function Vn(){const e=await $t();let n;try{n=await e.getFileHandle(Ut)}catch{throw new Error("Локальная база не найдена — сначала скачай сохранение")}const a=await n.getFile();if(a.size===0)throw new Error("Локальная база пуста");return new Uint8Array(await a.arrayBuffer())}async function tt(){try{return(await(await(await $t()).getFileHandle(Ut)).getFile()).size>0}catch{return!1}}var Zn=ArrayBuffer,V=Uint8Array,Ae=Uint16Array,Yn=Int16Array,rt=Int32Array,gt=function(e,n,a){if(V.prototype.slice)return V.prototype.slice.call(e,n,a);(n==null||n<0)&&(n=0),(a==null||a>e.length)&&(a=e.length);var o=new V(a-n);return o.set(e.subarray(n,a)),o},Re=function(e,n,a,o){if(V.prototype.fill)return V.prototype.fill.call(e,n,a,o);for((a==null||a<0)&&(a=0),(o==null||o>e.length)&&(o=e.length);ae.length)&&(o=e.length);a2046MB)","invalid block type","FSE accuracy too high","match distance too far back","unexpected EOF"],G=function(e,n,a){var o=new Error(n||jn[e]);if(o.code=e,Error.captureStackTrace&&Error.captureStackTrace(o,G),!a)throw o;return o},ar=function(e,n,a){for(var o=0,s=0;o>>0},An=function(e,n){var a=e[0]|e[1]<<8|e[2]<<16;if(a==3126568&&e[3]==253){var o=e[4],s=o>>5&1,c=o>>2&1,u=o&3,g=o>>6;o&8&&G(0);var y=6-s,m=u==3?4:u,C=ar(e,y,m);y+=m;var N=g?1<>3);$=T+(T>>3)*(e[5]&7)}$>2145386496&&G(1);var v=new V((n==1?E||$:n?0:$)+12);return v[0]=1,v[4]=4,v[8]=8,{b:y+N,y:0,l:0,d:C,w:n&&n!=1?n:v.subarray(12),e:$,o:new rt(v.buffer,0,3),u:E,c,m:Math.min(131072,$)}}else if((a>>4|e[3]<<20)==25481893)return Wn(e,4)+8;G(0)},Ee=function(e){for(var n=0;1<a&&G(3);for(var c=1<0;){var O=Ee(u+1),Q=o>>3,L=(1<>(o&7)&L,b=(1<b&&(U-=z)),E[++g]=--U,U==-1?(u+=U,M[--C]=g):u-=U,!U)do{var ee=o>>3;y=(e[ee]|e[ee+1]<<8)>>(o&7)&3,o+=2,g+=y}while(y==3)}(g>255||u)&&G(0);for(var D=0,re=(c>>1)+(c>>3)+3,Z=c-1,j=0;j<=g;++j){var _=E[j];if(_<1){$[j]=-_;continue}for(m=0;m<_;++m){M[D]=j;do D=D+re&Z;while(D>=C)}}for(D&&G(0),m=0;m>3,{b:s,s:M,n:S,t:T}]},ea=function(e,n){var a=0,o=-1,s=new V(292),c=e[n],u=s.subarray(0,256),g=s.subarray(256,268),y=new Ae(s.buffer,268);if(c<128){var m=Ie(e,n+1,6),C=m[0],N=m[1];n+=c;var E=C<<3,$=e[n];$||G(0);for(var T=0,v=0,M=N.b,S=M,O=(++n<<3)-8+Ee($);O-=M,!(O>3;if(T+=(e[Q]|e[Q+1]<<8)>>(O&7)&(1<>3,v+=(e[Q]|e[Q+1]<<8)>>(O&7)&(1<255&&G(0)}else{for(o=c-127;a>4,u[a+1]=L&15}++n}var U=0;for(a=0;a11&&G(0),U+=b&&1<0;--a){var j=y[a];Re(Z,a,j,y[a-1]=j+g[a]*(1<g&&N>3,$=(e[E]|e[E+1]<<8|e[E+2]<<16)>>(C&7);y=(y<>2,u=c<<1,g=c+u;Pe(e.subarray(o,o+=e[0]|e[1]<<8),n.subarray(0,c),a),Pe(e.subarray(o,o+=e[2]|e[3]<<8),n.subarray(c,u),a),Pe(e.subarray(o,o+=e[4]|e[5]<<8),n.subarray(u,g),a),Pe(e.subarray(o),n.subarray(g),a)},oa=function(e,n,a){var o,s=n.b,c=e[s],u=c>>1&3;n.l=c&1;var g=c>>3|e[s+1]<<5|e[s+2]<<13,y=(s+=3)+g;if(u==1)return s>=e.length?void 0:(n.b=s+1,a?(Re(a,e[s],n.y,n.y+=g),a):Re(new V(g),e[s]));if(!(y>e.length)){if(u==0)return n.b=y,a?(a.set(e.subarray(s,y),n.y),n.y+=g,a):gt(e,s,y);if(u==2){var m=e[s],C=m&3,N=m>>2&3,E=m>>4,$=0,T=0;C<2?N&1?E|=e[++s]<<4|(N&2&&e[++s]<<12):E=m>>3:(T=N,N<2?(E|=(e[++s]&63)<<4,$=e[s]>>6|e[++s]<<2):N==2?(E|=e[++s]<<4|(e[++s]&3)<<12,$=e[s]>>2|e[++s]<<6):(E|=e[++s]<<4|(e[++s]&63)<<12,$=e[s]>>6|e[++s]<<2|e[++s]<<10)),++s;var v=a?a.subarray(n.y,n.y+n.m):new V(n.m),M=v.length-E;if(C==0)v.set(e.subarray(s,s+=E),M);else if(C==1)Re(v,e[s++],M);else{var S=n.h;if(C==2){var O=ea(e,s);$+=s-(s=O[0]),n.h=S=O[1]}else S||G(0);(T?sa:Pe)(e.subarray(s,s+=$),v.subarray(M),S)}var Q=e[s++];if(Q){Q==255?Q=(e[s++]|e[s++]<<8)+32512:Q>127&&(Q=Q-128<<8|e[s++]);var L=e[s++];L&3&&G(0);for(var U=[ra,na,ta],b=2;b>-1;--b){var z=L>>(b<<1)+2&3;if(z==1){var B=new V([0,0,e[s++]]);U[b]={s:B.subarray(2,3),n:B.subarray(0,1),t:new Ae(B.buffer,0,1),b:0}}else z==2?(o=Ie(e,s,9-(b&1)),s=o[0],U[b]=o[1]):z==3&&(n.t||G(0),U[b]=n.t[b])}var ee=n.t=U,D=ee[0],re=ee[1],Z=ee[2],j=e[y-1];j||G(0);var _=(y<<3)-8+Ee(j)-Z.b,w=_>>3,x=0,q=(e[w]|e[w+1]<<8)>>(_&7)&(1<>3;var P=(e[w]|e[w+1]<<8)>>(_&7)&(1<>3;var R=(e[w]|e[w+1]<<8)>>(_&7)&(1<>3;var ve=1<>>(_&7)&ve-1);w=(_-=bt[$e])>>3;var se=ia[$e]+((e[w]|e[w+1]<<8|e[w+2]<<16)>>(_&7)&(1<>3;var ge=aa[he]+((e[w]|e[w+1]<<8|e[w+2]<<16)>>(_&7)&(1<>3,q=Z.t[q]+((e[w]|e[w+1]<<8)>>(_&7)&(1<>3,R=D.t[R]+((e[w]|e[w+1]<<8)>>(_&7)&(1<>3,P=re.t[P]+((e[w]|e[w+1]<<8)>>(_&7)&(1<3)n.o[2]=n.o[1],n.o[1]=n.o[0],n.o[0]=ne-=3;else{var Ue=ne-(ge!=0);Ue?(ne=Ue==3?n.o[0]-1:n.o[Ue],Ue>1&&(n.o[2]=n.o[1]),n.o[1]=n.o[0],n.o[0]=ne):ne=n.o[0]}for(var b=0;bse&&(ae=se);for(var b=0;b{throw t},o,s=console.log.bind(console),c=console.error.bind(console),u,g=!1,y,m=r=>r.startsWith("file://"),C=!1;function N(){return Ye.buffer}function E(){if(!B?.buffer?.resizable){var r=N();B=new Int8Array(r),Xe=new Int16Array(r),n.HEAPU8=q=new Uint8Array(r),Te=new Uint16Array(r),ze=new Int32Array(r),R=new Uint32Array(r),Rt=new Float32Array(r),It=new Float64Array(r),Mt=new BigInt64Array(r),Tt=new BigUint64Array(r)}}function $(){C=!0,He.A()}function T(r){r=`Aborted(${r})`,c(r),g=!0,r+=". Build with -sASSERTIONS for more info.",C&&Dt();var t=new WebAssembly.RuntimeError(r);throw t}var v;let M=()=>"";function S(r){if(r==v&&u)return new Uint8Array(u);throw"both async and sync fetching of the wasm failed"}async function O(r){if(!u)try{var t=await o(r);return new Uint8Array(t)}catch{}return S(r)}async function Q(r,t){try{var i=await O(r),l=await WebAssembly.instantiate(i,t);return l}catch(d){c(`failed to asynchronously prepare wasm: ${d}`),T(d)}}async function L(r,t,i){if(!r&&!m(t))try{var l=fetch(t,{credentials:"same-origin"}),d=await WebAssembly.instantiateStreaming(l,i);return d}catch(f){c(`wasm streaming compile failed: ${f}`),c("falling back to ArrayBuffer instantiation")}return Q(t,i)}function U(){var r={a:Ln};return r}async function b(){function r(f){return He=f.exports,Fn(He),E(),He}function t(f){return r(f.instance)}var i=U();v??=M();var l=await L(u,v,i),d=t(l);return d}class z{name="ExitStatus";constructor(t){this.message=`Program terminated with exit(${t})`,this.status=t}}var B,ee=()=>jt,D=r=>{var t=r.getArg(ee(),0);return Yt(t)},re=()=>Zt(),Z=r=>Bt(r),j=r=>Vt(r),_=globalThis.TextDecoder&&new TextDecoder,w=(r,t,i,l)=>{var d=t+i;if(l)return d;for(;r[t]&&!(t>=d);)++t;return t},x=(r,t=0,i,l)=>{var d=w(r,t,i,l);if(d-t>16&&r.buffer&&_)return _.decode(r.subarray(t,d));for(var f="";t>10,56320|H&1023)}}return f},q,P=(r,t,i)=>r?x(q,r,t,i):"",R,he=r=>{var t=re(),i=j(4),l=j(4);Gt(r,i,l);var d=R[i>>2],f=R[l>>2],p=P(d);fe(d);var h;return f&&(h=P(f),fe(f)),Z(t),[p,h]},le=r=>{var t=D(r);return he(t)},$e=r=>{var t=new WebAssembly.Exception(ee(),[r],{traceStack:!0});throw t.message=le(t),t},Ke=()=>T(""),ye={},xe=r=>{for(;r.length;){var t=r.pop(),i=r.pop();i(t)}};function ve(r){return this.fromWireType(R[r>>2])}var ne={},se={},ge={};class Ue extends Error{constructor(t){super(t),this.name="InternalError"}}var ue=r=>{throw new Ue(r)},ae=(r,t,i)=>{r.forEach(h=>ge[h]=t);function l(h){var k=i(h);k.length!==r.length&&ue("Mismatched type converter count");for(var H=0;H{d[h]=se[k],++p,p===f.length&&l(d)}));f.length===0&&l(d)},nt=r=>{var t=ye[r];delete ye[r];var i=t.rawConstructor,l=t.rawDestructor,d=t.fields,f=d.map(p=>p.getterReturnType).concat(d.map(p=>p.setterArgumentType));ae([r],f,p=>{var h={};for(var[k,H]of d.entries()){let F=p[k],K=H.getter,J=H.getterContext,X=p[k+d.length],W=H.setter,ie=H.setterContext;h[H.fieldName]={read:A=>F.fromWireType(K(J,A)),write:(A,Y)=>{var me=[];W(ie,A,X.toWireType(me,Y)),xe(me)},optional:F.optional}}return[{name:t.name,fromWireType:F=>{var K={};for(var J in h)K[J]=h[J].read(F);return l(F),K},toWireType:(F,K)=>{for(var J in h)if(!(J in K)&&!h[J].optional)throw new TypeError(`Missing field: "${J}"`);var X=i();for(J in h)h[J].write(X,K[J]);return F!==null&&F.push(l,X),X},readValueFromPointer:ve,destructorFunction:l}]})},te=r=>{for(var t="";;){var i=q[r++];if(!i)return t;t+=String.fromCharCode(i)}};class Me extends Error{constructor(t){super(t),this.name="BindingError"}}var I=r=>{throw new Me(r)};function Er(r,t,i={}){var l=t.name;if(r||I(`type "${l}" must have a positive integer typeid pointer`),se.hasOwnProperty(r)){if(i.ignoreDuplicateRegistrations)return;I(`Cannot register type '${l}' twice`)}if(se[r]=t,delete ge[r],ne.hasOwnProperty(r)){var d=ne[r];delete ne[r],d.forEach(f=>f())}}function de(r,t,i={}){return Er(r,t,i)}var Xe,Te,ze,Mt,Tt,Nt=(r,t,i)=>{switch(t){case 1:return i?l=>B[l]:l=>q[l];case 2:return i?l=>Xe[l>>1]:l=>Te[l>>1];case 4:return i?l=>ze[l>>2]:l=>R[l>>2];case 8:return i?l=>Mt[l>>3]:l=>Tt[l>>3];default:throw new TypeError(`invalid integer width (${t}): ${r}`)}},Ur=(r,t,i,l,d)=>{t=te(t);let f=l===0n,p=h=>h;if(f){let h=i*8;p=k=>BigInt.asUintN(h,k),d=p(d)}de(r,{name:t,fromWireType:p,toWireType:(h,k)=>(typeof k=="number"&&(k=BigInt(k)),k),readValueFromPointer:Nt(t,i,!f),destructorFunction:null})},$r=(r,t,i,l)=>{t=te(t),de(r,{name:t,fromWireType:function(d){return!!d},toWireType:function(d,f){return f?i:l},readValueFromPointer:function(d){return this.fromWireType(q[d])},destructorFunction:null})},xr=r=>({count:r.count,deleteScheduled:r.deleteScheduled,preservePointerOnDelete:r.preservePointerOnDelete,ptr:r.ptr,ptrType:r.ptrType,smartPtr:r.smartPtr,smartPtrType:r.smartPtrType}),at=r=>{function t(i){return i.$$.ptrType.registeredClass.name}I(t(r)+" instance already deleted")},it=!1,qt=r=>{},Mr=r=>{r.smartPtr?r.smartPtrType.rawDestructor(r.smartPtr):r.ptrType.registeredClass.rawDestructor(r.ptr)},Ht=r=>{r.count.value-=1;var t=r.count.value===0;t&&Mr(r)},Ne=r=>globalThis.FinalizationRegistry?(it=new FinalizationRegistry(t=>{Ht(t.$$)}),Ne=t=>{var i=t.$$,l=!!i.smartPtr;if(l){var d={$$:i};it.register(t,d,t)}return t},qt=t=>it.unregister(t),Ne(r)):(Ne=t=>t,r),Tr=()=>{let r=De.prototype;Object.assign(r,{isAliasOf(i){if(!(this instanceof De)||!(i instanceof De))return!1;var l=this.$$.ptrType.registeredClass,d=this.$$.ptr;i.$$=i.$$;for(var f=i.$$.ptrType.registeredClass,p=i.$$.ptr;l.baseClass;)d=l.upcast(d),l=l.baseClass;for(;f.baseClass;)p=f.upcast(p),f=f.baseClass;return l===f&&d===p},clone(){if(this.$$.ptr||at(this),this.$$.preservePointerOnDelete)return this.$$.count.value+=1,this;var i=Ne(Object.create(Object.getPrototypeOf(this),{$$:{value:xr(this.$$)}}));return i.$$.count.value+=1,i.$$.deleteScheduled=!1,i},delete(){this.$$.ptr||at(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&I("Object already scheduled for deletion"),qt(this),Ht(this.$$),this.$$.preservePointerOnDelete||(this.$$.smartPtr=void 0,this.$$.ptr=void 0)},isDeleted(){return!this.$$.ptr},deleteLater(){return this.$$.ptr||at(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&I("Object already scheduled for deletion"),this.$$.deleteScheduled=!0,this}});let t=Symbol.dispose;t&&(r[t]=r.delete)};function De(){}var Ft=(r,t)=>Object.defineProperty(t,"name",{value:r}),Lt={},st=(r,t,i)=>{if(r[t].overloadTable===void 0){var l=r[t];r[t]=function(...d){return r[t].overloadTable.hasOwnProperty(d.length)||I(`Function '${i}' called with an invalid number of arguments (${d.length}) - expects one of (${r[t].overloadTable})!`),r[t].overloadTable[d.length].apply(this,d)},r[t].overloadTable=[],r[t].overloadTable[l.argCount]=l}},Nr=(r,t,i)=>{n.hasOwnProperty(r)?(I(`Cannot register public name '${r}' twice`),st(n,r,r),n[r].overloadTable.hasOwnProperty(i)&&I(`Cannot register multiple overloads of a function with the same number of arguments (${i})!`),n[r].overloadTable[i]=t):(n[r]=t,n[r].argCount=i)},qr=48,Hr=57,Fr=r=>{r=r.replace(/[^a-zA-Z0-9_]/g,"$");var t=r.charCodeAt(0);return t>=qr&&t<=Hr?`_${r}`:r};function Lr(r,t,i,l,d,f,p,h){this.name=r,this.constructor=t,this.instancePrototype=i,this.rawDestructor=l,this.baseClass=d,this.getActualType=f,this.upcast=p,this.downcast=h,this.pureVirtualFunctions=[]}var ot=(r,t,i)=>{for(;t!==i;)t.upcast||I(`Expected null or instance of ${i.name}, got an instance of ${t.name}`),r=t.upcast(r),t=t.baseClass;return r},ct=r=>{if(r===null)return"null";var t=typeof r;return t==="object"||t==="array"||t==="function"?r.toString():""+r};function Qr(r,t){if(t===null)return this.isReference&&I(`null is not a valid ${this.name}`),0;t.$$||I(`Cannot pass "${ct(t)}" as a ${this.name}`),t.$$.ptr||I(`Cannot pass deleted object as a pointer of type ${this.name}`);var i=t.$$.ptrType.registeredClass,l=ot(t.$$.ptr,i,this.registeredClass);return l}function _r(r,t){var i;if(t===null)return this.isReference&&I(`null is not a valid ${this.name}`),this.isSmartPointer?(i=this.rawConstructor(),r!==null&&r.push(this.rawDestructor,i),i):0;(!t||!t.$$)&&I(`Cannot pass "${ct(t)}" as a ${this.name}`),t.$$.ptr||I(`Cannot pass deleted object as a pointer of type ${this.name}`),!this.isConst&&t.$$.ptrType.isConst&&I(`Cannot convert argument of type ${t.$$.smartPtrType?t.$$.smartPtrType.name:t.$$.ptrType.name} to parameter type ${this.name}`);var l=t.$$.ptrType.registeredClass;if(i=ot(t.$$.ptr,l,this.registeredClass),this.isSmartPointer)switch(t.$$.smartPtr===void 0&&I("Passing raw pointer to smart pointer is illegal"),this.sharingPolicy){case 0:t.$$.smartPtrType===this?i=t.$$.smartPtr:I(`Cannot convert argument of type ${t.$$.smartPtrType?t.$$.smartPtrType.name:t.$$.ptrType.name} to parameter type ${this.name}`);break;case 1:i=t.$$.smartPtr;break;case 2:if(t.$$.smartPtrType===this)i=t.$$.smartPtr;else{var d=t.clone();i=this.rawShare(i,dt.toHandle(()=>d.delete())),r!==null&&r.push(this.rawDestructor,i)}break;default:I("Unsupported sharing policy")}return i}function Pr(r,t){if(t===null)return this.isReference&&I(`null is not a valid ${this.name}`),0;t.$$||I(`Cannot pass "${ct(t)}" as a ${this.name}`),t.$$.ptr||I(`Cannot pass deleted object as a pointer of type ${this.name}`),t.$$.ptrType.isConst&&I(`Cannot convert argument of type ${t.$$.ptrType.name} to parameter type ${this.name}`);var i=t.$$.ptrType.registeredClass,l=ot(t.$$.ptr,i,this.registeredClass);return l}var Qt=(r,t,i)=>{if(t===i)return r;if(i.baseClass===void 0)return null;var l=Qt(r,t,i.baseClass);return l===null?null:i.downcast(l)},Sr={},Rr=(r,t)=>{for(t===void 0&&I("ptr should not be undefined");r.baseClass;)t=r.upcast(t),r=r.baseClass;return t},Ir=(r,t)=>(t=Rr(r,t),Sr[t]),Be=(r,t)=>{(!t.ptrType||!t.ptr)&&ue("makeClassHandle requires ptr and ptrType");var i=!!t.smartPtrType,l=!!t.smartPtr;return i!==l&&ue("Both smartPtrType and smartPtr must be specified"),t.count={value:1},Ne(Object.create(r,{$$:{value:t,writable:!0}}))};function Jr(r){var t=this.getPointee(r);if(!t)return this.destructor(r),null;var i=Ir(this.registeredClass,t);if(i!==void 0){if(i.$$.count.value===0)return i.$$.ptr=t,i.$$.smartPtr=r,i.clone();var l=i.clone();return this.destructor(r),l}function d(){return this.isSmartPointer?Be(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:t,smartPtrType:this,smartPtr:r}):Be(this.registeredClass.instancePrototype,{ptrType:this,ptr:r})}var f=this.registeredClass.getActualType(t),p=Lt[f];if(!p)return d.call(this);var h;this.isConst?h=p.constPointerType:h=p.pointerType;var k=Qt(t,this.registeredClass,h.registeredClass);return k===null?d.call(this):this.isSmartPointer?Be(h.registeredClass.instancePrototype,{ptrType:h,ptr:k,smartPtrType:this,smartPtr:r}):Be(h.registeredClass.instancePrototype,{ptrType:h,ptr:k})}var Or=()=>{Object.assign(Ve.prototype,{getPointee(r){return this.rawGetPointee&&(r=this.rawGetPointee(r)),r},destructor(r){this.rawDestructor?.(r)},readValueFromPointer:ve,fromWireType:Jr})};function Ve(r,t,i,l,d,f,p,h,k,H,F){this.name=r,this.registeredClass=t,this.isReference=i,this.isConst=l,this.isSmartPointer=d,this.pointeeType=f,this.sharingPolicy=p,this.rawGetPointee=h,this.rawConstructor=k,this.rawShare=H,this.rawDestructor=F,!d&&t.baseClass===void 0?l?(this.toWireType=Qr,this.destructorFunction=null):(this.toWireType=Pr,this.destructorFunction=null):this.toWireType=_r}var Kr=(r,t,i)=>{n.hasOwnProperty(r)||ue("Replacing nonexistent public symbol"),n[r].overloadTable!==void 0&&i!==void 0?n[r].overloadTable[i]=t:(n[r]=t,n[r].argCount=i)},Xr=r=>Wt.get(r),ce=(r,t,i=!1)=>{r=te(r);function l(){var f=Xr(t);return f}var d=l();return typeof d!="function"&&I(`unknown function pointer with signature ${r}: ${t}`),d};class zr extends Error{}var Dr=r=>{var t=Xt(r),i=te(t);return fe(t),i},Ze=(r,t)=>{var i=[],l={};function d(f){if(!l[f]&&!se[f]){if(ge[f]){ge[f].forEach(d);return}i.push(f),l[f]=!0}}throw t.forEach(d),new zr(`${r}: `+i.map(Dr).join([", "]))},Br=(r,t,i,l,d,f,p,h,k,H,F,K,J)=>{F=te(F),f=ce(d,f),h&&=ce(p,h),H&&=ce(k,H),J=ce(K,J);var X=Fr(F);Nr(X,function(){Ze(`Cannot construct ${F} due to unbound types`,[l])}),ae([r,t,i],l?[l]:[],W=>{W=W[0];var ie,A;l?(ie=W.registeredClass,A=ie.instancePrototype):A=De.prototype;var Y=Ft(F,function(...ht){if(Object.getPrototypeOf(this)!==me)throw new Me(`Use 'new' to construct ${F}`);if(oe.constructor_body===void 0)throw new Me(`${F} has no accessible constructor`);var At=oe.constructor_body[ht.length];if(At===void 0)throw new Me(`Tried to invoke ctor of ${F} with invalid number of parameters (${ht.length}) - expected (${Object.keys(oe.constructor_body).toString()}) parameters instead!`);return At.apply(this,ht)}),me=Object.create(A,{constructor:{value:Y}});Y.prototype=me;var oe=new Lr(F,Y,me,J,ie,f,h,H);oe.baseClass&&(oe.baseClass.__derivedClasses??=[],oe.baseClass.__derivedClasses.push(oe));var pt=new Ve(F,oe,!0,!1,!1),we=new Ve(F+"*",oe,!1,!1,!1),Ge=new Ve(F+" const*",oe,!1,!0,!1);return Lt[r]={pointerType:we,constPointerType:Ge},Kr(X,Y),[pt,we,Ge]})};function Vr(r){for(var t=1;t{for(var i=[],l=0;l>2]);return i},_t=r=>{r=r.trim();let t=r.indexOf("(");return t===-1?r:r.slice(0,t)},Zr=(r,t,i,l,d,f,p,h,k)=>{var H=ut(i,l);t=te(t),t=_t(t),f=ce(d,f,h),ae([],[r],F=>{F=F[0];var K=`${F.name}.${t}`;function J(){Ze(`Cannot call ${K} due to unbound types`,H)}t.startsWith("@@")&&(t=Symbol[t.substring(2)]);var X=F.registeredClass.constructor;return X[t]===void 0?(J.argCount=i-1,X[t]=J):(st(X,t,K),X[t].overloadTable[i-1]=J),ae([],H,W=>{var ie=[W[0],null].concat(W.slice(1)),A=lt(K,ie,null,f,p);if(X[t].overloadTable===void 0?(A.argCount=i-1,X[t]=A):X[t].overloadTable[i-1]=A,F.registeredClass.__derivedClasses)for(let Y of F.registeredClass.__derivedClasses)Y.constructor.hasOwnProperty(t)||(Y.constructor[t]=A);return[]}),[]})},Yr=(r,t,i,l,d,f)=>{var p=ut(t,i);d=ce(l,d),ae([],[r],h=>{h=h[0];var k=`constructor ${h.name}`;if(h.registeredClass.constructor_body===void 0&&(h.registeredClass.constructor_body=[]),h.registeredClass.constructor_body[t-1]!==void 0)throw new Me(`Cannot register multiple constructors with identical number of parameters (${t-1}) for class '${h.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`);return h.registeredClass.constructor_body[t-1]=()=>{Ze(`Cannot construct ${h.name} due to unbound types`,p)},ae([],p,H=>(H.splice(1,0,null),h.registeredClass.constructor_body[t-1]=lt(k,H,null,d,f),[])),[]})},Gr=(r,t,i,l,d,f,p,h,k,H)=>{var F=ut(i,l);t=te(t),t=_t(t),f=ce(d,f,k),ae([],[r],K=>{K=K[0];var J=`${K.name}.${t}`;t.startsWith("@@")&&(t=Symbol[t.substring(2)]),h&&K.registeredClass.pureVirtualFunctions.push(t);function X(){Ze(`Cannot call ${J} due to unbound types`,F)}var W=K.registeredClass.instancePrototype,ie=W[t];return ie===void 0||ie.overloadTable===void 0&&ie.className!==K.name&&ie.argCount===i-2?(X.argCount=i-2,X.className=K.name,W[t]=X):(st(W,t,J),W[t].overloadTable[i-2]=X),ae([],F,A=>{var Y=lt(J,A,K,f,p);return W[t].overloadTable===void 0?(Y.argCount=i-2,W[t]=Y):W[t].overloadTable[i-2]=Y,[]}),[]})},Pt=[],be=[0,1,,1,null,1,!0,1,!1,1],St=[],jr=r=>{if(r>9&&--be[r+1]===0){var t=be[r];be[r]=void 0;var i=St[r];i&&(St[r]=void 0,i(t)),Pt.push(r)}},dt={toValue:r=>(r||I(`Cannot use deleted val. handle = ${r}`),be[r]),toHandle:r=>{switch(r){case void 0:return 2;case null:return 4;case!0:return 6;case!1:return 8;default:{let t=Pt.pop()||be.length;return be[t]=r,be[t+1]=1,t}}}},Wr={name:"emscripten::val",fromWireType:r=>{var t=dt.toValue(r);return jr(r),t},toWireType:(r,t)=>dt.toHandle(t),readValueFromPointer:ve,destructorFunction:null},Ar=r=>de(r,Wr),Rt,It,en=(r,t)=>{switch(t){case 4:return function(i){return this.fromWireType(Rt[i>>2])};case 8:return function(i){return this.fromWireType(It[i>>3])};default:throw new TypeError(`invalid float width (${t}): ${r}`)}},tn=(r,t,i)=>{t=te(t),de(r,{name:t,fromWireType:l=>l,toWireType:(l,d)=>d,readValueFromPointer:en(t,i),destructorFunction:null})},rn=(r,t,i,l,d)=>{t=te(t);let f=l===0,p=k=>k;if(f){var h=32-8*i;p=k=>k<>>h,d=p(d)}de(r,{name:t,fromWireType:p,toWireType:(k,H)=>H,readValueFromPointer:Nt(t,i,l!==0),destructorFunction:null})},nn=(r,t,i)=>{var l=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array],d=l[t];function f(p){var h=R[p>>2],k=R[p+4>>2];return new d(B.buffer,k,h)}i=te(i),de(r,{name:i,fromWireType:f,readValueFromPointer:f},{ignoreDuplicateRegistrations:!0})},an=(r,t,i,l)=>{if(!(l>0))return 0;for(var d=i,f=i+l-1,p=0;p=f)break;t[i++]=h}else if(h<=2047){if(i+1>=f)break;t[i++]=192|h>>6,t[i++]=128|h&63}else if(h<=65535){if(i+2>=f)break;t[i++]=224|h>>12,t[i++]=128|h>>6&63,t[i++]=128|h&63}else{if(i+3>=f)break;t[i++]=240|h>>18,t[i++]=128|h>>12&63,t[i++]=128|h>>6&63,t[i++]=128|h&63,p++}}return t[i]=0,i-d},Jt=(r,t,i)=>an(r,q,t,i),Ot=r=>{for(var t=0,i=0;i=55296&&l<=57343?(t+=4,++i):t+=3}return t},sn=(r,t)=>{t=te(t),de(r,{name:t,fromWireType(i){var l=R[i>>2],d=i+4,f;return f=P(d,l,!0),fe(i),f},toWireType(i,l){l instanceof ArrayBuffer&&(l=new Uint8Array(l));var d,f=typeof l=="string";f||ArrayBuffer.isView(l)&&l.BYTES_PER_ELEMENT==1||I("Cannot pass non-string to std::string"),f?d=Ot(l):d=l.length;var p=ft(4+d+1),h=p+4;return R[p>>2]=d,f?Jt(l,h,d+1):q.set(l,h),i!==null&&i.push(fe,p),p},readValueFromPointer:ve,destructorFunction(i){fe(i)}})},Kt=globalThis.TextDecoder?new TextDecoder("utf-16le"):void 0,on=(r,t,i)=>{var l=r>>1,d=w(Te,l,t/2,i);if(d-l>16&&Kt)return Kt.decode(Te.subarray(l,d));for(var f="",p=l;p{if(i<2)return 0;i-=2;for(var l=t,d=i>1]=p,t+=2}return Xe[t>>1]=0,t-l},ln=r=>r.length*2,un=(r,t,i)=>{for(var l="",d=r>>2,f=0;!(f>=t/4);f++){var p=R[d+f];if(!p&&!i)break;l+=String.fromCodePoint(p)}return l},dn=(r,t,i=2147483647)=>{if(i<4)return 0;for(var l=t,d=l+i-4,f=0;f65535&&f++,ze[t>>2]=p,t+=4,t+4>d)break}return ze[t>>2]=0,t-l},fn=r=>{for(var t=0,i=0;i65535&&i++,t+=4}return t},pn=(r,t,i)=>{i=te(i);var l,d,f;t===2?(l=on,d=cn,f=ln):(l=un,d=dn,f=fn),de(r,{name:i,fromWireType:p=>{var h=R[p>>2],k=l(p+4,h*t,!0);return fe(p),k},toWireType:(p,h)=>{typeof h!="string"&&I(`Cannot pass non-string to C++ string type ${i}`);var k=f(h),H=ft(4+k+t);return R[H>>2]=k/t,d(h,H+4,k+t),p!==null&&p.push(fe,H),H},readValueFromPointer:ve,destructorFunction(p){fe(p)}})},hn=(r,t,i,l,d,f)=>{ye[r]={name:te(t),rawConstructor:ce(i,l),rawDestructor:ce(d,f),fields:[]}},gn=(r,t,i,l,d,f,p,h,k,H)=>{ye[r].fields.push({fieldName:te(t),getterReturnType:i,getter:ce(l,d),getterContext:f,setterArgumentType:p,setter:ce(h,k),setterContext:H})},wn=(r,t)=>{t=te(t),de(r,{isVoid:!0,name:t,fromWireType:()=>{},toWireType:(i,l)=>{}})},vn=()=>{},qe={},mn=r=>{if(r instanceof z||r=="unwind")return y;a(1,r)},yn=r=>{y=r,a(r,new z(r))},bn=r=>{if(!g)try{return r()}catch(t){mn(t)}},kn=()=>performance.now(),Cn=(r,t)=>{if(qe[r]&&(clearTimeout(qe[r].id),delete qe[r]),!t)return 0;var i=setTimeout(()=>{delete qe[r],bn(()=>zt(r,kn()))},t);return qe[r]={id:i,timeout_ms:t},0},En=()=>2147483648,Un=(r,t)=>Math.ceil(r/t)*t,$n=r=>{var t=Ye.buffer.byteLength,i=(r-t+65535)/65536|0;try{return Ye.grow(i),E(),1}catch{}},xn=r=>{var t=q.length;r>>>=0;var i=En();if(r>i)return!1;for(var l=1;l<=4;l*=2){var d=t*(1+.2/l);d=Math.min(d,r+100663296);var f=Math.min(i,Un(Math.max(r,d),65536)),p=$n(f);if(p)return!0}return!1},Mn=r=>52;function Tn(r,t,i,l){return 70}var Nn=[null,[],[]],qn=(r,t)=>{var i=Nn[r];!t||t===10?((r===1?s:c)(x(i)),i.length=0):i.push(t)},Hn=(r,t,i,l)=>{for(var d=0,f=0;f>2],h=R[t+4>>2];t+=8;for(var k=0;k>2]=d,0};Tr(),Or(),n.wasmBinary&&(u=n.wasmBinary),n.UTF8ToString=P,n.stringToUTF8=Jt,n.lengthBytesUTF8=Ot;var Xt,ft,fe,zt,Dt,Bt,Vt,Zt,Yt,Gt,jt,Ye,Wt;function Fn(r){Xt=r.B,ft=n._malloc=r.C,fe=n._free=r.D,zt=r.F,Dt=r.G,Bt=r.H,Vt=r.I,Zt=r.J,Yt=r.K,Gt=r.L,Ye=r.z,Wt=r.E,jt=r.M}var Ln={t:$e,x:Ke,f:nt,i:Ur,l:$r,o:Br,b:Zr,n:Yr,d:Gr,y:Ar,h:tn,c:rn,a:nn,k:sn,g:pn,j:hn,e:gn,m:wn,w:vn,p:Cn,u:xn,s:Mn,q:Tn,r:Hn,v:yn};async function Qn(){!g&&$()}var He;return He=await b(),await Qn(),n}var da=ua,fa=new Int8Array([-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,90,63,64,65,66,-1,67,68,69,70,71,-1,72,73,52,53,54,55,56,57,58,59,60,61,74,75,76,77,78,79,80,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,81,-1,82,83,84,85,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,86,87,88,89,-1]);function pa(e){let n=new Uint8Array(170930),a=0,o=0,s=0,c=-1;for(let u=0,g=e.length;u127)continue;let m=fa[y];if(!(m<0))if(c<0)c=m;else{c+=m*91,o|=c<88?13:14;do n[a++]=o,o>>>=8,s-=8;while(s>7);c=-1}}return c>=0&&(n[a++]=(o|c<""})}function wa(){kt=void 0}var va=class{_module;constructor(e){this._module=e}malloc(e){let n=this._module._malloc(e);return{ptr:n,size:e,dispose:()=>this.free({ptr:n,size:e,dispose:()=>{}})}}free(e){this._module._free(e.ptr)}dataToHeap(e){let n=this.malloc(e.byteLength);return this._module.HEAPU8.set(e,n.ptr),n}heapView(e){return this._module.HEAPU8.subarray(e.ptr,e.ptr+e.size)}heapToUint8Array(e){return new Uint8Array([...this.heapView(e)])}lengthBytes(e){return this._module.lengthBytesUTF8(e)}stringToHeap(e){let n=this.lengthBytes(e)+1,a=this._module._malloc(n);return this._module.stringToUTF8(e,a,n),{ptr:a,size:n,dispose:()=>this.free({ptr:a,size:n,dispose:()=>{}})}}heapToString(e){return this._module.UTF8ToString(e.ptr,e.size)}hasFilesystem(){let e=this._module;return e.FS_createPath!==void 0&&e.FS_createDataFile!==void 0&&e.FS_preloadFile!==void 0&&e.FS_unlink!==void 0}createPath(e,n=!0,a=!0){return this._module.FS_createPath("/",e,n,a)}createDataFile(e,n,a=!0,o=!0,s=!0){return this._module.FS_createDataFile("/",e,n,a,o,s)}preloadFile(e,n,a=!0,o=!0,s=!1,c=!0,u=!1){return this._module.FS_preloadFile("/",e,n,a,o,s,c,u)}unlink(e){return this._module.FS_unlink(e)}},Fe,ma=4294967295;function Le(e,n){if(!Number.isInteger(e)||e<0||e>ma)throw new Error(`${n} length ${e} is outside the WASM32-safe range`)}function wt(e){if(e.length===0)return new Uint8Array(0);if(e.length===1)return e[0];let n=0;for(let s of e)n+=s.length;let a=new Uint8Array(n),o=0;for(let s of e)a.set(s,o),o+=s.length;return a}var ya=class vr{_mainModule;_zstdClass;_zstd;_compressionLevel;_decompressFrameCompleted=!1;_decompressRemaining=0;_decompressSawInput=!1;constructor(n){this._mainModule=new va(n),this._zstdClass=n.zstd,this._zstd=new this._zstdClass,this._compressionLevel=this._zstdClass.defaultCLevel()}static load(){return Fe||(Fe=ga().then(n=>new vr(n))),Fe}static unload(){wa(),Fe?.then(n=>{n?._zstd?.delete()}),Fe=void 0}copyHeap(n){return this._mainModule.heapView(n).slice()}mallocChecked(n,a){Le(n,a);let o=this._mainModule.malloc(n);if(!o.ptr)throw this._mainModule.free(o),new Error(`Failed to allocate ${n} bytes for ${a}`);return o}throwStreamError(n,a){throw new Error(`${n} failed: ${a.errorName||"unknown error"}`)}ensureStreamOk(n,a){a.error&&this.throwStreamError(n,a)}version(){return this._zstdClass.version()}reset(){let n=this._zstd.reset();this.ensureStreamOk("reset",n),this._decompressFrameCompleted=!1,this._decompressRemaining=0,this._decompressSawInput=!1}resetCompression(n){n!==void 0&&(this._compressionLevel=n);let a=this._zstd.resetCompression(this._compressionLevel);this.ensureStreamOk("resetCompression",a)}resetDecompression(){let n=this._zstd.resetDecompression();this.ensureStreamOk("resetDecompression",n),this._decompressFrameCompleted=!1,this._decompressRemaining=0,this._decompressSawInput=!1}setCompressionLevel(n){let a=this._zstd.setCompressionLevel(n);this.ensureStreamOk("setCompressionLevel",a),this._compressionLevel=n}compress(n,a=this.defaultCLevel()){Le(n.length,"compress input");let o=this._mainModule.dataToHeap(n);try{if(!o.ptr&&n.length>0)throw new Error("Failed to allocate compress input buffer");let s=this._zstdClass.compressBound(n.length),c=this.mallocChecked(s,"compress output");try{if(c.size=this._zstdClass.compress(c.ptr,s,o.ptr,o.size,a),this._zstdClass.isError(c.size))throw new Error(`compress failed: ${this._zstdClass.getErrorName(c.size)}`);return this.copyHeap(c)}finally{this._mainModule.free(c)}}finally{this._mainModule.free(o)}}compressChunk(n){if(Le(n.length,"compressChunk input"),n.length===0)return new Uint8Array(0);let a=this._mainModule.dataToHeap(n);try{if(!a.ptr)throw new Error("Failed to allocate compressChunk input buffer");let o=this._zstdClass.CStreamOutSize(),s=[],c=0;for(;c0&&(u.size=g.produced,s.push(this.copyHeap(u)))}finally{this._mainModule.free(u)}}return wt(s)}finally{this._mainModule.free(a)}}compressEnd(){let n=this._zstdClass.CStreamOutSize(),a=[],o=1;for(;o!==0;){let s=this.mallocChecked(n,"compressEnd output");try{let c=this._zstd.compressEnd(s.ptr,n);if(this.ensureStreamOk("compressEnd",c),c.produced===0&&c.remaining!==0)throw new Error("compressEnd failed: no progress while finishing stream");o=c.remaining,c.produced>0&&(s.size=c.produced,a.push(this.copyHeap(s)))}finally{this._mainModule.free(s)}}return wt(a)}frameContentSize(n){return this._zstdClass.getFrameContentSize(n.ptr,n.size)}decompress(n){Le(n.length,"decompress input");let a=this._mainModule.dataToHeap(n);try{if(!a.ptr&&n.length>0)throw new Error("Failed to allocate decompress input buffer");let o=this.frameContentSize(a);if(o.error)throw new Error(`Failed to get frame content size: ${o.errorName}`);let s=o.known;if(s){let c=this._zstdClass.findFrameCompressedSize(a.ptr,a.size);if(this._zstdClass.isError(c))throw new Error(`Failed to find frame compressed size: ${this._zstdClass.getErrorName(c)}`);c0?a.ptr+c:0,C=this._zstd.decompressChunk(g.ptr,o,m,y);if(this.ensureStreamOk("decompressChunk",C),C.consumed===0&&C.produced===0){if(y===0){C.remaining>0&&(this._decompressRemaining=C.remaining);break}throw new Error("decompressChunk failed: no progress while consuming input")}c+=C.consumed,u=C.produced===o&&C.remaining!==0,C.produced>0&&(g.size=C.produced,s.push(this.copyHeap(g))),C.remaining===0?(this._decompressFrameCompleted=!0,this._decompressRemaining=0):this._decompressRemaining=C.remaining}finally{this._mainModule.free(g)}}return wt(s)}finally{this._mainModule.free(a)}}decompressEnd(){if(!this._decompressSawInput)throw new Error("decompressEnd failed: empty compressed input is not a completed frame");if(!this._decompressFrameCompleted||this._decompressRemaining!==0)throw new Error("decompressEnd failed: truncated Zstandard input")}defaultCLevel(){return this._zstdClass.defaultCLevel()}minCLevel(){return this._zstdClass.minCLevel()}maxCLevel(){return this._zstdClass.maxCLevel()}};let vt=null;function ba(){return vt||(vt=ya.load()),vt}function ka(e){return e.length>=4&&e[0]===40&&e[1]===181&&e[2]===47&&e[3]===253}function Ct(e){const n="SQLite format 3\0";if(e.length/g,">").replace(/"/g,""").replace(/'/g,"'")}function sr(e,n,a){const o=e.tagsList[0]??"",s=e.tagsList.length>1?` +${e.tagsList.length-1}`:"",c=Ce(e.tagsList.join(", ")),u=o?`${Ce(o)}${s}`:"",g=e.more.size>0?'':"";return`
`+(a?mr(e,n):"")+"
"}function mr(e,n){const a=[...e.more.entries()].map(([o,s])=>{const c=n.get(o);return`
${c?Ce(c.name):`Поле #${o}`}
${Ce(s)}
`}).join("");return a?`
${a}
`:""}function Ea(e,n){e.innerHTML=` - -
-

📚 Слова

-
- - -
-

-
База ещё не проверена.
-
-
-
- `;const a=e.querySelector("#home-db-banner"),o=e.querySelector("#home-db-text"),s=e.querySelector("#home-db-go"),c=e.querySelector("#words-search"),u=e.querySelector("#words-group"),g=e.querySelector("#words-count"),y=e.querySelector("#words-status"),m=e.querySelector("#words-list"),C=e.querySelector("#words-more");function N(w,x){a.hidden=!1,o.textContent=w,s.hidden=!1}function E(){a.hidden=!0}s.addEventListener("click",()=>{document.querySelector('.drawer-btn[data-page="sync"]')?.click()});let $=!1,T=new Map,v=0,M=[],S=null,O=0,Q=!1,L=0,U=0;function b(){const w=u.value;if(w==="")return{search:c.value.trim(),groupId:null};const x=Number(w);return{search:c.value.trim(),groupId:Number.isFinite(x)?x:null}}function z(){C.hidden=!($&&M.lengthsr(w,T,w.id===S)).join(""),g.textContent=v>0?`Показано ${M.length} из ${v}`:"",z()}function ee(w){if(S===w){w!=null&&D(w),S=null;return}S!=null&&D(S),S=w,w!=null&&re(w)}function D(w){const x=m.querySelector(`.word-row-wrap[data-word-id="${w}"]`);if(!x)return;const q=x.querySelector(".word-row"),P=x.querySelector(".word-extra");q&&(q.classList.remove("open"),q.setAttribute("aria-expanded","false")),P&&(P.hidden=!0,P.innerHTML="")}function re(w){const x=M.find(he=>he.id===w),q=m.querySelector(`.word-row-wrap[data-word-id="${w}"]`);if(!x||!q)return;const P=q.querySelector(".word-row"),R=q.querySelector(".word-extra");P&&(P.classList.add("open"),P.setAttribute("aria-expanded","true")),R&&(R.hidden=!1,R.innerHTML=mr(x,T))}function Z(w){y.textContent=w,y.hidden=w===""}async function j(){if(!$||Q)return;Q=!0;const w=++L,x=b();C.disabled=!0,M.length===0&&Z("Загрузка слов…");try{const[q,P]=await Promise.all([In({search:x.search,groupId:x.groupId??void 0}),er({search:x.search,groupId:x.groupId??void 0,limit:ir,offset:0})]);if(w!==L)return;v=q,M=P,O=P.length,S!=null&&!M.some(R=>R.id===S)&&(S=null),B(),Z(P.length===0?"По этому запросу слов нет.":""),P.length===0&&v===0&&(g.textContent="Слов в базе пока нет.")}catch(q){if(w!==L)return;Z(`Не удалось загрузить слова: ${q instanceof Error?q.message:String(q)}`)}finally{w===L&&(Q=!1,C.disabled=!1,z())}}async function _(){if(!$||Q||M.length>=v)return;Q=!0;const w=++L,x=b();C.disabled=!0,C.textContent="Загрузка…";try{const q=await er({search:x.search,groupId:x.groupId??void 0,limit:ir,offset:O});if(w!==L)return;M=M.concat(q),O+=q.length,m.insertAdjacentHTML("beforeend",q.map(P=>sr(P,T,P.id===S)).join("")),g.textContent=`Показано ${M.length} из ${v}`,z()}catch(q){if(w!==L)return;Z(`Не удалось догрузить: ${q instanceof Error?q.message:String(q)}`)}finally{w===L&&(Q=!1,C.disabled=!1,z())}}c.addEventListener("input",()=>{window.clearTimeout(U),U=window.setTimeout(()=>{j()},300)}),c.addEventListener("keydown",w=>{w.key==="Enter"&&(window.clearTimeout(U),j())}),u.addEventListener("change",()=>{j()}),C.addEventListener("click",()=>{_()}),m.addEventListener("click",w=>{const x=w.target.closest(".word-row");if(!x||x.disabled)return;const q=Number(x.dataset.wordId);Number.isFinite(q)&&ee(S===q?null:q)}),(async()=>{if(!await tt()){N("Локальной базы нет — скачай сохранение."),Z("Нет локальной базы — скачай сохранение на странице «Синхронизация»."),g.textContent="";return}try{const x=await Vn();if(!Ct(x)){N("Файл в OPFS не похож на SQLite — перекачай сохранение.",!0),Z("Файл в OPFS не похож на SQLite — перекачай сохранение.");return}E();try{const[P,R]=await Promise.all([Jn(),gr()]),he=P;T=new Map(R.map(le=>[le.id,le])),u.innerHTML=''+he.map(le=>``).join(""),$=!0,await j(),u.options[0].textContent=`Все группы (${v})`}catch(P){Z(`БД есть, но слова не прочитались: ${P instanceof Error?P.message:String(P)}`)}}catch(x){N(`Не удалось прочитать базу: ${x instanceof Error?x.message:String(x)}`)}})()}function or(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function Ua(e){const n=e%10,a=e%100;return n===1&&a!==11?"запись":n>=2&&n<=4&&(a<12||a>14)?"записи":"записей"}function $a(e,n){const a=or(e.name)||`Колода #${e.id}`;return`
`}function xa(e,n){e.innerHTML=` -
-

🗂️ Колоды

-

-
Проверка локальной базы…
-
-
- `;const a=e.querySelector("#decks-count"),o=e.querySelector("#decks-status"),s=e.querySelector("#decks-list");function c(u){o.textContent=u,o.hidden=u===""}s.addEventListener("click",u=>{const g=u.target.closest("[data-act]");if(!g)return;const y=Number(g.dataset.deckId);Number.isFinite(y)&&(g.dataset.act==="review"?n.goReview(y):n.goDeck(y))}),(async()=>{if(!await tt()){c("Нет локальной базы — скачай сохранение на странице «Синхронизация»."),a.textContent="";return}c("Загрузка колод…");try{const[g,y]=await Promise.all([On(),Xn()]);if(g.length===0){a.textContent="Колоды пока отсутствуют.",c(""),s.innerHTML="";return}a.textContent=`Всего: ${g.length}`,c(""),s.innerHTML=g.map(m=>$a(m,y.get(m.id)??0)).join("")}catch(g){c(`Не удалось загрузить колоды: ${g instanceof Error?g.message:String(g)}`)}})()}function Ma(e){return e?Ta(e):'—'}function Ta(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function mt(e){return e.replace(/&/g,"&").replace(/"/g,""").replace(//g,">")}function Na(e){const n=e%10,a=e%100;return n===1&&a!==11?"слово":n>=2&&n<=4&&(a<12||a>14)?"слова":"слов"}function qa(e,n,a){e.innerHTML=` - -
-

Колода…

-
Загрузка колоды…
-
-
- `;const o=e.querySelector("#deck-back"),s=e.querySelector("#deck-title"),c=e.querySelector("#deck-status"),u=e.querySelector("#deck-body");o.addEventListener("click",()=>a.goDecks()),(async()=>{if(!await tt()){s.textContent="Колода недоступна",c.textContent="Нет локальной базы — скачай сохранение на странице «Синхронизация».";return}let y=null;try{y=await Et(n)}catch(U){s.textContent="Колода недоступна",c.textContent=`Не удалось загрузить колоду: ${U instanceof Error?U.message:String(U)}`;return}if(!y){s.textContent="Колода не найдена",c.textContent=`Колоды с id=${n} нет в локальной базе. Возможно, база обновилась — вернись к списку.`;return}const m=y;s.textContent=m.name||`Колода #${m.id}`,c.hidden=!0;let C=null;try{C=await zn(m.id)}catch(U){c.hidden=!1,c.textContent=`Данные колоды загружены, а количество слов — нет: ${U instanceof Error?U.message:String(U)}`}u.innerHTML=` -
-
id
${m.id}
-
name
${Ma(m.name)}
-
-
- - - - - - -
- -
-

-
-

📚 ${C==null?"Количество слов неизвестно":`${C} ${Na(C)}`}

-
- -
- `;const N=u.querySelector("#deck-form"),E=u.querySelector("#deck-forward"),$=u.querySelector("#deck-backward"),T=u.querySelector("#deck-filter"),v=u.querySelector("#deck-save"),M=u.querySelector("#deck-form-status");u.querySelector("#deck-review").addEventListener("click",()=>a.goReview(m.id));const O=()=>`${E.value} -${$.value} -${T.value}`;let Q=O();function L(){v.disabled=O()===Q||v.dataset.busy==="1"}for(const U of[E,$,T])U.addEventListener("input",L);L(),N.addEventListener("submit",U=>{U.preventDefault(),!v.disabled&&(v.dataset.busy="1",v.disabled=!0,v.textContent="Сохранение…",M.textContent="",(async()=>{try{const b=await Kn(m.id,{forward:E.value,backward:$.value,filter:T.value});if(!b){M.textContent="Колода пропала из базы — вернись к списку.";return}E.value=b.forward,$.value=b.backward,T.value=b.filter,Q=O(),M.textContent="✅ Сохранено в локальную базу."}catch(b){M.textContent=`Не удалось сохранить: ${b instanceof Error?b.message:String(b)}`}finally{delete v.dataset.busy,v.textContent="💾 Сохранить",L()}})())})})()}function Ha(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}const Fa=[{label:"😞 Не получилось",cls:"grade-fail"},{label:"😟 Тяжело",cls:"grade-hard"},{label:"🙂 Хорошо",cls:"grade-good"},{label:"🤩 Легко",cls:"grade-easy"}];function La(e,n,a){e.innerHTML=` - -
-

Повторение…

-

-
Загрузка карточек…
-
-
- `;const o=e.querySelector("#review-back"),s=e.querySelector("#review-title"),c=e.querySelector("#review-progress"),u=e.querySelector("#review-status"),g=e.querySelector("#review-body");o.addEventListener("click",()=>a.goDecks());function y(L){u.textContent=L,u.hidden=L===""}let m=[],C=new Map,N=[],E="",$="",T=0,v=!1;function M(L,U){return L.length===0?`
—
`:`
`+L.map(b=>`

${Ha(b)}

`).join("")+"
"}function S(){const L=C.get(m[T]);if(!L){g.innerHTML="",c.textContent="",y("Слово пропало из базы — пропускаю.");return}const U=Dn(L,N),b=rr(E,U),z=v?rr($,U):null;c.textContent=`Карточка ${T+1} из ${m.length}`,y("");const B=v?M(z??[],"review-back-side"):'

Тап — показать ответ

',ee=v?`
- ${Fa.map(D=>``).join("")} -
`:"";g.innerHTML=` - - ${ee} - `,g.querySelector("#review-card").addEventListener("click",()=>{v||(v=!0,S())}),g.querySelectorAll(".review-grade").forEach(D=>{D.addEventListener("click",()=>{T+=1,v=!1,T>=m.length?O():S()})})}function O(){c.textContent=`Готово: ${m.length} из ${m.length}`,y(""),g.innerHTML=` -

Все карточки сессии просмотрены. Запись оценок — следующим шагом.

-
- -
- `,g.querySelector("#review-again").addEventListener("click",()=>{Q()})}async function Q(){if(y("Перемешиваю…"),g.innerHTML="",c.textContent="",m=await nr(n),T=0,v=!1,m.length===0){y("В колоде нет слов для повторения.");return}C=await tr(m),S()}(async()=>{if(!await tt()){s.textContent="Повторение недоступно",y("Нет локальной базы — скачай сохранение на странице «Синхронизация».");return}try{const[U,b]=await Promise.all([Et(n),gr()]);if(!U){s.textContent="Колода не найдена",y(`Колоды с id=${n} нет в локальной базе. Вернись к списку.`);return}if(N=b,E=U.forward,$=U.backward,s.textContent=U.name||`Колода #${U.id}`,m=await nr(U.id),m.length===0){c.textContent="",y("В колоде нет слов для повторения.");return}C=await tr(m),T=0,v=!1,S()}catch(U){s.textContent="Повторение недоступно",y(`Не удалось загрузить карточки: ${U instanceof Error?U.message:String(U)}`)}})()}const cr=24,Qa=/^[A-Za-z0-9]{24}$/,yr="japlearn_sync_token";function lr(e){return e.trim().replace(/\s+/g,"")}function Qe(e){return Qa.test(e)}function _a(){try{return localStorage.getItem(yr)??""}catch{return""}}function ur(e){try{localStorage.setItem(yr,e)}catch{}}const br="https://learning.micialware.ru";function Pa(e,n=br){return`${n.replace(/\/+$/,"")}/download/${encodeURIComponent(e)}`}async function Sa(e,n,a=br){const o=Pa(e,a);let s;try{s=await fetch(o,{method:"GET",...n})}catch(u){throw new Error(`Нет связи с сервером: ${u instanceof Error?u.message:String(u)}`)}if(!s.ok)throw s.status===404?new Error("Сохранение не найдено (404). Проверь токен."):new Error(`Сервер ответил ${s.status} ${s.statusText}`);const c=await s.arrayBuffer();if(c.byteLength===0)throw new Error("Сервер вернул пустой файл");return new Uint8Array(c)}function Ra(e){e.innerHTML=` -
-

📡 Синхронизация

-

Токен сохранения — 24 символа: латинские буквы и цифры.

- - - -
- - 0 / 24 -
- -
- -
- -

Введите токен из 24 символов

-
- `;const n=e.querySelector("#token-input"),a=e.querySelector("#token-error"),o=e.querySelector("#token-counter"),s=e.querySelector("#btn-download"),c=e.querySelector("#status");function u(E){c.textContent=E,console.log(`[JapLearn] ${E}`)}function g(){return lr(n.value)}function y(E){s.disabled=E||!Qe(g()),n.disabled=E}function m(){const E=g(),$=E.length;o.textContent=`${$} / ${cr}`;const T=Qe(E),v=$===0;n.classList.toggle("invalid",!v&&!T),n.setAttribute("aria-invalid",String(!v&&!T)),v?a.textContent="":$!==cr?a.textContent=`Нужно 24 символа, сейчас ${$}`:a.textContent=T?"":"Только латинские буквы и цифры",s.disabled=!T}async function C(){const E=g();if(Qe(E)){ur(E),y(!0),u("Скачивание сохранения...");try{const $=await Sa(E);u(`Скачано ${($.length/1024).toFixed(1)} КБ, распаковка...`);const T=await Ca($);u(`Распаковано ${(T.length/1024).toFixed(1)} КБ, сохранение в OPFS...`),await Bn(T),u(`Готово: база ${(T.length/1024).toFixed(1)} КБ сохранена локально (OPFS).`)}catch($){u(`Ошибка скачивания: ${$ instanceof Error?$.message:String($)}`)}finally{y(!1),m()}}}n.addEventListener("input",()=>{m()}),n.addEventListener("change",()=>{const E=g();n.value=E,Qe(E)&&(ur(E),u("Токен сохранён локально. Можно скачивать.")),m()}),s.addEventListener("click",()=>{C()});const N=_a();N&&(n.value=N),m(),Qe(lr(N))&&u("Токен восстановлен. Нажми «Скачать», чтобы получить базу.")}const Ia={home:"Главная",decks:"Колоды",deck:"Колода",review:"Повторение",sync:"Синхронизация"};function Je(e){const n=document.getElementById(e);if(!n)throw new Error(`[JapLearn] Не найден элемент #${e}`);return n}const kr=Je("burger-btn"),et=Je("drawer"),Cr=Je("drawer-overlay"),Ja=Je("page-title"),_e=Je("page");let xt=!1;function Oe(e){xt=e,et.classList.toggle("open",e),Cr.hidden=!e,kr.setAttribute("aria-expanded",String(e)),et.setAttribute("aria-hidden",String(!e)),document.body.classList.toggle("drawer-open",e)}function Oa(e){et.querySelectorAll(".drawer-btn").forEach(n=>{n.classList.toggle("active",n.dataset.page===e)})}function ke(e,n){Ja.textContent=Ia[e],Oa(e==="deck"||e==="review"?"decks":e),e==="home"?Ea(_e):e==="decks"?xa(_e,{goDeck:a=>ke("deck",a),goReview:a=>ke("review",a)}):e==="deck"?qa(_e,n??0,{goDecks:()=>ke("decks"),goReview:a=>ke("review",a)}):e==="review"?La(_e,n??0,{goDecks:()=>ke("decks")}):Ra(_e)}kr.addEventListener("click",()=>{Oe(!xt)});Cr.addEventListener("click",()=>{Oe(!1)});document.addEventListener("keydown",e=>{e.key==="Escape"&&xt&&Oe(!1)});et.querySelectorAll(".drawer-btn").forEach(e=>{e.addEventListener("click",()=>{const n=e.dataset.page;(n==="home"||n==="decks"||n==="sync")&&ke(n),Oe(!1)})});await _n();Oe(!1);ke("home");console.log("[JapLearn] crossOriginIsolated =",window.crossOriginIsolated); diff --git a/dist/assets/index-Bk0XDs7i.js b/dist/assets/index-Bk0XDs7i.js new file mode 100644 index 0000000..a85ac55 --- /dev/null +++ b/dist/assets/index-Bk0XDs7i.js @@ -0,0 +1,126 @@ +(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))o(s);new MutationObserver(s=>{for(const c of s)if(c.type==="childList")for(const u of c.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&o(u)}).observe(document,{childList:!0,subtree:!0});function a(s){const c={};return s.integrity&&(c.integrity=s.integrity),s.referrerPolicy&&(c.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?c.credentials="include":s.crossOrigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function o(s){if(s.ep)return;s.ep=!0;const c=a(s);fetch(s.href,c)}})();const Re=new Worker(new URL("/assets/db-worker-DFFhVJkA.js",import.meta.url),{type:"module"});let fr=0,Ae=null;const et=new Map;function Rn(){return Ae||(Ae=new Promise((e,r)=>{Re.onmessage=o=>{const s=o.data;if(s.type==="ready")return;const c=et.get(s.id);c&&(et.delete(s.id),s.type=="success"?c.resolve(s.data):c.reject(new Error(s.error??s.message??"Неизвестная ошибка Worker")))},Re.onerror=o=>{console.error("[SQLite Worker] Ошибка:",o),r(new Error("Ошибка Worker"))};const a=++fr;et.set(a,{resolve:e,reject:r}),Re.postMessage({type:"start",id:a})}),Ae)}function pr(e,r){return new Promise((a,o)=>{if(!Re){o(new Error("Worker не создан"));return}const s=++fr;et.set(s,{resolve:a,reject:o}),Re.postMessage({type:e,id:s,...r})})}async function ie(e,r){return await pr("query",{sql:e,params:r??[]})}async function hr(e,r){await pr("command",{sql:e,params:r??[]})}function wr(e){return new Date(e*1e3)}function In(){return Math.floor(Date.now()/1e3)}function Jn(e){const r=wr(e),a=String(r.getDate()).padStart(2,"0"),o=String(r.getMonth()+1).padStart(2,"0");return`${a}.${o}.${r.getFullYear()}`}function On(e){const r=wr(e),a=String(r.getHours()).padStart(2,"0"),o=String(r.getMinutes()).padStart(2,"0");return`${Jn(e)} ${a}:${o}`}function Kn(e){if(e==null)return[];const r=String(e).trim();return r?r.split(/[,\s;]+/).map(a=>a.trim()).filter(a=>a.length>0):[]}function Dn(e){const r=new Map;if(e==null)return r;const a=String(e);if(!a)return r;const o=a.split("");for(let s=0;s+10?`WHERE ${r.join(" AND ")}`:"",params:a}}async function Xn(e={}){const{clause:r,params:a}=mr(e),s=(await ie(`SELECT COUNT(*) as cnt FROM words ${r}`,a))[0]?.cnt;return typeof s=="number"?s:Number(s??0)}async function tr(e={}){const{clause:r,params:a}=mr(e),o=Math.max(1,Math.min(e.limit??50,500)),s=Math.max(0,e.offset??0);return(await ie(`SELECT id, key, value, tags, more, group_id FROM words ${r} ORDER BY id LIMIT ? OFFSET ?`,[...a,o,s])).map(gr)}async function zn(){return(await ie("SELECT id, name FROM word_groups ORDER BY id")).map(r=>({id:Number(r.id),name:String(r.name??"")}))}async function Bn(){return(await ie("SELECT id, name, forward, backward, filter FROM decks ORDER BY id")).map(r=>({id:Number(r.id),name:String(r.name??""),forward:String(r.forward??""),backward:String(r.backward??""),filter:String(r.filter??"")}))}async function Vn(e,r){return await hr("UPDATE decks SET forward = ?, backward = ?, filter = ? WHERE id = ?",[r.forward,r.backward,r.filter,e]),nt(e)}async function nt(e){const a=(await ie("SELECT id, name, forward, backward, filter FROM decks WHERE id = ? LIMIT 1",[e]))[0];return a?{id:Number(a.id),name:String(a.name??""),forward:String(a.forward??""),backward:String(a.backward??""),filter:String(a.filter??"")}:null}async function Yn(){const e=await ie("SELECT deck_id, COUNT(*) as cnt FROM states WHERE deck_id IS NOT NULL GROUP BY deck_id"),r=new Map;for(const a of e){if(a.deck_id==null)continue;const o=a.cnt;r.set(Number(a.deck_id),typeof o=="number"?o:Number(o??0))}return r}async function Zn(e){const a=(await ie("SELECT COUNT(DISTINCT word_id) as cnt FROM states WHERE deck_id = ?",[e]))[0]?.cnt;return typeof a=="number"?a:Number(a??0)}async function rr(e){const r=new Map,a=[...new Set(e.filter(s=>Number.isFinite(s)))];if(a.length===0)return r;const o=400;for(let s=0;s"?").join(","),g=await ie(`SELECT id, key, value, tags, more, group_id FROM words WHERE id IN (${u})`,c);for(const y of g.map(gr))r.set(y.id,y)}return r}function Gn(e,r){const a=new Map;a.set("key",e.key),a.set("value",e.value),a.set("tags",e.tagsList.join(", "));for(const o of r){const s=e.more.get(o.id)??"";if(s)for(const c of[o.identifier,o.name]){const u=c.trim().toLowerCase();!u||a.has(u)||a.set(u,s)}}return a}function nr(e,r){const a=[];for(const o of e.split(/\s+/)){const s=o.trim().toLowerCase();if(!s)continue;const c=(r.get(s)??"").trim();c&&a.push(c)}return a}async function ar(e,r){const o=(await ie("SELECT DISTINCT word_id as word_id FROM states WHERE deck_id = ?",[e])).map(s=>Number(s.word_id)).filter(s=>Number.isFinite(s));for(let s=o.length-1;s>0;s--){const c=Math.floor(Math.random()*(s+1));[o[s],o[c]]=[o[c],o[s]]}return o}async function vr(){return(await ie("SELECT id, name, identifier FROM custom_fields ORDER BY id")).map(r=>({id:Number(r.id),name:String(r.name??""),identifier:String(r.identifier??"")}))}async function jn(e,r){const o=(await ie("SELECT score FROM states WHERE deck_id = ? AND word_id = ? ORDER BY id DESC LIMIT 1",[e,r]))[0];if(!o||o.score==null)return null;const s=Number(o.score);return Number.isFinite(s)?s:null}async function Wn(e){await hr("INSERT INTO history (deck_id, word_id, timestamp, answer, before, after, fsrs) VALUES (?, ?, ?, ?, ?, ?, 0)",[e.deckId,e.wordId,In(),e.answer,e.before,e.after])}async function An(e){const a=(await ie("SELECT COUNT(*) as cnt FROM history WHERE deck_id = ?",[e]))[0]?.cnt;return typeof a=="number"?a:Number(a??0)}async function ea(e,r={}){const a=Math.max(1,Math.min(r.limit??50,500)),o=Math.max(0,r.offset??0);return(await ie(`SELECT h.id, h.deck_id, h.word_id, h.timestamp, h.answer, h.before, h.after, h.fsrs, + w.key as wkey, w.value as wvalue + FROM history h LEFT JOIN words w ON w.id = h.word_id + WHERE h.deck_id = ? ORDER BY h.timestamp DESC, h.id DESC LIMIT ? OFFSET ?`,[e,a,o])).map(c=>({id:Number(c.id),deck_id:Number(c.deck_id),word_id:Number(c.word_id),timestamp:Number(c.timestamp??0),answer:Number(c.answer??0),before:Number(c.before??0),after:Number(c.after??0),fsrs:Number(c.fsrs??0),wordKey:c.wkey==null?"":String(c.wkey),wordValue:c.wvalue==null?"":String(c.wvalue)}))}const Ut="japanese_app.db";async function xt(){return navigator.storage.getDirectory()}async function ta(e){e[18]=1,e[19]=1;const a=await(await xt()).getFileHandle(Ut,{create:!0}),o=a.createSyncAccessHandle;if(typeof o=="function")try{const c=await o.call(a);try{c.truncate(0),c.write(e,{at:0}),c.flush()}finally{c.close()}return}catch{}const s=await a.createWritable();try{await s.truncate(0);const c=new Uint8Array(e.length);c.set(e),await s.write(c)}finally{await s.close()}}async function ra(){const e=await xt();let r;try{r=await e.getFileHandle(Ut)}catch{throw new Error("Локальная база не найдена — сначала скачай сохранение")}const a=await r.getFile();if(a.size===0)throw new Error("Локальная база пуста");return new Uint8Array(await a.arrayBuffer())}async function Je(){try{return(await(await(await xt()).getFileHandle(Ut)).getFile()).size>0}catch{return!1}}var na=ArrayBuffer,G=Uint8Array,tt=Uint16Array,aa=Int16Array,at=Int32Array,mt=function(e,r,a){if(G.prototype.slice)return G.prototype.slice.call(e,r,a);(r==null||r<0)&&(r=0),(a==null||a>e.length)&&(a=e.length);var o=new G(a-r);return o.set(e.subarray(r,a)),o},Ie=function(e,r,a,o){if(G.prototype.fill)return G.prototype.fill.call(e,r,a,o);for((a==null||a<0)&&(a=0),(o==null||o>e.length)&&(o=e.length);ae.length)&&(o=e.length);a2046MB)","invalid block type","FSE accuracy too high","match distance too far back","unexpected EOF"],A=function(e,r,a){var o=new Error(r||ia[e]);if(o.code=e,Error.captureStackTrace&&Error.captureStackTrace(o,A),!a)throw o;return o},sr=function(e,r,a){for(var o=0,s=0;o>>0},ca=function(e,r){var a=e[0]|e[1]<<8|e[2]<<16;if(a==3126568&&e[3]==253){var o=e[4],s=o>>5&1,c=o>>2&1,u=o&3,g=o>>6;o&8&&A(0);var y=6-s,m=u==3?4:u,b=sr(e,y,m);y+=m;var T=g?1<>3);U=N+(N>>3)*(e[5]&7)}U>2145386496&&A(1);var w=new G((r==1?k||U:r?0:U)+12);return w[0]=1,w[4]=4,w[8]=8,{b:y+T,y:0,l:0,d:b,w:r&&r!=1?r:w.subarray(12),e:U,o:new at(w.buffer,0,3),u:k,c,m:Math.min(131072,U)}}else if((a>>4|e[3]<<20)==25481893)return oa(e,4)+8;A(0)},Ce=function(e){for(var r=0;1<a&&A(3);for(var c=1<0;){var J=Ce(u+1),L=o>>3,z=(1<>(o&7)&z,C=(1<C&&(P-=_)),k[++g]=--P,P==-1?(u+=P,E[--b]=g):u-=P,!P)do{var F=o>>3;y=(e[F]|e[F+1]<<8)>>(o&7)&3,o+=2,g+=y}while(y==3)}(g>255||u)&&A(0);for(var X=0,j=(c>>1)+(c>>3)+3,V=c-1,B=0;B<=g;++B){var H=k[B];if(H<1){U[B]=-H;continue}for(m=0;m=b)}}for(X&&A(0),m=0;m>3,{b:s,s:E,n:I,t:N}]},la=function(e,r){var a=0,o=-1,s=new G(292),c=e[r],u=s.subarray(0,256),g=s.subarray(256,268),y=new tt(s.buffer,268);if(c<128){var m=Oe(e,r+1,6),b=m[0],T=m[1];r+=c;var k=b<<3,U=e[r];U||A(0);for(var N=0,w=0,E=T.b,I=E,J=(++r<<3)-8+Ce(U);J-=E,!(J>3;if(N+=(e[L]|e[L+1]<<8)>>(J&7)&(1<>3,w+=(e[L]|e[L+1]<<8)>>(J&7)&(1<255&&A(0)}else{for(o=c-127;a>4,u[a+1]=z&15}++r}var P=0;for(a=0;a11&&A(0),P+=C&&1<0;--a){var B=y[a];Ie(V,a,B,y[a-1]=B+g[a]*(1<<_-a))}for(y[0]!=R&&A(0),a=0;ag&&T>3,U=(e[k]|e[k+1]<<8|e[k+2]<<16)>>(b&7);y=(y<>2,u=c<<1,g=c+u;Pe(e.subarray(o,o+=e[0]|e[1]<<8),r.subarray(0,c),a),Pe(e.subarray(o,o+=e[2]|e[3]<<8),r.subarray(c,u),a),Pe(e.subarray(o,o+=e[4]|e[5]<<8),r.subarray(u,g),a),Pe(e.subarray(o),r.subarray(g),a)},ga=function(e,r,a){var o,s=r.b,c=e[s],u=c>>1&3;r.l=c&1;var g=c>>3|e[s+1]<<5|e[s+2]<<13,y=(s+=3)+g;if(u==1)return s>=e.length?void 0:(r.b=s+1,a?(Ie(a,e[s],r.y,r.y+=g),a):Ie(new G(g),e[s]));if(!(y>e.length)){if(u==0)return r.b=y,a?(a.set(e.subarray(s,y),r.y),r.y+=g,a):mt(e,s,y);if(u==2){var m=e[s],b=m&3,T=m>>2&3,k=m>>4,U=0,N=0;b<2?T&1?k|=e[++s]<<4|(T&2&&e[++s]<<12):k=m>>3:(N=T,T<2?(k|=(e[++s]&63)<<4,U=e[s]>>6|e[++s]<<2):T==2?(k|=e[++s]<<4|(e[++s]&3)<<12,U=e[s]>>2|e[++s]<<6):(k|=e[++s]<<4|(e[++s]&63)<<12,U=e[s]>>6|e[++s]<<2|e[++s]<<10)),++s;var w=a?a.subarray(r.y,r.y+r.m):new G(r.m),E=w.length-k;if(b==0)w.set(e.subarray(s,s+=k),E);else if(b==1)Ie(w,e[s++],E);else{var I=r.h;if(b==2){var J=la(e,s);U+=s-(s=J[0]),r.h=I=J[1]}else I||A(0);(N?wa:Pe)(e.subarray(s,s+=U),w.subarray(E),I)}var L=e[s++];if(L){L==255?L=(e[s++]|e[s++]<<8)+32512:L>127&&(L=L-128<<8|e[s++]);var z=e[s++];z&3&&A(0);for(var P=[da,fa,ua],C=2;C>-1;--C){var _=z>>(C<<1)+2&3;if(_==1){var R=new G([0,0,e[s++]]);P[C]={s:R.subarray(2,3),n:R.subarray(0,1),t:new tt(R.buffer,0,1),b:0}}else _==2?(o=Oe(e,s,9-(C&1)),s=o[0],P[C]=o[1]):_==3&&(r.t||A(0),P[C]=r.t[C])}var F=r.t=P,X=F[0],j=F[1],V=F[2],B=e[y-1];B||A(0);var H=(y<<3)-8+Ce(B)-V.b,v=H>>3,x=0,M=(e[v]|e[v+1]<<8)>>(H&7)&(1<>3;var Q=(e[v]|e[v+1]<<8)>>(H&7)&(1<>3;var O=(e[v]|e[v+1]<<8)>>(H&7)&(1<>3;var ve=1<>>(H&7)&ve-1);v=(H-=Et[xe])>>3;var oe=ha[xe]+((e[v]|e[v+1]<<8|e[v+2]<<16)>>(H&7)&(1<>3;var we=pa[he]+((e[v]|e[v+1]<<8|e[v+2]<<16)>>(H&7)&(1<>3,M=V.t[M]+((e[v]|e[v+1]<<8)>>(H&7)&(1<>3,O=X.t[O]+((e[v]|e[v+1]<<8)>>(H&7)&(1<>3,Q=j.t[Q]+((e[v]|e[v+1]<<8)>>(H&7)&(1<3)r.o[2]=r.o[1],r.o[1]=r.o[0],r.o[0]=ne-=3;else{var $e=ne-(we!=0);$e?(ne=$e==3?r.o[0]-1:r.o[$e],$e>1&&(r.o[2]=r.o[1]),r.o[1]=r.o[0],r.o[0]=ne):ne=r.o[0]}for(var C=0;Coe&&(ae=oe);for(var C=0;C{throw t},o,s=console.log.bind(console),c=console.error.bind(console),u,g=!1,y,m=n=>n.startsWith("file://"),b=!1;function T(){return je.buffer}function k(){if(!R?.buffer?.resizable){var n=T();R=new Int8Array(n),ze=new Int16Array(n),r.HEAPU8=M=new Uint8Array(n),Ne=new Uint16Array(n),Be=new Int32Array(n),O=new Uint32Array(n),It=new Float32Array(n),Jt=new Float64Array(n),Tt=new BigInt64Array(n),Nt=new BigUint64Array(n)}}function U(){b=!0,qe.A()}function N(n){n=`Aborted(${n})`,c(n),g=!0,n+=". Build with -sASSERTIONS for more info.",b&&Bt();var t=new WebAssembly.RuntimeError(n);throw t}var w;let E=()=>"";function I(n){if(n==w&&u)return new Uint8Array(u);throw"both async and sync fetching of the wasm failed"}async function J(n){if(!u)try{var t=await o(n);return new Uint8Array(t)}catch{}return I(n)}async function L(n,t){try{var i=await J(n),l=await WebAssembly.instantiate(i,t);return l}catch(d){c(`failed to asynchronously prepare wasm: ${d}`),N(d)}}async function z(n,t,i){if(!n&&!m(t))try{var l=fetch(t,{credentials:"same-origin"}),d=await WebAssembly.instantiateStreaming(l,i);return d}catch(f){c(`wasm streaming compile failed: ${f}`),c("falling back to ArrayBuffer instantiation")}return L(t,i)}function P(){var n={a:Qn};return n}async function C(){function n(f){return qe=f.exports,_n(qe),k(),qe}function t(f){return n(f.instance)}var i=P();w??=E();var l=await z(u,w,i),d=t(l);return d}class _{name="ExitStatus";constructor(t){this.message=`Program terminated with exit(${t})`,this.status=t}}var R,F=()=>Wt,X=n=>{var t=n.getArg(F(),0);return Gt(t)},j=()=>Zt(),V=n=>Vt(n),B=n=>Yt(n),H=globalThis.TextDecoder&&new TextDecoder,v=(n,t,i,l)=>{var d=t+i;if(l)return d;for(;n[t]&&!(t>=d);)++t;return t},x=(n,t=0,i,l)=>{var d=v(n,t,i,l);if(d-t>16&&n.buffer&&H)return H.decode(n.subarray(t,d));for(var f="";t>10,56320|S&1023)}}return f},M,Q=(n,t,i)=>n?x(M,n,t,i):"",O,he=n=>{var t=j(),i=B(4),l=B(4);jt(n,i,l);var d=O[i>>2],f=O[l>>2],p=Q(d);pe(d);var h;return f&&(h=Q(f),pe(f)),V(t),[p,h]},ue=n=>{var t=X(n);return he(t)},xe=n=>{var t=new WebAssembly.Exception(F(),[n],{traceStack:!0});throw t.message=ue(t),t},Xe=()=>N(""),be={},Me=n=>{for(;n.length;){var t=n.pop(),i=n.pop();i(t)}};function ve(n){return this.fromWireType(O[n>>2])}var ne={},oe={},we={};class $e extends Error{constructor(t){super(t),this.name="InternalError"}}var de=n=>{throw new $e(n)},ae=(n,t,i)=>{n.forEach(h=>we[h]=t);function l(h){var $=i(h);$.length!==n.length&&de("Mismatched type converter count");for(var S=0;S{d[h]=oe[$],++p,p===f.length&&l(d)}));f.length===0&&l(d)},st=n=>{var t=be[n];delete be[n];var i=t.rawConstructor,l=t.rawDestructor,d=t.fields,f=d.map(p=>p.getterReturnType).concat(d.map(p=>p.setterArgumentType));ae([n],f,p=>{var h={};for(var[$,S]of d.entries()){let q=p[$],Y=S.getter,D=S.getterContext,Z=p[$+d.length],ee=S.setter,se=S.setterContext;h[S.fieldName]={read:te=>q.fromWireType(Y(D,te)),write:(te,W)=>{var ye=[];ee(se,te,Z.toWireType(ye,W)),Me(ye)},optional:q.optional}}return[{name:t.name,fromWireType:q=>{var Y={};for(var D in h)Y[D]=h[D].read(q);return l(q),Y},toWireType:(q,Y)=>{for(var D in h)if(!(D in Y)&&!h[D].optional)throw new TypeError(`Missing field: "${D}"`);var Z=i();for(D in h)h[D].write(Z,Y[D]);return q!==null&&q.push(l,Z),Z},readValueFromPointer:ve,destructorFunction:l}]})},re=n=>{for(var t="";;){var i=M[n++];if(!i)return t;t+=String.fromCharCode(i)}};class Te extends Error{constructor(t){super(t),this.name="BindingError"}}var K=n=>{throw new Te(n)};function xr(n,t,i={}){var l=t.name;if(n||K(`type "${l}" must have a positive integer typeid pointer`),oe.hasOwnProperty(n)){if(i.ignoreDuplicateRegistrations)return;K(`Cannot register type '${l}' twice`)}if(oe[n]=t,delete we[n],ne.hasOwnProperty(n)){var d=ne[n];delete ne[n],d.forEach(f=>f())}}function fe(n,t,i={}){return xr(n,t,i)}var ze,Ne,Be,Tt,Nt,St=(n,t,i)=>{switch(t){case 1:return i?l=>R[l]:l=>M[l];case 2:return i?l=>ze[l>>1]:l=>Ne[l>>1];case 4:return i?l=>Be[l>>2]:l=>O[l>>2];case 8:return i?l=>Tt[l>>3]:l=>Nt[l>>3];default:throw new TypeError(`invalid integer width (${t}): ${n}`)}},Mr=(n,t,i,l,d)=>{t=re(t);let f=l===0n,p=h=>h;if(f){let h=i*8;p=$=>BigInt.asUintN(h,$),d=p(d)}fe(n,{name:t,fromWireType:p,toWireType:(h,$)=>(typeof $=="number"&&($=BigInt($)),$),readValueFromPointer:St(t,i,!f),destructorFunction:null})},Tr=(n,t,i,l)=>{t=re(t),fe(n,{name:t,fromWireType:function(d){return!!d},toWireType:function(d,f){return f?i:l},readValueFromPointer:function(d){return this.fromWireType(M[d])},destructorFunction:null})},Nr=n=>({count:n.count,deleteScheduled:n.deleteScheduled,preservePointerOnDelete:n.preservePointerOnDelete,ptr:n.ptr,ptrType:n.ptrType,smartPtr:n.smartPtr,smartPtrType:n.smartPtrType}),it=n=>{function t(i){return i.$$.ptrType.registeredClass.name}K(t(n)+" instance already deleted")},ot=!1,Ht=n=>{},Sr=n=>{n.smartPtr?n.smartPtrType.rawDestructor(n.smartPtr):n.ptrType.registeredClass.rawDestructor(n.ptr)},qt=n=>{n.count.value-=1;var t=n.count.value===0;t&&Sr(n)},Se=n=>globalThis.FinalizationRegistry?(ot=new FinalizationRegistry(t=>{qt(t.$$)}),Se=t=>{var i=t.$$,l=!!i.smartPtr;if(l){var d={$$:i};ot.register(t,d,t)}return t},Ht=t=>ot.unregister(t),Se(n)):(Se=t=>t,n),Hr=()=>{let n=Ve.prototype;Object.assign(n,{isAliasOf(i){if(!(this instanceof Ve)||!(i instanceof Ve))return!1;var l=this.$$.ptrType.registeredClass,d=this.$$.ptr;i.$$=i.$$;for(var f=i.$$.ptrType.registeredClass,p=i.$$.ptr;l.baseClass;)d=l.upcast(d),l=l.baseClass;for(;f.baseClass;)p=f.upcast(p),f=f.baseClass;return l===f&&d===p},clone(){if(this.$$.ptr||it(this),this.$$.preservePointerOnDelete)return this.$$.count.value+=1,this;var i=Se(Object.create(Object.getPrototypeOf(this),{$$:{value:Nr(this.$$)}}));return i.$$.count.value+=1,i.$$.deleteScheduled=!1,i},delete(){this.$$.ptr||it(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&K("Object already scheduled for deletion"),Ht(this),qt(this.$$),this.$$.preservePointerOnDelete||(this.$$.smartPtr=void 0,this.$$.ptr=void 0)},isDeleted(){return!this.$$.ptr},deleteLater(){return this.$$.ptr||it(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&K("Object already scheduled for deletion"),this.$$.deleteScheduled=!0,this}});let t=Symbol.dispose;t&&(n[t]=n.delete)};function Ve(){}var Lt=(n,t)=>Object.defineProperty(t,"name",{value:n}),Ft={},ct=(n,t,i)=>{if(n[t].overloadTable===void 0){var l=n[t];n[t]=function(...d){return n[t].overloadTable.hasOwnProperty(d.length)||K(`Function '${i}' called with an invalid number of arguments (${d.length}) - expects one of (${n[t].overloadTable})!`),n[t].overloadTable[d.length].apply(this,d)},n[t].overloadTable=[],n[t].overloadTable[l.argCount]=l}},qr=(n,t,i)=>{r.hasOwnProperty(n)?(K(`Cannot register public name '${n}' twice`),ct(r,n,n),r[n].overloadTable.hasOwnProperty(i)&&K(`Cannot register multiple overloads of a function with the same number of arguments (${i})!`),r[n].overloadTable[i]=t):(r[n]=t,r[n].argCount=i)},Lr=48,Fr=57,_r=n=>{n=n.replace(/[^a-zA-Z0-9_]/g,"$");var t=n.charCodeAt(0);return t>=Lr&&t<=Fr?`_${n}`:n};function Qr(n,t,i,l,d,f,p,h){this.name=n,this.constructor=t,this.instancePrototype=i,this.rawDestructor=l,this.baseClass=d,this.getActualType=f,this.upcast=p,this.downcast=h,this.pureVirtualFunctions=[]}var lt=(n,t,i)=>{for(;t!==i;)t.upcast||K(`Expected null or instance of ${i.name}, got an instance of ${t.name}`),n=t.upcast(n),t=t.baseClass;return n},ut=n=>{if(n===null)return"null";var t=typeof n;return t==="object"||t==="array"||t==="function"?n.toString():""+n};function Pr(n,t){if(t===null)return this.isReference&&K(`null is not a valid ${this.name}`),0;t.$$||K(`Cannot pass "${ut(t)}" as a ${this.name}`),t.$$.ptr||K(`Cannot pass deleted object as a pointer of type ${this.name}`);var i=t.$$.ptrType.registeredClass,l=lt(t.$$.ptr,i,this.registeredClass);return l}function Rr(n,t){var i;if(t===null)return this.isReference&&K(`null is not a valid ${this.name}`),this.isSmartPointer?(i=this.rawConstructor(),n!==null&&n.push(this.rawDestructor,i),i):0;(!t||!t.$$)&&K(`Cannot pass "${ut(t)}" as a ${this.name}`),t.$$.ptr||K(`Cannot pass deleted object as a pointer of type ${this.name}`),!this.isConst&&t.$$.ptrType.isConst&&K(`Cannot convert argument of type ${t.$$.smartPtrType?t.$$.smartPtrType.name:t.$$.ptrType.name} to parameter type ${this.name}`);var l=t.$$.ptrType.registeredClass;if(i=lt(t.$$.ptr,l,this.registeredClass),this.isSmartPointer)switch(t.$$.smartPtr===void 0&&K("Passing raw pointer to smart pointer is illegal"),this.sharingPolicy){case 0:t.$$.smartPtrType===this?i=t.$$.smartPtr:K(`Cannot convert argument of type ${t.$$.smartPtrType?t.$$.smartPtrType.name:t.$$.ptrType.name} to parameter type ${this.name}`);break;case 1:i=t.$$.smartPtr;break;case 2:if(t.$$.smartPtrType===this)i=t.$$.smartPtr;else{var d=t.clone();i=this.rawShare(i,pt.toHandle(()=>d.delete())),n!==null&&n.push(this.rawDestructor,i)}break;default:K("Unsupported sharing policy")}return i}function Ir(n,t){if(t===null)return this.isReference&&K(`null is not a valid ${this.name}`),0;t.$$||K(`Cannot pass "${ut(t)}" as a ${this.name}`),t.$$.ptr||K(`Cannot pass deleted object as a pointer of type ${this.name}`),t.$$.ptrType.isConst&&K(`Cannot convert argument of type ${t.$$.ptrType.name} to parameter type ${this.name}`);var i=t.$$.ptrType.registeredClass,l=lt(t.$$.ptr,i,this.registeredClass);return l}var _t=(n,t,i)=>{if(t===i)return n;if(i.baseClass===void 0)return null;var l=_t(n,t,i.baseClass);return l===null?null:i.downcast(l)},Jr={},Or=(n,t)=>{for(t===void 0&&K("ptr should not be undefined");n.baseClass;)t=n.upcast(t),n=n.baseClass;return t},Kr=(n,t)=>(t=Or(n,t),Jr[t]),Ye=(n,t)=>{(!t.ptrType||!t.ptr)&&de("makeClassHandle requires ptr and ptrType");var i=!!t.smartPtrType,l=!!t.smartPtr;return i!==l&&de("Both smartPtrType and smartPtr must be specified"),t.count={value:1},Se(Object.create(n,{$$:{value:t,writable:!0}}))};function Dr(n){var t=this.getPointee(n);if(!t)return this.destructor(n),null;var i=Kr(this.registeredClass,t);if(i!==void 0){if(i.$$.count.value===0)return i.$$.ptr=t,i.$$.smartPtr=n,i.clone();var l=i.clone();return this.destructor(n),l}function d(){return this.isSmartPointer?Ye(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:t,smartPtrType:this,smartPtr:n}):Ye(this.registeredClass.instancePrototype,{ptrType:this,ptr:n})}var f=this.registeredClass.getActualType(t),p=Ft[f];if(!p)return d.call(this);var h;this.isConst?h=p.constPointerType:h=p.pointerType;var $=_t(t,this.registeredClass,h.registeredClass);return $===null?d.call(this):this.isSmartPointer?Ye(h.registeredClass.instancePrototype,{ptrType:h,ptr:$,smartPtrType:this,smartPtr:n}):Ye(h.registeredClass.instancePrototype,{ptrType:h,ptr:$})}var Xr=()=>{Object.assign(Ze.prototype,{getPointee(n){return this.rawGetPointee&&(n=this.rawGetPointee(n)),n},destructor(n){this.rawDestructor?.(n)},readValueFromPointer:ve,fromWireType:Dr})};function Ze(n,t,i,l,d,f,p,h,$,S,q){this.name=n,this.registeredClass=t,this.isReference=i,this.isConst=l,this.isSmartPointer=d,this.pointeeType=f,this.sharingPolicy=p,this.rawGetPointee=h,this.rawConstructor=$,this.rawShare=S,this.rawDestructor=q,!d&&t.baseClass===void 0?l?(this.toWireType=Pr,this.destructorFunction=null):(this.toWireType=Ir,this.destructorFunction=null):this.toWireType=Rr}var zr=(n,t,i)=>{r.hasOwnProperty(n)||de("Replacing nonexistent public symbol"),r[n].overloadTable!==void 0&&i!==void 0?r[n].overloadTable[i]=t:(r[n]=t,r[n].argCount=i)},Br=n=>At.get(n),le=(n,t,i=!1)=>{n=re(n);function l(){var f=Br(t);return f}var d=l();return typeof d!="function"&&K(`unknown function pointer with signature ${n}: ${t}`),d};class Vr extends Error{}var Yr=n=>{var t=Xt(n),i=re(t);return pe(t),i},Ge=(n,t)=>{var i=[],l={};function d(f){if(!l[f]&&!oe[f]){if(we[f]){we[f].forEach(d);return}i.push(f),l[f]=!0}}throw t.forEach(d),new Vr(`${n}: `+i.map(Yr).join([", "]))},Zr=(n,t,i,l,d,f,p,h,$,S,q,Y,D)=>{q=re(q),f=le(d,f),h&&=le(p,h),S&&=le($,S),D=le(Y,D);var Z=_r(q);qr(Z,function(){Ge(`Cannot construct ${q} due to unbound types`,[l])}),ae([n,t,i],l?[l]:[],ee=>{ee=ee[0];var se,te;l?(se=ee.registeredClass,te=se.instancePrototype):te=Ve.prototype;var W=Lt(q,function(...gt){if(Object.getPrototypeOf(this)!==ye)throw new Te(`Use 'new' to construct ${q}`);if(ce.constructor_body===void 0)throw new Te(`${q} has no accessible constructor`);var er=ce.constructor_body[gt.length];if(er===void 0)throw new Te(`Tried to invoke ctor of ${q} with invalid number of parameters (${gt.length}) - expected (${Object.keys(ce.constructor_body).toString()}) parameters instead!`);return er.apply(this,gt)}),ye=Object.create(te,{constructor:{value:W}});W.prototype=ye;var ce=new Qr(q,W,ye,D,se,f,h,S);ce.baseClass&&(ce.baseClass.__derivedClasses??=[],ce.baseClass.__derivedClasses.push(ce));var wt=new Ze(q,ce,!0,!1,!1),ge=new Ze(q+"*",ce,!1,!1,!1),We=new Ze(q+" const*",ce,!1,!0,!1);return Ft[n]={pointerType:ge,constPointerType:We},zr(Z,W),[wt,ge,We]})};function Gr(n){for(var t=1;t{for(var i=[],l=0;l>2]);return i},Qt=n=>{n=n.trim();let t=n.indexOf("(");return t===-1?n:n.slice(0,t)},jr=(n,t,i,l,d,f,p,h,$)=>{var S=ft(i,l);t=re(t),t=Qt(t),f=le(d,f,h),ae([],[n],q=>{q=q[0];var Y=`${q.name}.${t}`;function D(){Ge(`Cannot call ${Y} due to unbound types`,S)}t.startsWith("@@")&&(t=Symbol[t.substring(2)]);var Z=q.registeredClass.constructor;return Z[t]===void 0?(D.argCount=i-1,Z[t]=D):(ct(Z,t,Y),Z[t].overloadTable[i-1]=D),ae([],S,ee=>{var se=[ee[0],null].concat(ee.slice(1)),te=dt(Y,se,null,f,p);if(Z[t].overloadTable===void 0?(te.argCount=i-1,Z[t]=te):Z[t].overloadTable[i-1]=te,q.registeredClass.__derivedClasses)for(let W of q.registeredClass.__derivedClasses)W.constructor.hasOwnProperty(t)||(W.constructor[t]=te);return[]}),[]})},Wr=(n,t,i,l,d,f)=>{var p=ft(t,i);d=le(l,d),ae([],[n],h=>{h=h[0];var $=`constructor ${h.name}`;if(h.registeredClass.constructor_body===void 0&&(h.registeredClass.constructor_body=[]),h.registeredClass.constructor_body[t-1]!==void 0)throw new Te(`Cannot register multiple constructors with identical number of parameters (${t-1}) for class '${h.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`);return h.registeredClass.constructor_body[t-1]=()=>{Ge(`Cannot construct ${h.name} due to unbound types`,p)},ae([],p,S=>(S.splice(1,0,null),h.registeredClass.constructor_body[t-1]=dt($,S,null,d,f),[])),[]})},Ar=(n,t,i,l,d,f,p,h,$,S)=>{var q=ft(i,l);t=re(t),t=Qt(t),f=le(d,f,$),ae([],[n],Y=>{Y=Y[0];var D=`${Y.name}.${t}`;t.startsWith("@@")&&(t=Symbol[t.substring(2)]),h&&Y.registeredClass.pureVirtualFunctions.push(t);function Z(){Ge(`Cannot call ${D} due to unbound types`,q)}var ee=Y.registeredClass.instancePrototype,se=ee[t];return se===void 0||se.overloadTable===void 0&&se.className!==Y.name&&se.argCount===i-2?(Z.argCount=i-2,Z.className=Y.name,ee[t]=Z):(ct(ee,t,D),ee[t].overloadTable[i-2]=Z),ae([],q,te=>{var W=dt(D,te,Y,f,p);return ee[t].overloadTable===void 0?(W.argCount=i-2,ee[t]=W):ee[t].overloadTable[i-2]=W,[]}),[]})},Pt=[],ke=[0,1,,1,null,1,!0,1,!1,1],Rt=[],en=n=>{if(n>9&&--ke[n+1]===0){var t=ke[n];ke[n]=void 0;var i=Rt[n];i&&(Rt[n]=void 0,i(t)),Pt.push(n)}},pt={toValue:n=>(n||K(`Cannot use deleted val. handle = ${n}`),ke[n]),toHandle:n=>{switch(n){case void 0:return 2;case null:return 4;case!0:return 6;case!1:return 8;default:{let t=Pt.pop()||ke.length;return ke[t]=n,ke[t+1]=1,t}}}},tn={name:"emscripten::val",fromWireType:n=>{var t=pt.toValue(n);return en(n),t},toWireType:(n,t)=>pt.toHandle(t),readValueFromPointer:ve,destructorFunction:null},rn=n=>fe(n,tn),It,Jt,nn=(n,t)=>{switch(t){case 4:return function(i){return this.fromWireType(It[i>>2])};case 8:return function(i){return this.fromWireType(Jt[i>>3])};default:throw new TypeError(`invalid float width (${t}): ${n}`)}},an=(n,t,i)=>{t=re(t),fe(n,{name:t,fromWireType:l=>l,toWireType:(l,d)=>d,readValueFromPointer:nn(t,i),destructorFunction:null})},sn=(n,t,i,l,d)=>{t=re(t);let f=l===0,p=$=>$;if(f){var h=32-8*i;p=$=>$<>>h,d=p(d)}fe(n,{name:t,fromWireType:p,toWireType:($,S)=>S,readValueFromPointer:St(t,i,l!==0),destructorFunction:null})},on=(n,t,i)=>{var l=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array],d=l[t];function f(p){var h=O[p>>2],$=O[p+4>>2];return new d(R.buffer,$,h)}i=re(i),fe(n,{name:i,fromWireType:f,readValueFromPointer:f},{ignoreDuplicateRegistrations:!0})},cn=(n,t,i,l)=>{if(!(l>0))return 0;for(var d=i,f=i+l-1,p=0;p=f)break;t[i++]=h}else if(h<=2047){if(i+1>=f)break;t[i++]=192|h>>6,t[i++]=128|h&63}else if(h<=65535){if(i+2>=f)break;t[i++]=224|h>>12,t[i++]=128|h>>6&63,t[i++]=128|h&63}else{if(i+3>=f)break;t[i++]=240|h>>18,t[i++]=128|h>>12&63,t[i++]=128|h>>6&63,t[i++]=128|h&63,p++}}return t[i]=0,i-d},Ot=(n,t,i)=>cn(n,M,t,i),Kt=n=>{for(var t=0,i=0;i=55296&&l<=57343?(t+=4,++i):t+=3}return t},ln=(n,t)=>{t=re(t),fe(n,{name:t,fromWireType(i){var l=O[i>>2],d=i+4,f;return f=Q(d,l,!0),pe(i),f},toWireType(i,l){l instanceof ArrayBuffer&&(l=new Uint8Array(l));var d,f=typeof l=="string";f||ArrayBuffer.isView(l)&&l.BYTES_PER_ELEMENT==1||K("Cannot pass non-string to std::string"),f?d=Kt(l):d=l.length;var p=ht(4+d+1),h=p+4;return O[p>>2]=d,f?Ot(l,h,d+1):M.set(l,h),i!==null&&i.push(pe,p),p},readValueFromPointer:ve,destructorFunction(i){pe(i)}})},Dt=globalThis.TextDecoder?new TextDecoder("utf-16le"):void 0,un=(n,t,i)=>{var l=n>>1,d=v(Ne,l,t/2,i);if(d-l>16&&Dt)return Dt.decode(Ne.subarray(l,d));for(var f="",p=l;p{if(i<2)return 0;i-=2;for(var l=t,d=i>1]=p,t+=2}return ze[t>>1]=0,t-l},fn=n=>n.length*2,pn=(n,t,i)=>{for(var l="",d=n>>2,f=0;!(f>=t/4);f++){var p=O[d+f];if(!p&&!i)break;l+=String.fromCodePoint(p)}return l},hn=(n,t,i=2147483647)=>{if(i<4)return 0;for(var l=t,d=l+i-4,f=0;f65535&&f++,Be[t>>2]=p,t+=4,t+4>d)break}return Be[t>>2]=0,t-l},wn=n=>{for(var t=0,i=0;i65535&&i++,t+=4}return t},gn=(n,t,i)=>{i=re(i);var l,d,f;t===2?(l=un,d=dn,f=fn):(l=pn,d=hn,f=wn),fe(n,{name:i,fromWireType:p=>{var h=O[p>>2],$=l(p+4,h*t,!0);return pe(p),$},toWireType:(p,h)=>{typeof h!="string"&&K(`Cannot pass non-string to C++ string type ${i}`);var $=f(h),S=ht(4+$+t);return O[S>>2]=$/t,d(h,S+4,$+t),p!==null&&p.push(pe,S),S},readValueFromPointer:ve,destructorFunction(p){pe(p)}})},mn=(n,t,i,l,d,f)=>{be[n]={name:re(t),rawConstructor:le(i,l),rawDestructor:le(d,f),fields:[]}},vn=(n,t,i,l,d,f,p,h,$,S)=>{be[n].fields.push({fieldName:re(t),getterReturnType:i,getter:le(l,d),getterContext:f,setterArgumentType:p,setter:le(h,$),setterContext:S})},yn=(n,t)=>{t=re(t),fe(n,{isVoid:!0,name:t,fromWireType:()=>{},toWireType:(i,l)=>{}})},bn=()=>{},He={},kn=n=>{if(n instanceof _||n=="unwind")return y;a(1,n)},En=n=>{y=n,a(n,new _(n))},Cn=n=>{if(!g)try{return n()}catch(t){kn(t)}},$n=()=>performance.now(),Un=(n,t)=>{if(He[n]&&(clearTimeout(He[n].id),delete He[n]),!t)return 0;var i=setTimeout(()=>{delete He[n],Cn(()=>zt(n,$n()))},t);return He[n]={id:i,timeout_ms:t},0},xn=()=>2147483648,Mn=(n,t)=>Math.ceil(n/t)*t,Tn=n=>{var t=je.buffer.byteLength,i=(n-t+65535)/65536|0;try{return je.grow(i),k(),1}catch{}},Nn=n=>{var t=M.length;n>>>=0;var i=xn();if(n>i)return!1;for(var l=1;l<=4;l*=2){var d=t*(1+.2/l);d=Math.min(d,n+100663296);var f=Math.min(i,Mn(Math.max(n,d),65536)),p=Tn(f);if(p)return!0}return!1},Sn=n=>52;function Hn(n,t,i,l){return 70}var qn=[null,[],[]],Ln=(n,t)=>{var i=qn[n];!t||t===10?((n===1?s:c)(x(i)),i.length=0):i.push(t)},Fn=(n,t,i,l)=>{for(var d=0,f=0;f>2],h=O[t+4>>2];t+=8;for(var $=0;$>2]=d,0};Hr(),Xr(),r.wasmBinary&&(u=r.wasmBinary),r.UTF8ToString=Q,r.stringToUTF8=Ot,r.lengthBytesUTF8=Kt;var Xt,ht,pe,zt,Bt,Vt,Yt,Zt,Gt,jt,Wt,je,At;function _n(n){Xt=n.B,ht=r._malloc=n.C,pe=r._free=n.D,zt=n.F,Bt=n.G,Vt=n.H,Yt=n.I,Zt=n.J,Gt=n.K,jt=n.L,je=n.z,At=n.E,Wt=n.M}var Qn={t:xe,x:Xe,f:st,i:Mr,l:Tr,o:Zr,b:jr,n:Wr,d:Ar,y:rn,h:an,c:sn,a:on,k:ln,g:gn,j:mn,e:vn,m:yn,w:bn,p:Un,u:Nn,s:Sn,q:Hn,r:Fn,v:En};async function Pn(){!g&&U()}var qe;return qe=await C(),await Pn(),r}var ba=ya,ka=new Int8Array([-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,90,63,64,65,66,-1,67,68,69,70,71,-1,72,73,52,53,54,55,56,57,58,59,60,61,74,75,76,77,78,79,80,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,81,-1,82,83,84,85,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,86,87,88,89,-1]);function Ea(e){let r=new Uint8Array(170930),a=0,o=0,s=0,c=-1;for(let u=0,g=e.length;u127)continue;let m=ka[y];if(!(m<0))if(c<0)c=m;else{c+=m*91,o|=c<88?13:14;do r[a++]=o,o>>>=8,s-=8;while(s>7);c=-1}}return c>=0&&(r[a++]=(o|c<""})}function Ua(){Ct=void 0}var xa=class{_module;constructor(e){this._module=e}malloc(e){let r=this._module._malloc(e);return{ptr:r,size:e,dispose:()=>this.free({ptr:r,size:e,dispose:()=>{}})}}free(e){this._module._free(e.ptr)}dataToHeap(e){let r=this.malloc(e.byteLength);return this._module.HEAPU8.set(e,r.ptr),r}heapView(e){return this._module.HEAPU8.subarray(e.ptr,e.ptr+e.size)}heapToUint8Array(e){return new Uint8Array([...this.heapView(e)])}lengthBytes(e){return this._module.lengthBytesUTF8(e)}stringToHeap(e){let r=this.lengthBytes(e)+1,a=this._module._malloc(r);return this._module.stringToUTF8(e,a,r),{ptr:a,size:r,dispose:()=>this.free({ptr:a,size:r,dispose:()=>{}})}}heapToString(e){return this._module.UTF8ToString(e.ptr,e.size)}hasFilesystem(){let e=this._module;return e.FS_createPath!==void 0&&e.FS_createDataFile!==void 0&&e.FS_preloadFile!==void 0&&e.FS_unlink!==void 0}createPath(e,r=!0,a=!0){return this._module.FS_createPath("/",e,r,a)}createDataFile(e,r,a=!0,o=!0,s=!0){return this._module.FS_createDataFile("/",e,r,a,o,s)}preloadFile(e,r,a=!0,o=!0,s=!1,c=!0,u=!1){return this._module.FS_preloadFile("/",e,r,a,o,s,c,u)}unlink(e){return this._module.FS_unlink(e)}},Le,Ma=4294967295;function Fe(e,r){if(!Number.isInteger(e)||e<0||e>Ma)throw new Error(`${r} length ${e} is outside the WASM32-safe range`)}function vt(e){if(e.length===0)return new Uint8Array(0);if(e.length===1)return e[0];let r=0;for(let s of e)r+=s.length;let a=new Uint8Array(r),o=0;for(let s of e)a.set(s,o),o+=s.length;return a}var Ta=class br{_mainModule;_zstdClass;_zstd;_compressionLevel;_decompressFrameCompleted=!1;_decompressRemaining=0;_decompressSawInput=!1;constructor(r){this._mainModule=new xa(r),this._zstdClass=r.zstd,this._zstd=new this._zstdClass,this._compressionLevel=this._zstdClass.defaultCLevel()}static load(){return Le||(Le=$a().then(r=>new br(r))),Le}static unload(){Ua(),Le?.then(r=>{r?._zstd?.delete()}),Le=void 0}copyHeap(r){return this._mainModule.heapView(r).slice()}mallocChecked(r,a){Fe(r,a);let o=this._mainModule.malloc(r);if(!o.ptr)throw this._mainModule.free(o),new Error(`Failed to allocate ${r} bytes for ${a}`);return o}throwStreamError(r,a){throw new Error(`${r} failed: ${a.errorName||"unknown error"}`)}ensureStreamOk(r,a){a.error&&this.throwStreamError(r,a)}version(){return this._zstdClass.version()}reset(){let r=this._zstd.reset();this.ensureStreamOk("reset",r),this._decompressFrameCompleted=!1,this._decompressRemaining=0,this._decompressSawInput=!1}resetCompression(r){r!==void 0&&(this._compressionLevel=r);let a=this._zstd.resetCompression(this._compressionLevel);this.ensureStreamOk("resetCompression",a)}resetDecompression(){let r=this._zstd.resetDecompression();this.ensureStreamOk("resetDecompression",r),this._decompressFrameCompleted=!1,this._decompressRemaining=0,this._decompressSawInput=!1}setCompressionLevel(r){let a=this._zstd.setCompressionLevel(r);this.ensureStreamOk("setCompressionLevel",a),this._compressionLevel=r}compress(r,a=this.defaultCLevel()){Fe(r.length,"compress input");let o=this._mainModule.dataToHeap(r);try{if(!o.ptr&&r.length>0)throw new Error("Failed to allocate compress input buffer");let s=this._zstdClass.compressBound(r.length),c=this.mallocChecked(s,"compress output");try{if(c.size=this._zstdClass.compress(c.ptr,s,o.ptr,o.size,a),this._zstdClass.isError(c.size))throw new Error(`compress failed: ${this._zstdClass.getErrorName(c.size)}`);return this.copyHeap(c)}finally{this._mainModule.free(c)}}finally{this._mainModule.free(o)}}compressChunk(r){if(Fe(r.length,"compressChunk input"),r.length===0)return new Uint8Array(0);let a=this._mainModule.dataToHeap(r);try{if(!a.ptr)throw new Error("Failed to allocate compressChunk input buffer");let o=this._zstdClass.CStreamOutSize(),s=[],c=0;for(;c0&&(u.size=g.produced,s.push(this.copyHeap(u)))}finally{this._mainModule.free(u)}}return vt(s)}finally{this._mainModule.free(a)}}compressEnd(){let r=this._zstdClass.CStreamOutSize(),a=[],o=1;for(;o!==0;){let s=this.mallocChecked(r,"compressEnd output");try{let c=this._zstd.compressEnd(s.ptr,r);if(this.ensureStreamOk("compressEnd",c),c.produced===0&&c.remaining!==0)throw new Error("compressEnd failed: no progress while finishing stream");o=c.remaining,c.produced>0&&(s.size=c.produced,a.push(this.copyHeap(s)))}finally{this._mainModule.free(s)}}return vt(a)}frameContentSize(r){return this._zstdClass.getFrameContentSize(r.ptr,r.size)}decompress(r){Fe(r.length,"decompress input");let a=this._mainModule.dataToHeap(r);try{if(!a.ptr&&r.length>0)throw new Error("Failed to allocate decompress input buffer");let o=this.frameContentSize(a);if(o.error)throw new Error(`Failed to get frame content size: ${o.errorName}`);let s=o.known;if(s){let c=this._zstdClass.findFrameCompressedSize(a.ptr,a.size);if(this._zstdClass.isError(c))throw new Error(`Failed to find frame compressed size: ${this._zstdClass.getErrorName(c)}`);c0?a.ptr+c:0,b=this._zstd.decompressChunk(g.ptr,o,m,y);if(this.ensureStreamOk("decompressChunk",b),b.consumed===0&&b.produced===0){if(y===0){b.remaining>0&&(this._decompressRemaining=b.remaining);break}throw new Error("decompressChunk failed: no progress while consuming input")}c+=b.consumed,u=b.produced===o&&b.remaining!==0,b.produced>0&&(g.size=b.produced,s.push(this.copyHeap(g))),b.remaining===0?(this._decompressFrameCompleted=!0,this._decompressRemaining=0):this._decompressRemaining=b.remaining}finally{this._mainModule.free(g)}}return vt(s)}finally{this._mainModule.free(a)}}decompressEnd(){if(!this._decompressSawInput)throw new Error("decompressEnd failed: empty compressed input is not a completed frame");if(!this._decompressFrameCompleted||this._decompressRemaining!==0)throw new Error("decompressEnd failed: truncated Zstandard input")}defaultCLevel(){return this._zstdClass.defaultCLevel()}minCLevel(){return this._zstdClass.minCLevel()}maxCLevel(){return this._zstdClass.maxCLevel()}};let yt=null;function Na(){return yt||(yt=Ta.load()),yt}function Sa(e){return e.length>=4&&e[0]===40&&e[1]===181&&e[2]===47&&e[3]===253}function $t(e){const r="SQLite format 3\0";if(e.length/g,">").replace(/"/g,""").replace(/'/g,"'")}function or(e,r,a){const o=e.tagsList[0]??"",s=e.tagsList.length>1?` +${e.tagsList.length-1}`:"",c=Ee(e.tagsList.join(", ")),u=o?`${Ee(o)}${s}`:"",g=e.more.size>0?'':"";return`
`+(a?kr(e,r):"")+"
"}function kr(e,r){const a=[...e.more.entries()].map(([o,s])=>{const c=r.get(o);return`
${c?Ee(c.name):`Поле #${o}`}
${Ee(s)}
`}).join("");return a?`
${a}
`:""}function qa(e,r){e.innerHTML=` + +
+

📚 Слова

+
+ + +
+

+
База ещё не проверена.
+
+
+
+ `;const a=e.querySelector("#home-db-banner"),o=e.querySelector("#home-db-text"),s=e.querySelector("#home-db-go"),c=e.querySelector("#words-search"),u=e.querySelector("#words-group"),g=e.querySelector("#words-count"),y=e.querySelector("#words-status"),m=e.querySelector("#words-list"),b=e.querySelector("#words-more");function T(v,x){a.hidden=!1,o.textContent=v,s.hidden=!1}function k(){a.hidden=!0}s.addEventListener("click",()=>{document.querySelector('.drawer-btn[data-page="sync"]')?.click()});let U=!1,N=new Map,w=0,E=[],I=null,J=0,L=!1,z=0,P=0;function C(){const v=u.value;if(v==="")return{search:c.value.trim(),groupId:null};const x=Number(v);return{search:c.value.trim(),groupId:Number.isFinite(x)?x:null}}function _(){b.hidden=!(U&&E.lengthor(v,N,v.id===I)).join(""),g.textContent=w>0?`Показано ${E.length} из ${w}`:"",_()}function F(v){if(I===v){v!=null&&X(v),I=null;return}I!=null&&X(I),I=v,v!=null&&j(v)}function X(v){const x=m.querySelector(`.word-row-wrap[data-word-id="${v}"]`);if(!x)return;const M=x.querySelector(".word-row"),Q=x.querySelector(".word-extra");M&&(M.classList.remove("open"),M.setAttribute("aria-expanded","false")),Q&&(Q.hidden=!0,Q.innerHTML="")}function j(v){const x=E.find(he=>he.id===v),M=m.querySelector(`.word-row-wrap[data-word-id="${v}"]`);if(!x||!M)return;const Q=M.querySelector(".word-row"),O=M.querySelector(".word-extra");Q&&(Q.classList.add("open"),Q.setAttribute("aria-expanded","true")),O&&(O.hidden=!1,O.innerHTML=kr(x,N))}function V(v){y.textContent=v,y.hidden=v===""}async function B(){if(!U||L)return;L=!0;const v=++z,x=C();b.disabled=!0,E.length===0&&V("Загрузка слов…");try{const[M,Q]=await Promise.all([Xn({search:x.search,groupId:x.groupId??void 0}),tr({search:x.search,groupId:x.groupId??void 0,limit:ir,offset:0})]);if(v!==z)return;w=M,E=Q,J=Q.length,I!=null&&!E.some(O=>O.id===I)&&(I=null),R(),V(Q.length===0?"По этому запросу слов нет.":""),Q.length===0&&w===0&&(g.textContent="Слов в базе пока нет.")}catch(M){if(v!==z)return;V(`Не удалось загрузить слова: ${M instanceof Error?M.message:String(M)}`)}finally{v===z&&(L=!1,b.disabled=!1,_())}}async function H(){if(!U||L||E.length>=w)return;L=!0;const v=++z,x=C();b.disabled=!0,b.textContent="Загрузка…";try{const M=await tr({search:x.search,groupId:x.groupId??void 0,limit:ir,offset:J});if(v!==z)return;E=E.concat(M),J+=M.length,m.insertAdjacentHTML("beforeend",M.map(Q=>or(Q,N,Q.id===I)).join("")),g.textContent=`Показано ${E.length} из ${w}`,_()}catch(M){if(v!==z)return;V(`Не удалось догрузить: ${M instanceof Error?M.message:String(M)}`)}finally{v===z&&(L=!1,b.disabled=!1,_())}}c.addEventListener("input",()=>{window.clearTimeout(P),P=window.setTimeout(()=>{B()},300)}),c.addEventListener("keydown",v=>{v.key==="Enter"&&(window.clearTimeout(P),B())}),u.addEventListener("change",()=>{B()}),b.addEventListener("click",()=>{H()}),m.addEventListener("click",v=>{const x=v.target.closest(".word-row");if(!x||x.disabled)return;const M=Number(x.dataset.wordId);Number.isFinite(M)&&F(I===M?null:M)}),(async()=>{if(!await Je()){T("Локальной базы нет — скачай сохранение."),V("Нет локальной базы — скачай сохранение на странице «Синхронизация»."),g.textContent="";return}try{const x=await ra();if(!$t(x)){T("Файл в OPFS не похож на SQLite — перекачай сохранение.",!0),V("Файл в OPFS не похож на SQLite — перекачай сохранение.");return}k();try{const[Q,O]=await Promise.all([zn(),vr()]),he=Q;N=new Map(O.map(ue=>[ue.id,ue])),u.innerHTML=''+he.map(ue=>``).join(""),U=!0,await B(),u.options[0].textContent=`Все группы (${w})`}catch(Q){V(`БД есть, но слова не прочитались: ${Q instanceof Error?Q.message:String(Q)}`)}}catch(x){T(`Не удалось прочитать базу: ${x instanceof Error?x.message:String(x)}`)}})()}function cr(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function La(e){const r=e%10,a=e%100;return r===1&&a!==11?"запись":r>=2&&r<=4&&(a<12||a>14)?"записи":"записей"}function Fa(e,r){const a=cr(e.name)||`Колода #${e.id}`;return`
`}function _a(e,r){e.innerHTML=` +
+

🗂️ Колоды

+

+
Проверка локальной базы…
+
+
+ `;const a=e.querySelector("#decks-count"),o=e.querySelector("#decks-status"),s=e.querySelector("#decks-list");function c(u){o.textContent=u,o.hidden=u===""}s.addEventListener("click",u=>{const g=u.target.closest("[data-act]");if(!g)return;const y=Number(g.dataset.deckId);Number.isFinite(y)&&(g.dataset.act==="review"?r.goReview(y):r.goDeck(y))}),(async()=>{if(!await Je()){c("Нет локальной базы — скачай сохранение на странице «Синхронизация»."),a.textContent="";return}c("Загрузка колод…");try{const[g,y]=await Promise.all([Bn(),Yn()]);if(g.length===0){a.textContent="Колоды пока отсутствуют.",c(""),s.innerHTML="";return}a.textContent=`Всего: ${g.length}`,c(""),s.innerHTML=g.map(m=>Fa(m,y.get(m.id)??0)).join("")}catch(g){c(`Не удалось загрузить колоды: ${g instanceof Error?g.message:String(g)}`)}})()}function Qa(e){return e?Pa(e):'—'}function Pa(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function bt(e){return e.replace(/&/g,"&").replace(/"/g,""").replace(//g,">")}function Ra(e){const r=e%10,a=e%100;return r===1&&a!==11?"слово":r>=2&&r<=4&&(a<12||a>14)?"слова":"слов"}function Ia(e,r,a){e.innerHTML=` + +
+

Колода…

+
Загрузка колоды…
+
+
+ `;const o=e.querySelector("#deck-back"),s=e.querySelector("#deck-title"),c=e.querySelector("#deck-status"),u=e.querySelector("#deck-body");o.addEventListener("click",()=>a.goDecks()),(async()=>{if(!await Je()){s.textContent="Колода недоступна",c.textContent="Нет локальной базы — скачай сохранение на странице «Синхронизация».";return}let y=null;try{y=await nt(r)}catch(C){s.textContent="Колода недоступна",c.textContent=`Не удалось загрузить колоду: ${C instanceof Error?C.message:String(C)}`;return}if(!y){s.textContent="Колода не найдена",c.textContent=`Колоды с id=${r} нет в локальной базе. Возможно, база обновилась — вернись к списку.`;return}const m=y;s.textContent=m.name||`Колода #${m.id}`,c.hidden=!0;let b=null;try{b=await Zn(m.id)}catch(C){c.hidden=!1,c.textContent=`Данные колоды загружены, а количество слов — нет: ${C instanceof Error?C.message:String(C)}`}u.innerHTML=` +
+
id
${m.id}
+
name
${Qa(m.name)}
+
+
+ + + + + + +
+ +
+

+
+

📚 ${b==null?"Количество слов неизвестно":`${b} ${Ra(b)}`}

+
+ +
+
+ +
+ `;const T=u.querySelector("#deck-form"),k=u.querySelector("#deck-forward"),U=u.querySelector("#deck-backward"),N=u.querySelector("#deck-filter"),w=u.querySelector("#deck-save"),E=u.querySelector("#deck-form-status"),I=u.querySelector("#deck-review"),J=u.querySelector("#deck-history");I.addEventListener("click",()=>a.goReview(m.id)),J.addEventListener("click",()=>a.goHistory(m.id));const L=()=>`${k.value} +${U.value} +${N.value}`;let z=L();function P(){w.disabled=L()===z||w.dataset.busy==="1"}for(const C of[k,U,N])C.addEventListener("input",P);P(),T.addEventListener("submit",C=>{C.preventDefault(),!w.disabled&&(w.dataset.busy="1",w.disabled=!0,w.textContent="Сохранение…",E.textContent="",(async()=>{try{const _=await Vn(m.id,{forward:k.value,backward:U.value,filter:N.value});if(!_){E.textContent="Колода пропала из базы — вернись к списку.";return}k.value=_.forward,U.value=_.backward,N.value=_.filter,z=L(),E.textContent="✅ Сохранено в локальную базу."}catch(_){E.textContent=`Не удалось сохранить: ${_ instanceof Error?_.message:String(_)}`}finally{delete w.dataset.busy,w.textContent="💾 Сохранить",P()}})())})})()}function _e(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}const Ja=50,Oa=[{label:"Не получилось",cls:"grade-fail"},{label:"Тяжело",cls:"grade-hard"},{label:"Хорошо",cls:"grade-good"},{label:"Легко",cls:"grade-easy"}];function Ka(e){const r=Oa[e.answer]??{label:`Оценка ${e.answer}`,cls:""},a=e.wordKey?`${_e(e.wordKey)}`+(e.wordValue?` — ${_e(e.wordValue)}`:""):`Слово #${e.word_id} (удалено)`,o=e.before===e.after?`${e.before}`:`${e.before}→${e.after}`;return`
${a}
${_e(r.label)}${_e(On(e.timestamp))} · score ${_e(o)}
`}function Da(e,r,a){e.innerHTML=` + +
+

История…

+

+
Загрузка истории…
+
+
+
+ `;const o=e.querySelector("#hist-back"),s=e.querySelector("#hist-title"),c=e.querySelector("#hist-count"),u=e.querySelector("#hist-status"),g=e.querySelector("#hist-list"),y=e.querySelector("#hist-more");o.addEventListener("click",()=>a.goDeck(r));function m(w){u.textContent=w,u.hidden=w===""}let b=0,T=0,k=!1;function U(){const w=b-T;y.hidden=w<=0,w>0&&(y.textContent=`Показать ещё (${w} осталось)`)}async function N(){if(!(k||T>=b)){k=!0,y.disabled=!0,y.textContent="Загрузка…";try{const w=await ea(r,{limit:Ja,offset:T});T+=w.length,g.insertAdjacentHTML("beforeend",w.map(Ka).join("")),c.textContent=`Показано ${T} из ${b}`}catch(w){m(`Не удалось загрузить историю: ${w instanceof Error?w.message:String(w)}`)}finally{k=!1,y.disabled=!1,U()}}}y.addEventListener("click",()=>{N()}),(async()=>{if(!await Je()){s.textContent="История недоступна",m("Нет локальной базы — скачай сохранение на странице «Синхронизация».");return}try{const E=await nt(r);if(!E){s.textContent="Колода не найдена",m(`Колоды с id=${r} нет в локальной базе.`);return}if(s.textContent=`🕘 ${E.name||`Колода #${E.id}`}`,b=await An(E.id),b===0){c.textContent="Ответов пока нет — начни повторение.",m("");return}m(""),await N()}catch(E){s.textContent="История недоступна",m(`Не удалось загрузить историю: ${E instanceof Error?E.message:String(E)}`)}})()}function Xa(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}const za=[{value:0,label:"😞 Не получилось",cls:"grade-fail"},{value:1,label:"😟 Тяжело",cls:"grade-hard"},{value:2,label:"🙂 Хорошо",cls:"grade-good"},{value:3,label:"🤩 Легко",cls:"grade-easy"}];function Ba(e,r,a){e.innerHTML=` + +
+

Повторение…

+

+
Загрузка карточек…
+
+
+ `;const o=e.querySelector("#review-back"),s=e.querySelector("#review-title"),c=e.querySelector("#review-progress"),u=e.querySelector("#review-status"),g=e.querySelector("#review-body");o.addEventListener("click",()=>a.goDecks());function y(R){u.textContent=R,u.hidden=R===""}let m=[],b=new Map,T=[],k="",U="",N=0,w=!1,E=null,I=0,J=!1;function L(R,F){return R.length===0?`
—
`:`
`+R.map(X=>`

${Xa(X)}

`).join("")+"
"}async function z(){const R=++I,F=m[N],X=b.get(F);if(!X){g.innerHTML="",c.textContent="",y("Слово пропало из базы — пропускаю.");return}E=null;let j="";try{E=await jn(r,F)??1}catch{E=1,j=" (score прочитать не удалось — пишу before/after=1)"}if(R!==I)return;J=!1;const V=Gn(X,T),B=nr(k,V),H=w?nr(U,V):null;c.textContent=`Карточка ${N+1} из ${m.length}`,y("");const v=w?L(H??[],"review-back-side"):'

Тап — показать ответ

',x=w?`
+ ${za.map(M=>``).join("")} +
`:"";g.innerHTML=` + + ${x} + `,g.querySelector("#review-card").addEventListener("click",()=>{w||(w=!0,z())}),g.querySelectorAll(".review-grade").forEach(M=>{M.addEventListener("click",()=>{const Q=Number(M.dataset.grade);P(Q,j)})})}async function P(R,F){if(J)return;J=!0;const X=m[N],j=E??1,V=[...g.querySelectorAll(".review-grade")];for(const B of V)B.disabled=!0;try{await Wn({deckId:r,wordId:X,answer:R,before:j,after:j})}catch(B){y(`Ответ не записался: ${B instanceof Error?B.message:String(B)}${F}`),J=!1;for(const H of V)H.disabled=!1;return}N+=1,w=!1,E=null,N>=m.length?C():await z()}function C(){c.textContent=`Готово: ${m.length} из ${m.length}`,y(""),g.innerHTML=` +

Все карточки сессии просмотрены, ответы записаны в history.

+
+ +
+ `,g.querySelector("#review-again").addEventListener("click",()=>{_()})}async function _(){if(y("Перемешиваю…"),g.innerHTML="",c.textContent="",m=await ar(r),N=0,w=!1,m.length===0){y("В колоде нет слов для повторения.");return}b=await rr(m),z()}(async()=>{if(!await Je()){s.textContent="Повторение недоступно",y("Нет локальной базы — скачай сохранение на странице «Синхронизация».");return}try{const[F,X]=await Promise.all([nt(r),vr()]);if(!F){s.textContent="Колода не найдена",y(`Колоды с id=${r} нет в локальной базе. Вернись к списку.`);return}if(T=X,k=F.forward,U=F.backward,s.textContent=F.name||`Колода #${F.id}`,m=await ar(F.id),m.length===0){c.textContent="",y("В колоде нет слов для повторения.");return}b=await rr(m),N=0,w=!1,await z()}catch(F){s.textContent="Повторение недоступно",y(`Не удалось загрузить карточки: ${F instanceof Error?F.message:String(F)}`)}})()}const lr=24,Va=/^[A-Za-z0-9]{24}$/,Er="japlearn_sync_token";function ur(e){return e.trim().replace(/\s+/g,"")}function Qe(e){return Va.test(e)}function Ya(){try{return localStorage.getItem(Er)??""}catch{return""}}function dr(e){try{localStorage.setItem(Er,e)}catch{}}const Cr="https://learning.micialware.ru";function Za(e,r=Cr){return`${r.replace(/\/+$/,"")}/download/${encodeURIComponent(e)}`}async function Ga(e,r,a=Cr){const o=Za(e,a);let s;try{s=await fetch(o,{method:"GET",...r})}catch(u){throw new Error(`Нет связи с сервером: ${u instanceof Error?u.message:String(u)}`)}if(!s.ok)throw s.status===404?new Error("Сохранение не найдено (404). Проверь токен."):new Error(`Сервер ответил ${s.status} ${s.statusText}`);const c=await s.arrayBuffer();if(c.byteLength===0)throw new Error("Сервер вернул пустой файл");return new Uint8Array(c)}function ja(e){e.innerHTML=` +
+

📡 Синхронизация

+

Токен сохранения — 24 символа: латинские буквы и цифры.

+ + + +
+ + 0 / 24 +
+ +
+ +
+ +

Введите токен из 24 символов

+
+ `;const r=e.querySelector("#token-input"),a=e.querySelector("#token-error"),o=e.querySelector("#token-counter"),s=e.querySelector("#btn-download"),c=e.querySelector("#status");function u(k){c.textContent=k,console.log(`[JapLearn] ${k}`)}function g(){return ur(r.value)}function y(k){s.disabled=k||!Qe(g()),r.disabled=k}function m(){const k=g(),U=k.length;o.textContent=`${U} / ${lr}`;const N=Qe(k),w=U===0;r.classList.toggle("invalid",!w&&!N),r.setAttribute("aria-invalid",String(!w&&!N)),w?a.textContent="":U!==lr?a.textContent=`Нужно 24 символа, сейчас ${U}`:a.textContent=N?"":"Только латинские буквы и цифры",s.disabled=!N}async function b(){const k=g();if(Qe(k)){dr(k),y(!0),u("Скачивание сохранения...");try{const U=await Ga(k);u(`Скачано ${(U.length/1024).toFixed(1)} КБ, распаковка...`);const N=await Ha(U);u(`Распаковано ${(N.length/1024).toFixed(1)} КБ, сохранение в OPFS...`),await ta(N),u(`Готово: база ${(N.length/1024).toFixed(1)} КБ сохранена локально (OPFS).`)}catch(U){u(`Ошибка скачивания: ${U instanceof Error?U.message:String(U)}`)}finally{y(!1),m()}}}r.addEventListener("input",()=>{m()}),r.addEventListener("change",()=>{const k=g();r.value=k,Qe(k)&&(dr(k),u("Токен сохранён локально. Можно скачивать.")),m()}),s.addEventListener("click",()=>{b()});const T=Ya();T&&(r.value=T),m(),Qe(ur(T))&&u("Токен восстановлен. Нажми «Скачать», чтобы получить базу.")}const Wa={home:"Главная",decks:"Колоды",deck:"Колода",review:"Повторение",history:"История",sync:"Синхронизация"};function Ke(e){const r=document.getElementById(e);if(!r)throw new Error(`[JapLearn] Не найден элемент #${e}`);return r}const $r=Ke("burger-btn"),rt=Ke("drawer"),Ur=Ke("drawer-overlay"),Aa=Ke("page-title"),Ue=Ke("page");let Mt=!1;function De(e){Mt=e,rt.classList.toggle("open",e),Ur.hidden=!e,$r.setAttribute("aria-expanded",String(e)),rt.setAttribute("aria-hidden",String(!e)),document.body.classList.toggle("drawer-open",e)}function es(e){rt.querySelectorAll(".drawer-btn").forEach(r=>{r.classList.toggle("active",r.dataset.page===e)})}function me(e,r){Aa.textContent=Wa[e],es(e==="deck"||e==="review"||e==="history"?"decks":e),e==="home"?qa(Ue):e==="decks"?_a(Ue,{goDeck:a=>me("deck",a),goReview:a=>me("review",a)}):e==="deck"?Ia(Ue,r??0,{goDecks:()=>me("decks"),goReview:a=>me("review",a),goHistory:a=>me("history",a)}):e==="review"?Ba(Ue,r??0,{goDecks:()=>me("decks")}):e==="history"?Da(Ue,r??0,{goDeck:a=>me("deck",a)}):ja(Ue)}$r.addEventListener("click",()=>{De(!Mt)});Ur.addEventListener("click",()=>{De(!1)});document.addEventListener("keydown",e=>{e.key==="Escape"&&Mt&&De(!1)});rt.querySelectorAll(".drawer-btn").forEach(e=>{e.addEventListener("click",()=>{const r=e.dataset.page;(r==="home"||r==="decks"||r==="sync")&&me(r),De(!1)})});await Rn();De(!1);me("home");console.log("[JapLearn] crossOriginIsolated =",window.crossOriginIsolated); diff --git a/dist/index.html b/dist/index.html index 6423296..d0a6558 100644 --- a/dist/index.html +++ b/dist/index.html @@ -5,8 +5,8 @@ JapLearn — интервальные повторения - - + +
diff --git a/src/db-types.ts b/src/db-types.ts index f7c5c4e..cddc619 100644 --- a/src/db-types.ts +++ b/src/db-types.ts @@ -8,6 +8,7 @@ // - states.fsrs_memory: сырое INTEGER-значение FSRS (не дата, не трогаем). import { command, query } from "./sqlite-api.ts"; +import { nowSec } from "./time.ts"; export interface CustomField { id: number; @@ -355,7 +356,8 @@ export async function sampleDeckWordIds( // Тасование Фишера–Йетса: равномерная перестановка за O(n). for (let i = ids.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); - [ids[i], ids[j]] = [ids[i]!, ids[j]!]; + // NB: в RHS индексы перевёрнуты — иначе присваивание [i],[j] = [i],[j] ничего не меняет. + [ids[i], ids[j]] = [ids[j]!, ids[i]!]; } if (limit != null && limit >= 0) return ids.slice(0, limit); return ids; @@ -371,6 +373,106 @@ export async function getCustomFields(): Promise { } +/** + * Текущий score слова: свежий states по паре (deck_id, word_id). + * + * Берём один — самый новый (ORDER BY id DESC LIMIT 1): именно он отражает + * последнее известное состояние до открытия карточки. Нет строки — null + * (слово в очередь попало из выборки, а states успели удалить). + */ +export async function getLatestStateScore( + deckId: number, + wordId: number, +): Promise { + const rows = await query( + "SELECT score FROM states WHERE deck_id = ? AND word_id = ? ORDER BY id DESC LIMIT 1", + [deckId, wordId] as never[], + ); + const r = (rows as Record[])[0]; + if (!r || r["score"] == null) return null; + const n = Number(r["score"]); + return Number.isFinite(n) ? n : null; +} + +/** Оценка повторения: 0 = "не получилось", 3 = "легко". */ +export type ReviewGrade = 0 | 1 | 2 | 3; + +/** + * Записывает ответ в history. + * + * - timestamp: unix-секунды (nowSec из time.ts, тот же формат что history.timestamp); + * - fsrs: всегда 0 (false) — FSRS-шедулер подключим позже; + * - before/after: score слова. Логики пересчёта пока нет, поэтому оба равны + * score на момент открытия карточки (передаётся снаружи — см. review-page). + * + * Ничего не возвращает: id вставленной строки не нужен для UI. + */ +export async function recordReviewAnswer(args: { + deckId: number; + wordId: number; + answer: ReviewGrade; + before: number; + after: number; +}): Promise { + await command( + "INSERT INTO history (deck_id, word_id, timestamp, answer, before, after, fsrs) VALUES (?, ?, ?, ?, ?, ?, 0)", + [args.deckId, args.wordId, nowSec(), args.answer, args.before, args.after] as never[], + ); +} + +export interface DeckHistoryFilter { + limit?: number; + offset?: number; +} + +/** Сколько всего записей history у колоды (для счётчика/пагинации). */ +export async function countDeckHistory(deckId: number): Promise { + const rows = await query("SELECT COUNT(*) as cnt FROM history WHERE deck_id = ?", [ + deckId, + ] as never[]); + const cnt = (rows[0] as Record | undefined)?.["cnt"]; + return typeof cnt === "number" ? cnt : Number(cnt ?? 0); +} + +/** Запись истории с подписью слова (LEFT JOIN — слово могли удалить). */ +export interface DeckHistoryItem extends HistoryEntry { + /** key слова или '' если слово удалено. */ + wordKey: string; + /** value слова или '' если слово удалено. */ + wordValue: string; +} + +/** + * История ответов колоды: свежие первыми, пагинация limit/offset. + * Подтягивает key/value слова одним JOIN — без N+1. + */ +export async function getDeckHistory( + deckId: number, + filter: DeckHistoryFilter = {}, +): Promise { + const limit = Math.max(1, Math.min(filter.limit ?? 50, 500)); + const offset = Math.max(0, filter.offset ?? 0); + const rows = await query( + `SELECT h.id, h.deck_id, h.word_id, h.timestamp, h.answer, h.before, h.after, h.fsrs, + w.key as wkey, w.value as wvalue + FROM history h LEFT JOIN words w ON w.id = h.word_id + WHERE h.deck_id = ? ORDER BY h.timestamp DESC, h.id DESC LIMIT ? OFFSET ?`, + [deckId, limit, offset] as never[], + ); + return (rows as Record[]).map((r) => ({ + id: Number(r["id"]), + deck_id: Number(r["deck_id"]), + word_id: Number(r["word_id"]), + timestamp: Number(r["timestamp"] ?? 0), + answer: Number(r["answer"] ?? 0), + before: Number(r["before"] ?? 0), + after: Number(r["after"] ?? 0), + fsrs: Number(r["fsrs"] ?? 0), + wordKey: r["wkey"] == null ? "" : String(r["wkey"]), + wordValue: r["wvalue"] == null ? "" : String(r["wvalue"]), + })); +} + export interface HistoryEntry { id: number; deck_id: number; diff --git a/src/deck-page.ts b/src/deck-page.ts index a90580a..7d68aa8 100644 --- a/src/deck-page.ts +++ b/src/deck-page.ts @@ -1,4 +1,4 @@ -// Страница одной колоды: данные строки decks + количество связанных слов. +// Страница одной колоды: данные строки decks + количество связанных слов + ссылки. // forward / backward / filter — редактируемые, сохраняются в локальную БД. // Никакого списка states здесь нет. Навигация: «← Назад к колодам» в список. @@ -8,6 +8,7 @@ import { hasDatabaseFile } from './opfs.ts'; export type DeckNav = { goDecks: () => void; goReview: (deckId: number) => void; + goHistory: (deckId: number) => void; }; function fmtOrDash(s: string): string { @@ -110,6 +111,9 @@ export function renderDeckPage(root: HTMLElement, deckId: number, nav: DeckNav):
+
+ +
`; const form = bodyEl.querySelector('#deck-form')!; @@ -119,8 +123,10 @@ export function renderDeckPage(root: HTMLElement, deckId: number, nav: DeckNav): const saveBtn = bodyEl.querySelector('#deck-save')!; const formStatus = bodyEl.querySelector('#deck-form-status')!; const reviewBtn = bodyEl.querySelector('#deck-review')!; + const historyBtn = bodyEl.querySelector('#deck-history')!; reviewBtn.addEventListener('click', () => nav.goReview(d.id)); + historyBtn.addEventListener('click', () => nav.goHistory(d.id)); // Кнопка активна только при отличии от сохранённого. const snapshot = (): string => diff --git a/src/history-page.ts b/src/history-page.ts new file mode 100644 index 0000000..330ae49 --- /dev/null +++ b/src/history-page.ts @@ -0,0 +1,131 @@ +// Страница истории ответов колоды: плоский список записей history. +// Свежие первыми, пагинация «Показать ещё». Навигация: назад на страницу колоды. + +import { countDeckHistory, getDeck, getDeckHistory } from './db-types.ts'; +import type { DeckHistoryItem } from './db-types.ts'; +import { hasDatabaseFile } from './opfs.ts'; +import { formatDbDateTime } from './time.ts'; + +export type HistoryNav = { + goDeck: (deckId: number) => void; +}; + +function escapeHtml(s: string): string { + return s + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +const HIST_PAGE = 50; + +// answer 0..3 -> подпись + класс (те же цвета, что кнопки повторения). +const GRADE_META: ReadonlyArray<{ label: string; cls: string }> = [ + { label: 'Не получилось', cls: 'grade-fail' }, + { label: 'Тяжело', cls: 'grade-hard' }, + { label: 'Хорошо', cls: 'grade-good' }, + { label: 'Легко', cls: 'grade-easy' }, +]; + +function histRowHtml(h: DeckHistoryItem): string { + const g = GRADE_META[h.answer] ?? { label: `Оценка ${h.answer}`, cls: '' }; + const word = h.wordKey + ? `${escapeHtml(h.wordKey)}` + + (h.wordValue ? ` — ${escapeHtml(h.wordValue)}` : '') + : `Слово #${h.word_id} (удалено)`; + const shift = h.before === h.after ? `${h.before}` : `${h.before}→${h.after}`; + return `
` + + `
${word}
` + + `
${escapeHtml(g.label)}` + + `${escapeHtml(formatDbDateTime(h.timestamp))} · score ${escapeHtml(shift)}
` + + `
`; +} + +export function renderHistoryPage(root: HTMLElement, deckId: number, nav: HistoryNav): void { + root.innerHTML = ` + +
+

История…

+

+
Загрузка истории…
+
+
+
+ `; + + const backBtn = root.querySelector('#hist-back')!; + const titleEl = root.querySelector('#hist-title')!; + const countEl = root.querySelector('#hist-count')!; + const statusEl = root.querySelector('#hist-status')!; + const listEl = root.querySelector('#hist-list')!; + const moreBtn = root.querySelector('#hist-more')!; + + backBtn.addEventListener('click', () => nav.goDeck(deckId)); + + function setStatus(msg: string): void { + statusEl.textContent = msg; + statusEl.hidden = msg === ''; + } + + let total = 0; + let offset = 0; + let loading = false; + + function refreshMore(): void { + const left = total - offset; + moreBtn.hidden = left <= 0; + if (left > 0) moreBtn.textContent = `Показать ещё (${left} осталось)`; + } + + async function loadPage(): Promise { + if (loading || offset >= total) return; + loading = true; + moreBtn.disabled = true; + moreBtn.textContent = 'Загрузка…'; + try { + const items = await getDeckHistory(deckId, { limit: HIST_PAGE, offset }); + offset += items.length; + listEl.insertAdjacentHTML('beforeend', items.map(histRowHtml).join('')); + countEl.textContent = `Показано ${offset} из ${total}`; + } catch (err) { + setStatus(`Не удалось загрузить историю: ${err instanceof Error ? err.message : String(err)}`); + } finally { + loading = false; + moreBtn.disabled = false; + refreshMore(); + } + } + + moreBtn.addEventListener('click', () => void loadPage()); + + void (async () => { + const has = await hasDatabaseFile(); + if (!has) { + titleEl.textContent = 'История недоступна'; + setStatus('Нет локальной базы — скачай сохранение на странице «Синхронизация».'); + return; + } + try { + const deck = await getDeck(deckId); + if (!deck) { + titleEl.textContent = 'Колода не найдена'; + setStatus(`Колоды с id=${deckId} нет в локальной базе.`); + return; + } + titleEl.textContent = `🕘 ${deck.name || `Колода #${deck.id}`}`; + total = await countDeckHistory(deck.id); + if (total === 0) { + countEl.textContent = 'Ответов пока нет — начни повторение.'; + setStatus(''); + return; + } + setStatus(''); + await loadPage(); + } catch (err) { + titleEl.textContent = 'История недоступна'; + setStatus(`Не удалось загрузить историю: ${err instanceof Error ? err.message : String(err)}`); + } + })(); +} diff --git a/src/home-page.ts b/src/home-page.ts index 318c102..730a27c 100644 --- a/src/home-page.ts +++ b/src/home-page.ts @@ -10,7 +10,7 @@ export type HomeNav = { goSync?: () => void; }; -const PAGE_SIZE = 30; +const PAGE_SIZE = 100; function escapeHtml(s: string): string { return s diff --git a/src/main.ts b/src/main.ts index 68a02ff..ed61c27 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,19 +2,21 @@ import './style.css'; import { renderHomePage } from './home-page.ts'; import { renderDecksPage } from './decks-page.ts'; import { renderDeckPage } from './deck-page.ts'; +import { renderHistoryPage } from './history-page.ts'; import { renderReviewPage } from './review-page.ts'; import { renderSyncPage } from './sync-page.ts'; import { initWorker } from './sqlite-api.ts'; -// Каркас приложения: топ-бар + бургер-меню + страницы (Главная / Колоды / Колода / Повторение / Синхронизация). +// Каркас приложения: топ-бар + бургер-меню + страницы (Главная / Колоды / Колода / Повторение / История / Синхронизация). -type Page = 'home' | 'decks' | 'deck' | 'review' | 'sync'; +type Page = 'home' | 'decks' | 'deck' | 'review' | 'history' | 'sync'; const PAGE_TITLES: Record = { home: 'Главная', decks: 'Колоды', deck: 'Колода', review: 'Повторение', + history: 'История', sync: 'Синхронизация', }; @@ -49,8 +51,8 @@ function setActiveButton(page: Page): void { function renderPage(page: Page, deckId?: number): void { pageTitle.textContent = PAGE_TITLES[page]; - // Страницы колоды и повторения — подразделы «Колод»: в меню подсвечиваем «Колоды». - setActiveButton(page === 'deck' || page === 'review' ? 'decks' : page); + // Колода / повторение / история — подразделы «Колод»: в меню подсвечиваем «Колоды». + setActiveButton(page === 'deck' || page === 'review' || page === 'history' ? 'decks' : page); if (page === 'home') { renderHomePage(pageRoot, {}); } else if (page === 'decks') { @@ -62,9 +64,12 @@ function renderPage(page: Page, deckId?: number): void { renderDeckPage(pageRoot, deckId ?? 0, { goDecks: () => renderPage('decks'), goReview: (id) => renderPage('review', id), + goHistory: (id) => renderPage('history', id), }); } else if (page === 'review') { renderReviewPage(pageRoot, deckId ?? 0, { goDecks: () => renderPage('decks') }); + } else if (page === 'history') { + renderHistoryPage(pageRoot, deckId ?? 0, { goDeck: (id) => renderPage('deck', id) }); } else { renderSyncPage(pageRoot); } diff --git a/src/review-page.ts b/src/review-page.ts index 18ca2ea..3395c34 100644 --- a/src/review-page.ts +++ b/src/review-page.ts @@ -1,17 +1,19 @@ // Страница повторения колоды: карточка за карточкой по случайной выборке. // Лицевая сторона — поля из deck.forward, по тапу дорисовывается deck.backward. // Внизу 4 кнопки самооценки: "не получилось" / "тяжело" / "хорошо" / "легко". -// Пока без записи ответов: только навигация по очереди (история — следующий шаг). +// Ответ пишется в history при нажатии оценки: before/after = score до открытия. import { getCustomFields, getDeck, + getLatestStateScore, getWordsByIds, + recordReviewAnswer, renderCardSide, sampleDeckWordIds, wordFieldValues, } from './db-types.ts'; -import type { CustomField, Word } from './db-types.ts'; +import type { CustomField, ReviewGrade, Word } from './db-types.ts'; import { hasDatabaseFile } from './opfs.ts'; export type ReviewNav = { @@ -27,12 +29,13 @@ function escapeHtml(s: string): string { .replace(/'/g, '''); } -const GRADES = [ - { label: '😞 Не получилось', cls: 'grade-fail' }, - { label: '😟 Тяжело', cls: 'grade-hard' }, - { label: '🙂 Хорошо', cls: 'grade-good' }, - { label: '🤩 Легко', cls: 'grade-easy' }, -] as const; +// Порядок = значение answer в history: 0 — "не получилось", 3 — "легко". +const GRADES: ReadonlyArray<{ value: ReviewGrade; label: string; cls: string }> = [ + { value: 0, label: '😞 Не получилось', cls: 'grade-fail' }, + { value: 1, label: '😟 Тяжело', cls: 'grade-hard' }, + { value: 2, label: '🙂 Хорошо', cls: 'grade-good' }, + { value: 3, label: '🤩 Легко', cls: 'grade-easy' }, +]; export function renderReviewPage(root: HTMLElement, deckId: number, nav: ReviewNav): void { root.innerHTML = ` @@ -66,6 +69,11 @@ export function renderReviewPage(root: HTMLElement, deckId: number, nav: ReviewN let backwardTpl = ''; let pos = 0; let revealed = false; + // score слова на момент открытия карточки (до ответа). null = ещё грузится/нет states. + let beforeScore: number | null = null; + // Защита от гонок: paint асинхронный (ждёт score), клики не должны мешать. + let paintSeq = 0; + let answering = false; function sideHtml(values: string[], cls: string): string { if (values.length === 0) { @@ -77,14 +85,28 @@ export function renderReviewPage(root: HTMLElement, deckId: number, nav: ReviewN } - function paint(): void { - const w = words.get(queue[pos]!); + async function paint(): Promise { + const mySeq = ++paintSeq; + const wordId = queue[pos]!; + const w = words.get(wordId); if (!w) { bodyEl.innerHTML = ''; progressEl.textContent = ''; setStatus('Слово пропало из базы — пропускаю.'); return; } + // score ДО открытия: свежий states по (deck_id, word_id). Нет строки — считаем 1 + // (дефолт схемы states.score DEFAULT 1). Ошибка чтения — тоже 1, но с пометкой. + beforeScore = null; + let scoreNote = ''; + try { + beforeScore = (await getLatestStateScore(deckId, wordId)) ?? 1; + } catch { + beforeScore = 1; + scoreNote = ' (score прочитать не удалось — пишу before/after=1)'; + } + if (mySeq !== paintSeq) return; // пока ждали score, очередь уже ушла дальше + answering = false; const values = wordFieldValues(w, fields); const front = renderCardSide(forwardTpl, values); const back = revealed ? renderCardSide(backwardTpl, values) : null; @@ -96,7 +118,7 @@ export function renderReviewPage(root: HTMLElement, deckId: number, nav: ReviewN // Оценки — только после открытия (вместе с backward), до этого лишь тап по карточке. const gradesHtml = revealed ? `
- ${GRADES.map((g) => ``).join('')} + ${GRADES.map((g) => ``).join('')}
` : ''; bodyEl.innerHTML = ` @@ -111,25 +133,46 @@ export function renderReviewPage(root: HTMLElement, deckId: number, nav: ReviewN bodyEl.querySelector('#review-card')!.addEventListener('click', () => { if (!revealed) { revealed = true; - paint(); + void paint(); } }); bodyEl.querySelectorAll('.review-grade').forEach((btn) => { btn.addEventListener('click', () => { - // Запись ответов (history/states) — следующим шагом, пока просто дальше. - pos += 1; - revealed = false; - if (pos >= queue.length) paintDone(); - else paint(); + const grade = Number(btn.dataset.grade) as ReviewGrade; + void answer(grade, scoreNote); }); }); } + // Нажатие оценки: пишем строку в history и идём дальше. before/after — score + // до открытия (пересчёт по ответу добавим позже, пока оба равны beforeScore). + async function answer(grade: ReviewGrade, scoreNote: string): Promise { + if (answering) return; + answering = true; + const wordId = queue[pos]!; + const before = beforeScore ?? 1; + const btns = [...bodyEl.querySelectorAll('.review-grade')]; + for (const b of btns) b.disabled = true; + try { + await recordReviewAnswer({ deckId, wordId, answer: grade, before, after: before }); + } catch (err) { + setStatus(`Ответ не записался: ${err instanceof Error ? err.message : String(err)}${scoreNote}`); + answering = false; + for (const b of btns) b.disabled = false; + return; + } + pos += 1; + revealed = false; + beforeScore = null; + if (pos >= queue.length) paintDone(); + else await paint(); + } + function paintDone(): void { progressEl.textContent = `Готово: ${queue.length} из ${queue.length}`; setStatus(''); bodyEl.innerHTML = ` -

Все карточки сессии просмотрены. Запись оценок — следующим шагом.

+

Все карточки сессии просмотрены, ответы записаны в history.

@@ -151,7 +194,7 @@ export function renderReviewPage(root: HTMLElement, deckId: number, nav: ReviewN return; } words = await getWordsByIds(queue); - paint(); + void paint(); } void (async () => { @@ -182,7 +225,7 @@ export function renderReviewPage(root: HTMLElement, deckId: number, nav: ReviewN words = await getWordsByIds(queue); pos = 0; revealed = false; - paint(); + await paint(); } catch (err) { titleEl.textContent = 'Повторение недоступно'; setStatus(`Не удалось загрузить карточки: ${err instanceof Error ? err.message : String(err)}`); diff --git a/src/style.css b/src/style.css index fc90bf0..4af0ce0 100644 --- a/src/style.css +++ b/src/style.css @@ -565,6 +565,43 @@ button.word-row-wrap:not(:disabled):hover { border-color: #7c3aed; } +/* --- История ответов колоды: плоские строки, цветы оценок --- */ + +.hist-row-wrap { + padding: 9px 4px; + border-bottom: 1px solid #eee9fb; +} + +.hist-row-wrap:last-child { + border-bottom: none; +} + +.hist-main { + overflow-wrap: break-word; +} + +.hist-meta { + display: flex; + align-items: baseline; + gap: 8px; + margin-top: 4px; +} + +.hist-grade { + flex: none; + padding: 2px 10px; + border-radius: 999px; + font-size: 0.8rem; + font-weight: 700; +} + +.hist-grade.grade-fail, +.hist-grade.grade-hard, +.hist-grade.grade-good, +.hist-grade.grade-easy { + border: none; +} + /* --- Повторение: карточка + 4 кнопки оценок --- */ #review-body {