Editor: Find & Replace, read-aloud, backup, typography, phonetic, org niceties
Writer power-ups (Phase 11), plus the selection-bubble vs copy/paste fix. - Find & Replace (Ctrl/Cmd+F): SearchHighlight decoration extension + FindReplace bar (match-case, replace-all back-to-front, scroll without popping the selection bubble). - Read-aloud (Web Speech, offline) on the word card and selection bubble. - Keyboard/touch access to the ESL helpers: Ctrl/Cmd+D look up word at caret, Ctrl/Cmd+J rewrite selection, touch long-press lookup. Refactored the right-click handler into a shared openWordLookup(pos). - Whole-corpus backup: GET /api/docs/export-all zips every doc (md/docx), de-dupes filenames, dated name; sidebar download links. TestExportAll. - Smart typography input rules (curly quotes/em-dash/ellipsis), ASCII-only so CJK is untouched. - Duplicate doc, sidebar sort (Recent/Title/Longest), toolbar outline popover. - English phonetic (chosen over pinyin for an English learner): ECDICT-built phonetic.json.gz (46,579 words) + Result.Phonetic + WordCard IPA line; scripts/build_phonetic.py (full build + --seed fallback). - Selection bubble no longer blocks copy/paste: deferred to pointer-up and made click-through except on its buttons. Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
This commit is contained in:
@@ -27,3 +27,12 @@ var synonymsGz []byte
|
||||
//
|
||||
//go:embed data/gloss.json.gz
|
||||
var glossGz []byte
|
||||
|
||||
// phoneticGz is the gzipped English-phonetic map: word → IPA, lowercase keys.
|
||||
// Built from ECDICT's phonetic column (scripts/build_phonetic.py). Shown beside
|
||||
// the read-aloud button so the writer — a Mandarin speaker learning English —
|
||||
// can see how to say the word. Ships with a small common-word seed; a full build
|
||||
// broadens coverage.
|
||||
//
|
||||
//go:embed data/phonetic.json.gz
|
||||
var phoneticGz []byte
|
||||
|
||||
BIN
internal/lexicon/data/phonetic.json.gz
Normal file
BIN
internal/lexicon/data/phonetic.json.gz
Normal file
Binary file not shown.
@@ -26,6 +26,7 @@ type Meaning struct {
|
||||
type Result struct {
|
||||
Word string `json:"word"`
|
||||
Gloss string `json:"gloss"`
|
||||
Phonetic string `json:"phonetic"` // IPA for the English word; "" when absent
|
||||
Definitions []Meaning `json:"definitions"`
|
||||
Synonyms []string `json:"synonyms"`
|
||||
}
|
||||
@@ -56,6 +57,7 @@ type Lexicon struct {
|
||||
defs map[string][][]string // word → [[pos, def, example], …]
|
||||
synonyms map[string][]string // word → [synonym, …]
|
||||
gloss map[string]string // word → Chinese gloss
|
||||
phonetic map[string]string // word → IPA
|
||||
}
|
||||
|
||||
// New returns a Lexicon. The datasets aren't read until the first Lookup.
|
||||
@@ -75,6 +77,10 @@ func (l *Lexicon) load() {
|
||||
l.loadErr = fmt.Errorf("load gloss: %w", err)
|
||||
return
|
||||
}
|
||||
if err := gunzipJSON(phoneticGz, &l.phonetic); err != nil {
|
||||
l.loadErr = fmt.Errorf("load phonetic: %w", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -95,6 +101,9 @@ func (l *Lexicon) Lookup(word string) (Result, error) {
|
||||
}
|
||||
|
||||
res.Gloss = lookupGloss(l.gloss, norm)
|
||||
// Phonetic is a word→string map like the gloss, so the same de-inflecting
|
||||
// candidate walk resolves "running" → "run", etc.
|
||||
res.Phonetic = lookupGloss(l.phonetic, norm)
|
||||
|
||||
if raw := lookupDefs(l.defs, norm); raw != nil {
|
||||
for _, m := range raw {
|
||||
|
||||
Reference in New Issue
Block a user