summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-07-18 15:13:29 +0300
committerPaul Buetow <paul@buetow.org>2025-07-18 15:13:29 +0300
commit93500a6d9a237c3a78a73d552c17583938672a59 (patch)
tree99a16d84877ea7753db7350df4c1b60bf14467c1
parent1b3228d4c93f5951a1d6cc0e77a364c3dde3e625 (diff)
fix: improve APKG export functionalityv0.4.0
- Fix Anki import errors by adding missing deck fields (newToday, revToday, etc.) - Fix media files not displaying/playing by using original filenames in cards - Add phonetic information to notes field with proper line breaks - Add 'e' hotkey for quick APKG export - Increment version to 0.4.0 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
-rw-r--r--internal/anki/apkg_generator.go89
-rw-r--r--internal/anki/generator.go8
-rw-r--r--internal/gui/app.go5
-rw-r--r--internal/version.go2
4 files changed, 66 insertions, 38 deletions
diff --git a/internal/anki/apkg_generator.go b/internal/anki/apkg_generator.go
index 099c146..6f0043f 100644
--- a/internal/anki/apkg_generator.go
+++ b/internal/anki/apkg_generator.go
@@ -52,15 +52,8 @@ func (g *APKGGenerator) GenerateAPKG(outputPath string) error {
}
defer os.RemoveAll(tempDir)
- // Create SQLite database
- dbPath := filepath.Join(tempDir, "collection.anki2")
- if err := g.createDatabase(dbPath); err != nil {
- return fmt.Errorf("failed to create database: %w", err)
- }
-
- // Copy media files
- mediaDir := filepath.Join(tempDir, "media")
- if err := g.copyMediaFiles(mediaDir); err != nil {
+ // Copy media files FIRST (this populates g.mediaFiles map)
+ if err := g.copyMediaFiles(tempDir); err != nil {
return fmt.Errorf("failed to copy media files: %w", err)
}
@@ -69,6 +62,12 @@ func (g *APKGGenerator) GenerateAPKG(outputPath string) error {
return fmt.Errorf("failed to create media mapping: %w", err)
}
+ // Create SQLite database (this uses g.mediaFiles map)
+ dbPath := filepath.Join(tempDir, "collection.anki2")
+ if err := g.createDatabase(dbPath); err != nil {
+ return fmt.Errorf("failed to create database: %w", err)
+ }
+
// Create the .apkg zip file
if err := g.createZipPackage(tempDir, outputPath); err != nil {
return fmt.Errorf("failed to create zip package: %w", err)
@@ -194,26 +193,41 @@ func (g *APKGGenerator) insertCollection(db *sql.DB) error {
now := time.Now().Unix()
// Create deck configuration
+ // The arrays are [learningCount, reviewCount] for today's stats
decks := map[string]interface{}{
"1": map[string]interface{}{
- "id": 1,
- "name": "Default",
- "mod": now,
- "desc": "",
- "collapsed": false,
- "dyn": 0,
- "conf": 1,
- "usn": 0,
+ "id": 1,
+ "name": "Default",
+ "mod": now,
+ "desc": "",
+ "collapsed": false,
+ "dyn": 0,
+ "conf": 1,
+ "usn": 0,
+ "newToday": []int{0, 0},
+ "revToday": []int{0, 0},
+ "lrnToday": []int{0, 0},
+ "timeToday": []int{0, 0},
+ "browserCollapsed": false,
+ "extendNew": 10,
+ "extendRev": 50,
},
fmt.Sprintf("%d", g.deckID): map[string]interface{}{
- "id": g.deckID,
- "name": g.deckName,
- "mod": now,
- "desc": "Bulgarian vocabulary cards created by TotalRecall",
- "collapsed": false,
- "dyn": 0,
- "conf": 1,
- "usn": 0,
+ "id": g.deckID,
+ "name": g.deckName,
+ "mod": now,
+ "desc": "Bulgarian vocabulary cards created by TotalRecall",
+ "collapsed": false,
+ "dyn": 0,
+ "conf": 1,
+ "usn": 0,
+ "newToday": []int{0, 0},
+ "revToday": []int{0, 0},
+ "lrnToday": []int{0, 0},
+ "timeToday": []int{0, 0},
+ "browserCollapsed": false,
+ "extendNew": 10,
+ "extendRev": 50,
},
}
decksJSON, _ := json.Marshal(decks)
@@ -489,16 +503,20 @@ func (g *APKGGenerator) insertNotesAndCards(db *sql.DB) error {
}
imageField := ""
- if card.ImageFile != "" {
- if num, ok := g.mediaFiles[filepath.Base(card.ImageFile)]; ok {
- imageField = fmt.Sprintf(`<img src="%d">`, num)
+ if card.ImageFile != "" && fileExists(card.ImageFile) {
+ basename := filepath.Base(card.ImageFile)
+ if _, ok := g.mediaFiles[basename]; ok {
+ // Use the original filename in the card content
+ imageField = fmt.Sprintf(`<img src="%s">`, basename)
}
}
audioField := ""
- if card.AudioFile != "" {
- if num, ok := g.mediaFiles[filepath.Base(card.AudioFile)]; ok {
- audioField = fmt.Sprintf("[sound:%d]", num)
+ if card.AudioFile != "" && fileExists(card.AudioFile) {
+ basename := filepath.Base(card.AudioFile)
+ if _, ok := g.mediaFiles[basename]; ok {
+ // Use the original filename in the card content
+ audioField = fmt.Sprintf("[sound:%s]", basename)
}
}
@@ -564,16 +582,15 @@ func (g *APKGGenerator) insertNotesAndCards(db *sql.DB) error {
}
// copyMediaFiles copies media files and assigns them numbers
-func (g *APKGGenerator) copyMediaFiles(mediaDir string) error {
- // Media files don't go in a subdirectory for .apkg
- // They go directly in the temp directory with numeric names
+func (g *APKGGenerator) copyMediaFiles(tempDir string) error {
+ // Media files go directly in the temp directory with numeric names
for _, card := range g.cards {
// Copy audio file
if card.AudioFile != "" && fileExists(card.AudioFile) {
filename := filepath.Base(card.AudioFile)
if _, exists := g.mediaFiles[filename]; !exists {
- targetPath := filepath.Join(filepath.Dir(mediaDir), fmt.Sprintf("%d", g.mediaCounter))
+ targetPath := filepath.Join(tempDir, fmt.Sprintf("%d", g.mediaCounter))
if err := copyFile(card.AudioFile, targetPath); err != nil {
return fmt.Errorf("failed to copy audio file %s: %w", card.AudioFile, err)
}
@@ -586,7 +603,7 @@ func (g *APKGGenerator) copyMediaFiles(mediaDir string) error {
if card.ImageFile != "" && fileExists(card.ImageFile) {
filename := filepath.Base(card.ImageFile)
if _, exists := g.mediaFiles[filename]; !exists {
- targetPath := filepath.Join(filepath.Dir(mediaDir), fmt.Sprintf("%d", g.mediaCounter))
+ targetPath := filepath.Join(tempDir, fmt.Sprintf("%d", g.mediaCounter))
if err := copyFile(card.ImageFile, targetPath); err != nil {
return fmt.Errorf("failed to copy image file %s: %w", card.ImageFile, err)
}
diff --git a/internal/anki/generator.go b/internal/anki/generator.go
index eecce48..21b1995 100644
--- a/internal/anki/generator.go
+++ b/internal/anki/generator.go
@@ -192,6 +192,14 @@ func (g *Generator) GenerateFromDirectory(dir string) error {
}
}
+ // Load phonetic information as notes
+ phoneticFile := filepath.Join(wordDir, fmt.Sprintf("%s_phonetic.txt", sanitizedWord))
+ if data, err := os.ReadFile(phoneticFile); err == nil {
+ // Preserve line breaks by converting \n to <br> for HTML display
+ notes := strings.TrimSpace(string(data))
+ card.Notes = strings.ReplaceAll(notes, "\n", "<br>")
+ }
+
// Only add card if it has at least some content
if card.AudioFile != "" || card.ImageFile != "" || card.Translation != "" {
g.AddCard(card)
diff --git a/internal/gui/app.go b/internal/gui/app.go
index b84a917..4a392ed 100644
--- a/internal/gui/app.go
+++ b/internal/gui/app.go
@@ -334,7 +334,7 @@ func (a *Application) setupUI() {
// Create menu
fileMenu := fyne.NewMenu("File",
- fyne.NewMenuItem("Export to Anki...", a.onExportToAnki),
+ fyne.NewMenuItem("Export to Anki... (E)", a.onExportToAnki),
fyne.NewMenuItemSeparator(),
fyne.NewMenuItem("Preferences...", a.onPreferences),
fyne.NewMenuItemSeparator(),
@@ -1439,6 +1439,9 @@ func (a *Application) handleShortcutKey(key fyne.KeyName) {
if a.currentAudioFile != "" {
a.audioPlayer.Play()
}
+
+ case fyne.KeyE: // Export to APKG
+ a.onExportToAnki()
}
}
diff --git a/internal/version.go b/internal/version.go
index 547370b..1dee17c 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -1,3 +1,3 @@
package internal
-const Version = "0.3.0"
+const Version = "0.4.0"