refactor(hardcover): move note mappings to SQLite on web and native (#4083)
Hardcover's note → journal-id mapping store was split-brained: SQLite on
Tauri, localStorage on Web. Unify on SQLite for both. Existing localStorage
entries are migrated lazily on first loadForBook(bookHash) and removed.
Wiring up SQLite on Web exposed two latent gaps:
- @tursodatabase/database-wasm needs SharedArrayBuffer, which requires
cross-origin isolation. next.config.mjs now sets COOP/COEP headers.
- The WASM connector calls getFileHandle(name) directly under
navigator.storage.getDirectory() and does not traverse subdirectories,
so paths like "Readest/hardcover-sync.db" raise "Name is not allowed".
webAppService.openDatabase now flattens the resolved path to a single
OPFS-safe segment before opening.
Also catches up two i18n keys (`{{percentage}}% used`,
`Resets in {{duration}}`) added by the daily-reset countdown feature.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,13 @@ const nextConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
source: '/(.*)',
|
||||
headers: [
|
||||
{ key: 'Cross-Origin-Opener-Policy', value: 'same-origin' },
|
||||
{ key: 'Cross-Origin-Embedder-Policy', value: 'require-corp' },
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1434,5 +1434,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "تم حذف النسخة السحابية للملف: {{title}}",
|
||||
"Failed to upload file: {{title}}": "فشل تحميل الملف: {{title}}",
|
||||
"Failed to download file: {{title}}": "فشل تنزيل الملف: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "فشل في حذف النسخة السحابية للملف: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "فشل في حذف النسخة السحابية للملف: {{title}}",
|
||||
"{{percentage}}% used": "تم استخدام {{percentage}}٪",
|
||||
"Resets in {{duration}}": "يُعاد التعيين خلال {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1338,5 +1338,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "ফাইলের ক্লাউড অনুলিপি মুছে ফেলা হয়েছে: {{title}}",
|
||||
"Failed to upload file: {{title}}": "ফাইল আপলোড ব্যর্থ: {{title}}",
|
||||
"Failed to download file: {{title}}": "ফাইল ডাউনলোড ব্যর্থ: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "ফাইলের ক্লাউড অনুলিপি মুছতে ব্যর্থ: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "ফাইলের ক্লাউড অনুলিপি মুছতে ব্যর্থ: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% ব্যবহৃত",
|
||||
"Resets in {{duration}}": "{{duration}} এ রিসেট হবে"
|
||||
}
|
||||
|
||||
@@ -1314,5 +1314,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "ཡིག་ཆའི་སྤྲི་དོན་གྱི་འདྲ་བཤུས་བསུབ་ཟིན། {{title}}",
|
||||
"Failed to upload file: {{title}}": "ཡིག་ཆ་སྤྲད་མ་ཐུབ། {{title}}",
|
||||
"Failed to download file: {{title}}": "ཡིག་ཆ་ཕབ་ལེན་བྱས་མ་ཐུབ། {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "ཡིག་ཆའི་སྤྲི་དོན་གྱི་འདྲ་བཤུས་བསུབ་ཐུབ་མེད། {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "ཡིག་ཆའི་སྤྲི་དོན་གྱི་འདྲ་བཤུས་བསུབ་ཐུབ་མེད། {{title}}",
|
||||
"{{percentage}}% used": "སྤྱོད་ཟིན་ {{percentage}}%",
|
||||
"Resets in {{duration}}": "{{duration}} ནང་སླར་གསོ་བྱེད།"
|
||||
}
|
||||
|
||||
@@ -1338,5 +1338,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Cloud-Kopie der Datei gelöscht: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Fehler beim Hochladen der Datei: {{title}}",
|
||||
"Failed to download file: {{title}}": "Fehler beim Herunterladen der Datei: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Fehler beim Löschen der Cloud-Kopie der Datei: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Fehler beim Löschen der Cloud-Kopie der Datei: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% verbraucht",
|
||||
"Resets in {{duration}}": "Setzt sich in {{duration}} zurück"
|
||||
}
|
||||
|
||||
@@ -1338,5 +1338,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Διαγράφηκε το αντίγραφο του αρχείου: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Αποτυχία ανέβασμα αρχείου: {{title}}",
|
||||
"Failed to download file: {{title}}": "Αποτυχία λήψης αρχείου: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Αποτυχία διαγραφής του αντιγράφου του αρχείου: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Αποτυχία διαγραφής του αντιγράφου του αρχείου: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% σε χρήση",
|
||||
"Resets in {{duration}}": "Επαναφορά σε {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1362,5 +1362,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Copia en la nube del archivo eliminada: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Error al subir archivo: {{title}}",
|
||||
"Failed to download file: {{title}}": "Error al descargar archivo: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Error al eliminar la copia en la nube del archivo: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Error al eliminar la copia en la nube del archivo: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% usado",
|
||||
"Resets in {{duration}}": "Se restablece en {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1338,5 +1338,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "نسخهی ابری فایل «{{title}}» حذف شد.",
|
||||
"Failed to upload file: {{title}}": "آپلود فایل «{{title}}» ناموفق بود.",
|
||||
"Failed to download file: {{title}}": "دانلود فایل «{{title}}» ناموفق بود.",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "حذف نسخهی ابری فایل «{{title}}» ناموفق بود."
|
||||
"Failed to delete cloud copy of the file: {{title}}": "حذف نسخهی ابری فایل «{{title}}» ناموفق بود.",
|
||||
"{{percentage}}% used": "{{percentage}}٪ استفاده شده",
|
||||
"Resets in {{duration}}": "بازنشانی در {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1362,5 +1362,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Suppression de la copie cloud du fichier : {{title}}",
|
||||
"Failed to upload file: {{title}}": "Échec du téléchargement du fichier : {{title}}",
|
||||
"Failed to download file: {{title}}": "Échec du téléchargement du fichier : {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Échec de la suppression de la copie cloud du fichier : {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Échec de la suppression de la copie cloud du fichier : {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}} % utilisés",
|
||||
"Resets in {{duration}}": "Réinitialisation dans {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1362,5 +1362,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "עותק הענן של הקובץ נמחק: {{title}}",
|
||||
"Failed to upload file: {{title}}": "העלאת הקובץ נכשלה: {{title}}",
|
||||
"Failed to download file: {{title}}": "הורדת הקובץ נכשלה: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "מחיקת עותק הענן של הקובץ נכשלה: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "מחיקת עותק הענן של הקובץ נכשלה: {{title}}",
|
||||
"{{percentage}}% used": "נוצלו {{percentage}}%",
|
||||
"Resets in {{duration}}": "מתאפס בעוד {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1338,5 +1338,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "फ़ाइल की क्लाउड प्रति हटाई गई: {{title}}",
|
||||
"Failed to upload file: {{title}}": "फ़ाइल अपलोड करने में विफल: {{title}}",
|
||||
"Failed to download file: {{title}}": "फ़ाइल डाउनलोड करने में विफल: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "फ़ाइल की क्लाउड प्रति हटाने में विफल: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "फ़ाइल की क्लाउड प्रति हटाने में विफल: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% उपयोग किया गया",
|
||||
"Resets in {{duration}}": "{{duration}} में रीसेट होगा"
|
||||
}
|
||||
|
||||
@@ -1338,5 +1338,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Fájl felhőmásolata törölve: {{title}}",
|
||||
"Failed to upload file: {{title}}": "A fájl feltöltése sikertelen: {{title}}",
|
||||
"Failed to download file: {{title}}": "Fájl letöltése sikertelen: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Fájl felhőmásolatának törlése sikertelen: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Fájl felhőmásolatának törlése sikertelen: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% felhasználva",
|
||||
"Resets in {{duration}}": "Visszaáll {{duration}} múlva"
|
||||
}
|
||||
|
||||
@@ -1314,5 +1314,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Salinan cloud file dihapus: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Gagal mengunggah file: {{title}}",
|
||||
"Failed to download file: {{title}}": "Gagal mengunduh file: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Gagal menghapus salinan cloud file: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Gagal menghapus salinan cloud file: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% terpakai",
|
||||
"Resets in {{duration}}": "Disetel ulang dalam {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1362,5 +1362,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Copia cloud del file eliminata: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Caricamento file non riuscito: {{title}}",
|
||||
"Failed to download file: {{title}}": "Download file non riuscito: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Impossibile eliminare la copia cloud del file: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Impossibile eliminare la copia cloud del file: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% utilizzato",
|
||||
"Resets in {{duration}}": "Si reimposta tra {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1314,5 +1314,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "ファイルのクラウドコピーを削除しました:{{title}}",
|
||||
"Failed to upload file: {{title}}": "ファイルのアップロードに失敗しました:{{title}}",
|
||||
"Failed to download file: {{title}}": "ファイルのダウンロードに失敗しました:{{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "ファイルのクラウドコピーの削除に失敗しました:{{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "ファイルのクラウドコピーの削除に失敗しました:{{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% 使用済み",
|
||||
"Resets in {{duration}}": "{{duration}}でリセット"
|
||||
}
|
||||
|
||||
@@ -1314,5 +1314,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "파일의 클라우드 사본이 삭제됨: {{title}}",
|
||||
"Failed to upload file: {{title}}": "파일 업로드 실패: {{title}}",
|
||||
"Failed to download file: {{title}}": "파일 다운로드 실패: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "파일의 클라우드 사본 삭제에 실패했습니다: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "파일의 클라우드 사본 삭제에 실패했습니다: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% 사용됨",
|
||||
"Resets in {{duration}}": "{{duration}} 후 재설정"
|
||||
}
|
||||
|
||||
@@ -1314,5 +1314,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Salinan awan fail dipadam: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Gagal memuat naik fail: {{title}}",
|
||||
"Failed to download file: {{title}}": "Gagal memuat turun fail: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Gagal memadam salinan awan fail: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Gagal memadam salinan awan fail: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% telah digunakan",
|
||||
"Resets in {{duration}}": "Set semula dalam {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1338,5 +1338,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Verwijderde cloudkopie van het bestand: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Uploaden van bestand mislukt: {{title}}",
|
||||
"Failed to download file: {{title}}": "Downloaden van bestand mislukt: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Verwijderen van cloudkopie van het bestand is mislukt: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Verwijderen van cloudkopie van het bestand is mislukt: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% gebruikt",
|
||||
"Resets in {{duration}}": "Reset over {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1386,5 +1386,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Usunięto kopię pliku w chmurze: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Nie udało się przesłać pliku: {{title}}",
|
||||
"Failed to download file: {{title}}": "Nie udało się pobrać pliku: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Nie udało się usunąć kopii pliku w chmurze: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Nie udało się usunąć kopii pliku w chmurze: {{title}}",
|
||||
"{{percentage}}% used": "Wykorzystano {{percentage}}%",
|
||||
"Resets in {{duration}}": "Resetuje się za {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1362,5 +1362,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Cópia na nuvem do arquivo excluída: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Falha ao enviar arquivo: {{title}}",
|
||||
"Failed to download file: {{title}}": "Falha ao baixar arquivo: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Falha ao excluir cópia na nuvem do arquivo: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Falha ao excluir cópia na nuvem do arquivo: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% utilizado",
|
||||
"Resets in {{duration}}": "Redefine em {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1362,5 +1362,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Cópia na nuvem do arquivo excluída: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Falha ao enviar arquivo: {{title}}",
|
||||
"Failed to download file: {{title}}": "Falha ao baixar arquivo: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Falha ao excluir cópia na nuvem do arquivo: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Falha ao excluir cópia na nuvem do arquivo: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% utilizado",
|
||||
"Resets in {{duration}}": "Repõe em {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1362,5 +1362,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Copia din cloud a fișierului a fost ștearsă: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Nu s-a putut încărca fișierul: {{title}}",
|
||||
"Failed to download file: {{title}}": "Nu s-a putut descărca fișierul: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Nu s-a putut șterge copia din cloud a fișierului: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Nu s-a putut șterge copia din cloud a fișierului: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% utilizat",
|
||||
"Resets in {{duration}}": "Se resetează în {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1386,5 +1386,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Удалена облачная копия файла: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Не удалось загрузить файл: {{title}}",
|
||||
"Failed to download file: {{title}}": "Не удалось скачать файл: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Не удалось удалить копию файла в облаке: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Не удалось удалить копию файла в облаке: {{title}}",
|
||||
"{{percentage}}% used": "Использовано {{percentage}}%",
|
||||
"Resets in {{duration}}": "Сброс через {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1338,5 +1338,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "ගොනුවේ cloud පිටපත මකා දමන ලදී: {{title}}",
|
||||
"Failed to upload file: {{title}}": "ගොනුව උඩුගත කිරීමට අසමත්: {{title}}",
|
||||
"Failed to download file: {{title}}": "ගොනුව බාගැනීමට අසමත්: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "ගොනුවේ cloud පිටපත මකා දැමීමට අසමත්: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "ගොනුවේ cloud පිටපත මකා දැමීමට අසමත්: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% භාවිතයි",
|
||||
"Resets in {{duration}}": "{{duration}}කින් යළි පිහිටුවයි"
|
||||
}
|
||||
|
||||
@@ -1386,5 +1386,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Izbrisana kopija datoteke v oblaku: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Nalaganje datoteke ni uspelo: {{title}}",
|
||||
"Failed to download file: {{title}}": "Prenos datoteke ni uspel: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Brisanje kopije datoteke v oblaku ni uspelo: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Brisanje kopije datoteke v oblaku ni uspelo: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}} % uporabljeno",
|
||||
"Resets in {{duration}}": "Ponastavi se čez {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1338,5 +1338,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Raderade molnkopia av fil: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Kunde inte ladda upp fil: {{title}}",
|
||||
"Failed to download file: {{title}}": "Kunde inte ladda ner fil: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Kunde inte radera molnkopia av fil: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Kunde inte radera molnkopia av fil: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}} % använt",
|
||||
"Resets in {{duration}}": "Återställs om {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1338,5 +1338,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "கோப்பின் cloud நகல் நீக்கப்பட்டது: {{title}}",
|
||||
"Failed to upload file: {{title}}": "கோப்பை பதிவேற்ற முடியவில்லை: {{title}}",
|
||||
"Failed to download file: {{title}}": "கோப்பை பதிவிறக்க முடியவில்லை: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "கோப்பின் cloud நகலை நீக்க முடியவில்லை: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "கோப்பின் cloud நகலை நீக்க முடியவில்லை: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% பயன்படுத்தப்பட்டது",
|
||||
"Resets in {{duration}}": "{{duration}} இல் மீட்டமைக்கப்படும்"
|
||||
}
|
||||
|
||||
@@ -1314,5 +1314,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "ลบสำเนาไฟล์บนคลาวด์แล้ว: {{title}}",
|
||||
"Failed to upload file: {{title}}": "ไม่สามารถอัปโหลดไฟล์: {{title}}",
|
||||
"Failed to download file: {{title}}": "ไม่สามารถดาวน์โหลดไฟล์: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "ไม่สามารถลบสำเนาไฟล์บนคลาวด์: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "ไม่สามารถลบสำเนาไฟล์บนคลาวด์: {{title}}",
|
||||
"{{percentage}}% used": "ใช้ไปแล้ว {{percentage}}%",
|
||||
"Resets in {{duration}}": "รีเซ็ตใน {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1338,5 +1338,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Dosyanın bulut kopyası silindi: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Dosya yüklenemedi: {{title}}",
|
||||
"Failed to download file: {{title}}": "Dosya indirilemedi: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Dosyanın bulut kopyasını silme başarısız oldu: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Dosyanın bulut kopyasını silme başarısız oldu: {{title}}",
|
||||
"{{percentage}}% used": "%{{percentage}} kullanıldı",
|
||||
"Resets in {{duration}}": "{{duration}} sonra sıfırlanır"
|
||||
}
|
||||
|
||||
@@ -1386,5 +1386,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Видалено хмарну копію файлу: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Не вдалося завантажити файл: {{title}}",
|
||||
"Failed to download file: {{title}}": "Не вдалося завантажити файл: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Не вдалося видалити копію файлу в хмарі: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Не вдалося видалити копію файлу в хмарі: {{title}}",
|
||||
"{{percentage}}% used": "Використано {{percentage}}%",
|
||||
"Resets in {{duration}}": "Скидання через {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1338,5 +1338,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Faylning bulutdagi nusxasi oʻchirildi: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Faylni yuklab boʻlmadi: {{title}}",
|
||||
"Failed to download file: {{title}}": "Faylni yuklab boʻlmadi: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Faylning bulutdagi nusxasini oʻchirib boʻlmadi: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Faylning bulutdagi nusxasini oʻchirib boʻlmadi: {{title}}",
|
||||
"{{percentage}}% used": "{{percentage}}% ishlatilgan",
|
||||
"Resets in {{duration}}": "{{duration}} dan keyin tiklanadi"
|
||||
}
|
||||
|
||||
@@ -1314,5 +1314,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "Đã xóa bản sao đám mây của tệp: {{title}}",
|
||||
"Failed to upload file: {{title}}": "Không thể tải lên tệp: {{title}}",
|
||||
"Failed to download file: {{title}}": "Không thể tải về tệp: {{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Không thể xóa bản sao đám mây của tệp: {{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "Không thể xóa bản sao đám mây của tệp: {{title}}",
|
||||
"{{percentage}}% used": "Đã dùng {{percentage}}%",
|
||||
"Resets in {{duration}}": "Đặt lại sau {{duration}}"
|
||||
}
|
||||
|
||||
@@ -1314,5 +1314,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "已删除文件云副本:{{title}}",
|
||||
"Failed to upload file: {{title}}": "上传文件失败:{{title}}",
|
||||
"Failed to download file: {{title}}": "下载文件失败:{{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "未能删除文件云副本:{{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "未能删除文件云副本:{{title}}",
|
||||
"{{percentage}}% used": "已使用 {{percentage}}%",
|
||||
"Resets in {{duration}}": "{{duration}}后重置"
|
||||
}
|
||||
|
||||
@@ -1314,5 +1314,7 @@
|
||||
"Deleted cloud copy of the file: {{title}}": "已刪除檔案的雲端副本:{{title}}",
|
||||
"Failed to upload file: {{title}}": "檔案上傳失敗:{{title}}",
|
||||
"Failed to download file: {{title}}": "檔案下載失敗:{{title}}",
|
||||
"Failed to delete cloud copy of the file: {{title}}": "未能刪除檔案的雲端副本:{{title}}"
|
||||
"Failed to delete cloud copy of the file: {{title}}": "未能刪除檔案的雲端副本:{{title}}",
|
||||
"{{percentage}}% used": "已使用 {{percentage}}%",
|
||||
"Resets in {{duration}}": "{{duration}}後重設"
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -10,7 +10,22 @@ type HardcoverSyncMapRow = {
|
||||
|
||||
const DB_SCHEMA = 'hardcover-sync';
|
||||
const DB_PATH = 'hardcover-sync.db';
|
||||
const STORAGE_PREFIX = 'hardcover-note-mapping';
|
||||
// Legacy localStorage key prefix used on Web before this store moved to SQLite.
|
||||
// Kept only for one-time migration of existing entries into the database.
|
||||
const LEGACY_STORAGE_PREFIX = 'hardcover-note-mapping';
|
||||
|
||||
const UPSERT_SQL = `
|
||||
INSERT INTO hardcover_note_mappings
|
||||
(book_hash, note_id, hardcover_journal_id, payload_hash, synced_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(book_hash, note_id)
|
||||
DO UPDATE SET
|
||||
hardcover_journal_id = excluded.hardcover_journal_id,
|
||||
payload_hash = excluded.payload_hash,
|
||||
synced_at = excluded.synced_at
|
||||
`;
|
||||
|
||||
type OpenDb = Awaited<ReturnType<AppService['openDatabase']>>;
|
||||
|
||||
export class HardcoverSyncMapStore {
|
||||
private appService: AppService;
|
||||
@@ -22,15 +37,7 @@ export class HardcoverSyncMapStore {
|
||||
this.appService = appService;
|
||||
}
|
||||
|
||||
private isWebStorageAvailable(): boolean {
|
||||
return typeof window !== 'undefined' && typeof window.localStorage !== 'undefined';
|
||||
}
|
||||
|
||||
private getStorageKey(bookHash: string, noteId: string): string {
|
||||
return `${STORAGE_PREFIX}:${bookHash}:${noteId}`;
|
||||
}
|
||||
|
||||
private async withDb<T>(fn: (db: Awaited<ReturnType<AppService['openDatabase']>>) => Promise<T>) {
|
||||
private async withDb<T>(fn: (db: OpenDb) => Promise<T>) {
|
||||
const db = await this.appService.openDatabase(DB_SCHEMA, DB_PATH, 'Data');
|
||||
try {
|
||||
return await fn(db);
|
||||
@@ -39,31 +46,57 @@ export class HardcoverSyncMapStore {
|
||||
}
|
||||
}
|
||||
|
||||
private async upsertRow(db: OpenDb, row: HardcoverSyncMapRow): Promise<void> {
|
||||
await db.execute(UPSERT_SQL, [
|
||||
row.book_hash,
|
||||
row.note_id,
|
||||
row.hardcover_journal_id,
|
||||
row.payload_hash,
|
||||
row.synced_at,
|
||||
]);
|
||||
}
|
||||
|
||||
// One-time migration: lift legacy localStorage entries for this book into
|
||||
// the database, then remove them. Runs only when window.localStorage is
|
||||
// available (i.e. on Web); a no-op on Tauri/native.
|
||||
private async migrateLegacyForBook(db: OpenDb, bookHash: string): Promise<void> {
|
||||
if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const prefix = `${LEGACY_STORAGE_PREFIX}:${bookHash}:`;
|
||||
const legacyKeys: string[] = [];
|
||||
for (let i = 0; i < window.localStorage.length; i++) {
|
||||
const key = window.localStorage.key(i);
|
||||
if (key && key.startsWith(prefix)) {
|
||||
legacyKeys.push(key);
|
||||
}
|
||||
}
|
||||
if (legacyKeys.length === 0) return;
|
||||
|
||||
for (const key of legacyKeys) {
|
||||
const raw = window.localStorage.getItem(key);
|
||||
if (!raw) {
|
||||
window.localStorage.removeItem(key);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const row = JSON.parse(raw) as HardcoverSyncMapRow;
|
||||
await this.upsertRow(db, row);
|
||||
} catch (error) {
|
||||
console.error('Failed to migrate Hardcover mapping from localStorage:', error);
|
||||
}
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
async loadForBook(bookHash: string): Promise<void> {
|
||||
this.loadedBookHash = bookHash;
|
||||
this.mappings.clear();
|
||||
this.modified = false;
|
||||
|
||||
if (this.isWebStorageAvailable()) {
|
||||
try {
|
||||
const prefix = `${STORAGE_PREFIX}:${bookHash}:`;
|
||||
for (let i = 0; i < window.localStorage.length; i++) {
|
||||
const key = window.localStorage.key(i);
|
||||
if (key && key.startsWith(prefix)) {
|
||||
const raw = window.localStorage.getItem(key);
|
||||
if (raw) {
|
||||
const row = JSON.parse(raw) as HardcoverSyncMapRow;
|
||||
this.mappings.set(row.note_id, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to read Hardcover note mapping from localStorage:', error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await this.withDb(async (db) => {
|
||||
await this.migrateLegacyForBook(db, bookHash);
|
||||
|
||||
const rows = await db.select<HardcoverSyncMapRow>(
|
||||
`SELECT book_hash, note_id, hardcover_journal_id, payload_hash, synced_at
|
||||
FROM hardcover_note_mappings
|
||||
@@ -79,35 +112,9 @@ export class HardcoverSyncMapStore {
|
||||
async flush(): Promise<void> {
|
||||
if (!this.modified || !this.loadedBookHash) return;
|
||||
|
||||
if (this.isWebStorageAvailable()) {
|
||||
try {
|
||||
for (const row of this.mappings.values()) {
|
||||
window.localStorage.setItem(
|
||||
this.getStorageKey(row.book_hash, row.note_id),
|
||||
JSON.stringify(row),
|
||||
);
|
||||
}
|
||||
this.modified = false;
|
||||
} catch (error) {
|
||||
console.error('Failed to write Hardcover note mapping to localStorage:', error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await this.withDb(async (db) => {
|
||||
// Execute inserts sequentially but within a single DB connection
|
||||
for (const row of this.mappings.values()) {
|
||||
await db.execute(
|
||||
`INSERT INTO hardcover_note_mappings
|
||||
(book_hash, note_id, hardcover_journal_id, payload_hash, synced_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(book_hash, note_id)
|
||||
DO UPDATE SET
|
||||
hardcover_journal_id = excluded.hardcover_journal_id,
|
||||
payload_hash = excluded.payload_hash,
|
||||
synced_at = excluded.synced_at`,
|
||||
[row.book_hash, row.note_id, row.hardcover_journal_id, row.payload_hash, row.synced_at],
|
||||
);
|
||||
await this.upsertRow(db, row);
|
||||
}
|
||||
});
|
||||
this.modified = false;
|
||||
|
||||
@@ -364,8 +364,13 @@ export class WebAppService extends BaseAppService {
|
||||
opts?: DatabaseOpts,
|
||||
): Promise<DatabaseService> {
|
||||
const fullPath = await this.resolveFilePath(path, base);
|
||||
// OPFS `getFileHandle` rejects names containing path separators, and the
|
||||
// Turso WASM connector passes the whole string as a single OPFS handle
|
||||
// name without traversing directories. Flatten to a safe single segment.
|
||||
const opfsName = fullPath.replace(/[/\\]+/g, '_').replace(/^_+/, '');
|
||||
console.log(`Opening database at ${opfsName} with schema ${schema}`);
|
||||
const { WebDatabaseService } = await import('./database/webDatabaseService');
|
||||
const db = await WebDatabaseService.open(fullPath, opts);
|
||||
const db = await WebDatabaseService.open(opfsName, opts);
|
||||
const { migrate } = await import('./database/migrate');
|
||||
const { getMigrations } = await import('./database/migrations');
|
||||
await migrate(db, getMigrations(schema));
|
||||
|
||||
Reference in New Issue
Block a user