1// legacy-migration.js 2// Ð¡Ð¾Ñ ÑанноÑÑÑ Ð´Ð°Ð½Ð½ÑÑ ÑÑÑеÑÑвÑÑÑÐ¸Ñ Ð³Ð¾ÑÑей ÑÑаÑой ÑиÑÑемÑ. 3// Ðанал A: избÑанное (favorites/{sid}) â живÑе ÑекÑÑÑ Ð³Ð¾ÑÑÑ (guestStore). 4// Ðанал B: иÑÑоÑÐ¸Ñ (texts/{sid}) â ÑкаÑиваемÑй .txt-аÑÑ Ð¸Ð² («ÐÐ¾Ñ Ð²Ð°Ñи ÑекÑÑÑ»). 5// РеалÑнÑе легаÑи-даннÑе еÑÑÑ ÑолÑко на origin пÑода (memorizun.com); на беÑе 6// ÑеÑÑиÑÑеÑÑÑ Ð½Ð° заÑеÑннÑÑ Ñейк-даннÑÑ . См. docs/superpowers/specs/2026-06-04-*. 7// 8// ЧиÑÑÑе помоÑники ÑкÑпоÑÑиÑÑÑÑÑÑ Ð´Ð»Ñ Node-ÑеÑÑов; бÑаÑзеÑÐ½Ð°Ñ Ð¾Ð±Ð²Ñзка â под 9// `typeof window` (паÑÑеÑн как в userState.js). 10 11const LEGACY_MIN_LEN = 30; // коÑоÑе â обÑезок/мÑÑÐ¾Ñ 12const LEGACY_MAX_CHARS = 50000; // длиннее â вÑбÑÐ¾Ñ (бÑл ÑекÑÑ Ð½Ð° 1.66 млн) 13const LEGACY_NORM_PREFIX = 300; // длина клÑÑа дедÑпа по ÑодеÑÐ¶Ð¸Ð¼Ð¾Ð¼Ñ 14 15function legacyNormalizeKey(content) { 16 return String(content == null ? '' : content).trim().slice(0, LEGACY_NORM_PREFIX); 17} 18 19function legacyIsAcceptable(content) { 20 const c = String(content == null ? '' : content).trim(); 21 return c.length >= LEGACY_MIN_LEN && c.length <= LEGACY_MAX_CHARS; 22} 23 24function legacySavedAtToMs(savedAt) { 25 if (typeof savedAt === 'number' && isFinite(savedAt)) return savedAt; 26 if (typeof savedAt === 'string') { 27 const t = Date.parse(savedAt); 28 if (!isNaN(t)) return t; 29 } 30 return null; 31} 32 33function legacyRecordsFromNode(node) { 34 if (!node || typeof node !== 'object' || Array.isArray(node)) return []; 35 return Object.values(node).filter(r => r && typeof r === 'object'); 36} 37 38// Ðлан меÑжа избÑанного в живÑе ÑекÑÑÑ. 39// favNode: RTDB-нода favorites/{sid}; existingTexts: ÑекÑÑÑ guestStore (Ñ .text). 40// â маÑÑив {text, createdAt} к ÑÐ¾Ñ ÑанениÑ: дедÑп пÑоÑив existing и внÑÑÑи, 41// ÑоÑÑиÑовка по возÑаÑÑÐ°Ð½Ð¸Ñ savedAt (без даÑÑ â в конеÑ). 42function legacyPlanFavoritesMerge(favNode, existingTexts) { 43 const seen = new Set(); 44 (existingTexts || []).forEach(t => { 45 const c = t && (t.text != null ? t.text : t.content); 46 if (c) seen.add(legacyNormalizeKey(c)); 47 }); 48 const recs = legacyRecordsFromNode(favNode) 49 .map(r => ({ content: r.content != null ? r.content : r.text, savedAt: r.savedAt })) 50 .filter(r => legacyIsAcceptable(r.content)); 51 recs.sort((a, b) => { 52 const ta = legacySavedAtToMs(a.savedAt), tb = legacySavedAtToMs(b.savedAt); 53 return (ta == null ? Infinity : ta) - (tb == null ? Infinity : tb); 54 }); 55 const out = []; 56 for (const r of recs) { 57 const key = legacyNormalizeKey(r.content); 58 if (seen.has(key)) continue; 59 seen.add(key); 60 out.push({ text: String(r.content).trim(), createdAt: legacySavedAtToMs(r.savedAt) }); 61 } 62 return out; 63} 64 65// ÐÐ¼Ñ Ñайла Ð´Ð»Ñ Ð¾Ð´Ð½Ð¾Ð³Ð¾ ÑекÑÑа в ZIP: "NN-<пеÑÐ²Ð°Ñ ÑÑÑока, оÑиÑеннаÑ>.txt". 66function legacyExportFilename(content, index) { 67 const firstLine = String(content == null ? '' : content).trim().split('\n')[0] || ''; 68 const base = firstLine.slice(0, 40) 69 .replace(/[\/\\:*?"<>|]+/g, ' ') // запÑеÑÑннÑе в имени Ñайла â пÑобел 70 .replace(/\s+/g, ' ') 71 .trim(); 72 const num = String(index + 1).padStart(2, '0'); 73 return `${num}-${base || 'text'}.txt`; 74} 75 76// Ðлан ÑкÑпоÑÑа «вÑÐµÑ ÑекÑÑов» в оÑделÑнÑе ÑайлÑ. 77// current: ÑекÑÑие ÑекÑÑÑ (Ñ .text/.content) â ÑеалÑнÑе ÑекÑÑÑ ÑзеÑа, оÑÑев ÑолÑко пÑÑÑÑÑ . 78// textsNode/favNode: легаÑи-Ð½Ð¾Ð´Ñ RTDB â оÑÑев <30/>50000, ÑоÑÑиÑовка по ÑбÑÐ²Ð°Ð½Ð¸Ñ savedAt. 79// ÐедÑп: current пÑиоÑиÑеÑнее, легаÑи добавлÑеÑÑÑ Ð±ÐµÐ· дÑблей. â [{filename, content}]. 80function legacyPlanExportFiles(current, textsNode, favNode) { 81 const seen = new Set(); 82 const contents = [];
83 (current || []).forEach(t => { 84 const raw = t && (t.text != null ? t.text : t.content); 85 const s = String(raw == null ? '' : raw).trim(); 86 if (!s) return; 87 const key = legacyNormalizeKey(s); 88 if (seen.has(key)) return; 89 seen.add(key); 90 contents.push(s); 91 }); 92 const legacy = legacyRecordsFromNode(textsNode) 93 .concat(legacyRecordsFromNode(favNode)) 94 .map(r => ({ content: r.content != null ? r.content : r.text, savedAt: r.savedAt })) 95 .filter(r => legacyIsAcceptable(r.content)); 96 legacy.sort((a, b) => { 97 const ta = legacySavedAtToMs(a.savedAt), tb = legacySavedAtToMs(b.savedAt); 98 return (tb == null ? -Infinity : tb) - (ta == null ? -Infinity : ta); 99 }); 100 legacy.forEach(r => { 101 const s = String(r.content).trim(); 102 const key = legacyNormalizeKey(s); 103 if (seen.has(key)) return; 104 seen.add(key); 105 contents.push(s); 106 }); 107 return contents.map((content, i) => ({ filename: legacyExportFilename(content, i), content })); 108} 109 110const legacyMigrationApi = { 111 LEGACY_MIN_LEN, LEGACY_MAX_CHARS, LEGACY_NORM_PREFIX, 112 legacyNormalizeKey, legacyIsAcceptable, legacySavedAtToMs, legacyRecordsFromNode, 113 legacyPlanFavoritesMerge, legacyExportFilename, legacyPlanExportFiles, 114}; 115 116if (typeof module !== 'undefined' && module.exports) { 117 module.exports = legacyMigrationApi; 118} 119 120// ===== ÐÑаÑзеÑÐ½Ð°Ñ Ð¾Ð±Ð²Ñзка ===== 121if (typeof window !== 'undefined') { 122 const FLAG_CHECKED = 'memorizun_legacy_checked'; // канал A (пеÑÐµÐ½Ð¾Ñ Ð¸Ð·Ð±Ñанного) оÑÑабоÑал 123 124 async function readNode(path) { 125 if (typeof firebase === 'undefined' || !firebase.database) return null; 126 const snap = await firebase.database().ref(path).once('value'); 127 return snap.val(); 128 } 129 130 // Ðанал A: избÑанное (favorites/{sid}) â живÑе ÑекÑÑÑ Ð³Ð¾ÑÑÑ. ÐдноÑазово (FLAG_CHECKED). 131 async function runOnce() { 132 try { 133 if (localStorage.getItem(FLAG_CHECKED)) return; 134 const sid = localStorage.getItem('memorizunSessionId'); 135 if (!sid) { localStorage.setItem(FLAG_CHECKED, '1'); return; } 136 const favNode = await readNode('favorites/' + sid); 137 const existing = window.guestStore ? window.guestStore.loadTexts() : []; 138 const toSave = legacyPlanFavoritesMerge(favNode, existing); 139 toSave.forEach(rec => window.guestStore && window.guestStore.saveText(rec)); 140 if (toSave.length) console.log('ð¥ ÐегаÑи избÑанное â ÑекÑÑÑ:', toSave.length); 141 localStorage.setItem(FLAG_CHECKED, '1'); 142 } catch (e) { 143 console.warn('â ï¸ ÐеÑÐµÐ½Ð¾Ñ Ð»ÐµÐ³Ð°Ñи-избÑанного не ÑдалÑÑ (не кÑиÑиÑно):', (e && e.message) || e); 144 localStorage.setItem(FLAG_CHECKED, '1'); 145 } 146 } 147 148 // ÐоÑÑоÑÐ½Ð½Ð°Ñ ÐºÐ½Ð¾Ð¿ÐºÐ°: «СкаÑаÑÑ Ð²Ñе мои ÑекÑÑÑ» â ZIP из оÑделÑнÑÑ .txt. 149 // ТекÑÑие ÑекÑÑÑ (лÑбой пÑовайдеÑ) + легаÑи по sessionId. ÐÐ»Ñ Ð²ÑÐµÑ ÑзеÑов. 150 async function downloadAllTexts() { 151 try { 152 const current = (window.historySidebar && window.historySidebar.texts) || []; 153 const sid = localStorage.getItem('memorizunSessionId'); 154 let favNode = null, textsNode = null; 155 if (sid) { 156 [favNode, textsNode] = await Promise.all([ 157 readNode('favorites/' + sid), 158 readNode('texts/' + sid), 159 ]); 160 } 161 const files = legacyPlanExportFiles(current, textsNode, favNode); 162 if (!files.length) { 163 console.log('â¹ï¸ ÐÐµÑ ÑекÑÑов Ð´Ð»Ñ ÑкаÑиваниÑ'); 164 return; 165 } 166 if (typeof JSZip === 'undefined') { 167 console.warn('â ï¸ JSZip не загÑÑжен â ÑкаÑивание недоÑÑÑпно'); 168 return; 169 } 170 const zip = new JSZip(); 171 files.forEach(f => zip.file(f.filename, f.content)); 172 const blob = await zip.generateAsync({ type: 'blob' }); 173 const url = URL.createObjectURL(blob); 174 const a = document.createElement('a'); 175 a.href = url; 176 a.download = 'memorizun-texts.zip'; 177 document.body.appendChild(a); 178 a.click(); 179 document.body.removeChild(a); 180 URL.revokeObjectURL(url); 181 console.log('ð¾ СкаÑано ÑекÑÑов:', files.length); 182 if (window.track) window.track('download_all_texts', { count: files.length }); 183 } catch (e) { 184 console.warn('â ï¸ Ð¡ÐºÐ°Ñивание не ÑдалоÑÑ:', (e && e.message) || e); 185 } 186 } 187 188 window.legacyMigration = { runOnce, downloadAllTexts }; 189 console.log('â legacyMigration иниÑиализиÑован'); 190}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.