Skip to content

Commit

Permalink
Implement smart readings display and highlight English definition in …
Browse files Browse the repository at this point in the history
…popover dictionary based on gloss
  • Loading branch information
justinsilvestre committed Feb 11, 2024
1 parent 760650b commit b55eb1b
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 62 deletions.
4 changes: 2 additions & 2 deletions brandt.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ progress: 81 / 4143 tasks complete (~1.9%)
- [x] display text with English translation and gloss (Feb 9)
- [x] display character definitions on click (Feb 9)
- [ ] display lesson notes in popover
- [ ] in popover: highlight reading/definition according to gloss, if present
- [ ] display correct character readings via gloss, if present
- [x] in popover: highlight reading/definition according to gloss, if present
- [x] display correct character readings via gloss, if present
- [x] enable character definitions for characters first seen in other passages
- [ ] make build script to aggregate lexicon + make text file to be checked into repo to track changes
- Lesson 1, Text 1
Expand Down
16 changes: 16 additions & 0 deletions src/app/lexiconEntryEnKeywords.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { LexiconEntry } from "@/app/texts/Passage";

export function findEntryMatchingEnKeywords(
entries: LexiconEntry[],
enKeywords: string[]
) {
return entries.find((e) =>
getEntryEnKeywords(e).some((k) => enKeywords.includes(k))
);
}
export function getEntryEnKeywords(entry: LexiconEntry): string[] {
return entry.en?.split(/[,;]/).map((s) => toEnMatchKeyword(s)) || [];
}
export function toEnMatchKeyword(s: string): string {
return s.trim().replace(/^(an? |to |the )/g, "");
}
20 changes: 6 additions & 14 deletions src/app/prebuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import {
} from "@/app/texts/files";
import * as fs from "fs";
import * as path from "path";
import {
getEntryEnKeywords,
findEntryMatchingEnKeywords,
toEnMatchKeyword,
} from "./lexiconEntryEnKeywords";

const prebuildDirectoryPath = path.join(process.cwd(), "prebuild");

Expand Down Expand Up @@ -76,9 +81,7 @@ function mergeLexiconEntries(
const merged: LexiconEntry[] = [...a];
for (const entry of b) {
const bEntryEnKeywords = getEntryEnKeywords(entry);
const matchingEntry = merged.find((e) =>
getEntryEnKeywords(e).some((k) => bEntryEnKeywords.includes(k))
);
const matchingEntry = findEntryMatchingEnKeywords(merged, bEntryEnKeywords);
if (matchingEntry) {
matchingEntry.en = mergeEntryEnKeywords(matchingEntry, entry);
matchingEntry.jyutping = mergeEntryPronunciation(
Expand All @@ -99,18 +102,7 @@ function mergeLexiconEntries(
return merged;
}

function getEntryEnKeywords(entry: LexiconEntry): string[] {
return entry.en?.split(/[,;]/).map((s) => toEnMatchKeyword(s)) || [];
}
function toEnMatchKeyword(s: string): string {
return s.trim().replace(/^(an? |to |the )/, "");
}

function mergeEntryEnKeywords(a: LexiconEntry, b: LexiconEntry): string {
// const aKeywords = getEntryEnKeywords(a);
// const bKeywords = getEntryEnKeywords(b);
// const mergedKeywords = [...new Set([...aKeywords, ...bKeywords])].join(', ');
// return mergedKeywords;
const mergedSegments =
a.en
?.split(";")
Expand Down
167 changes: 126 additions & 41 deletions src/app/texts/[textId]/ChineseWithPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import { PassageVocab } from "../Passage";
import { usePopover } from "./Popover";
import { useState } from "react";
import { textIsPunctuation } from "./punctuation";
import {
findEntryMatchingEnKeywords,
toEnMatchKeyword,
} from "@/app/lexiconEntryEnKeywords";

export type DisplayOptions = {
ruby: null | "en" | "vi" | "jyutping" | "pinyin";
Expand All @@ -28,6 +32,7 @@ export function ChineseWithPopover({
}) {
const popover = usePopover();
const [popoverChar, setChar] = useState<string | null>(null);
const [popoverCharGloss, setCharGloss] = useState<string | null>(null);

let glossedChars = 0;

Expand All @@ -40,16 +45,31 @@ export function ChineseWithPopover({

const id = `text-${char}-${i}`;

if (!vocab[char]) {
const entries = vocab[char];

if (!entries?.length) {
return <span key={i}>{char}</span>;
}

const enGloss = gloss?.[glossIndex]?.replace(/_/g, " ") || null;

const soleEntry = entries.length === 1 ? entries[0] : null;

const matchingEntry = enGloss
? findEntryMatchingEnKeywords(entries, [enGloss])
: null;

let rubyText: string | null = null;
if (displayOptions.ruby === "en")
rubyText = gloss?.[glossIndex]?.replace(/_/g, " ") || null;
if (displayOptions.ruby === "en") rubyText = enGloss;
else
rubyText =
displayOptions?.ruby && vocab[char]
? vocab[char]![0][displayOptions.ruby]
displayOptions?.ruby && (matchingEntry || soleEntry)
? (matchingEntry || soleEntry
? [matchingEntry || soleEntry!]
: entries
)
.map((e) => e[displayOptions.ruby!])
.join(" / ")
: null;

return (
Expand All @@ -64,55 +84,120 @@ export function ChineseWithPopover({
onClick: (e) => {
popover.refs.setReference(e.currentTarget);
setChar(char);
setCharGloss(enGloss);
},
})}
>
{rubyText ? (
<ruby id={id}>
{char}
<rt className="text-[0.40em] mr-[0.40em]">{rubyText}</rt>
<rt className="text-[0.40em] mr-[0.40em]">
{rubyText}
{!soleEntry && !matchingEntry ? "*" : ""}
</rt>
</ruby>
) : (
char
)}
</span>
);
})}
{popover.open && popoverChar && vocab[popoverChar] && (
<FloatingPortal>
<FloatingFocusManager context={popover.context} modal={popover.modal}>
<div
ref={popover.refs.setFloating}
style={{
...popover.floatingStyles,
filter:
"drop-shadow(1px 1px 2px rgb(var(--foreground-rgb) / 0.4))",
}}
aria-labelledby={popover.labelId}
aria-describedby={popover.descriptionId}
{...popover.getFloatingProps()}
>
<FloatingArrow
ref={popover.arrowRef}
context={popover.context}
style={{
fill: "rgba(var(--background-rgb))",
}}
fill="blue"
/>
<div className="bg-background max-w-[10rem]">
{popoverChar &&
vocab[popoverChar]?.map((entry, i) => (
<div key={i} className="p-1 rounded">
<b>{entry.vi}</b>{" "}
<span className="text-sm">{entry.en}</span>
</div>
))}
</div>
</div>
</FloatingFocusManager>
</FloatingPortal>
)}
{popover.open &&
popoverChar &&
vocab[popoverChar] &&
PopoverDictionaryContent(popover, popoverChar, vocab, popoverCharGloss)}
</span>
);
}

function PopoverDictionaryContent(
popover: ReturnType<typeof usePopover>,
popoverChar: string,
vocab: PassageVocab,
enGloss: string | null
) {
return (
<FloatingPortal>
<FloatingFocusManager context={popover.context} modal={popover.modal}>
<div
ref={popover.refs.setFloating}
style={{
...popover.floatingStyles,
filter: "drop-shadow(1px 1px 2px rgb(var(--foreground-rgb) / 0.4))",
}}
aria-labelledby={popover.labelId}
aria-describedby={popover.descriptionId}
{...popover.getFloatingProps()}
>
<FloatingArrow
ref={popover.arrowRef}
context={popover.context}
style={{
fill: "rgba(var(--background-rgb))",
}}
fill="blue"
/>
<div className="bg-background max-w-[10rem]">
{popoverChar &&
vocab[popoverChar]?.map((entry, i, entries) => {
const enDefinitionSegmentsCount =
entry.en?.split(/[,;]/).length || 0;

return (
<div key={i} className="p-1 rounded">
{[entry.jyutping, entry.pinyin, entry.vi]
.filter((e) => e)
.map((e, i, readings) => (
<span key={i}>
<b>{e}</b>
{i < readings.length - 1 ? " / " : " "}
</span>
))}
<span className="text-sm">
{entry.en
?.split("; ")
.map(
(semicolonSegment, semicolonI, semicolonSegments) => {
const commaSegments = semicolonSegment.split(", ");
return (
<span key={semicolonSegment}>
{commaSegments.map(
(commaSegment, commaI, commaSegments) => {
const segmentKeyword =
toEnMatchKeyword(commaSegment);

return (
<span
key={commaSegment}
className={`${
segmentKeyword === enGloss &&
enDefinitionSegmentsCount > 1
? "bg-yellow-400/10 border-yellow-400 border text-foreground"
: ""
}`}
>
{commaSegment}
{commaI < commaSegments.length - 1
? ", "
: ""}
</span>
);
}
)}
{semicolonI < semicolonSegments.length - 1
? "; "
: ""}
</span>
);
}
)}
</span>
</div>
);
})}
</div>
</div>
</FloatingFocusManager>
</FloatingPortal>
);
}
2 changes: 1 addition & 1 deletion texts/brandt-ch01-1.passage.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ father speak this what harm

兒曰。常聞人言。鵲鳴吉。鴉鳴凶。
(The) boy said, "(I) have often heard people say (that when a) magpie chatters, (it brings) good luck, (and when a) crow caws, (it brings) bad luck.
boy speak frequent hear people say magpie sing auspicious crow sing unlucky
boy speak frequent hear person say magpie sing auspicious crow sing unlucky

今{d:鳴者}{e:鴉也}。故叱之。
Today the crows have been cawing, therefore I have hooted at them."
Expand Down
8 changes: 4 additions & 4 deletions texts/brandt-ch01-1.vocab.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ Traditional Vietnamese Jyutping English
cảnh geng² the neck; the throat; an isthmus
nhi ji⁴ a conjunctive particle; an adversative particle; and; yet; but; like; you; your
minh ming⁴ the cry of a bird or animal; a sound; to sing; to cry
nhi ji⁴ a child; a son; male
nhi ji⁴ a child; a son; male; boy
sất cik¹ to hoot at
chi zi¹ of; it; him; her; them; this; that; to go; to proceed
phụ fu⁶ a father
viết joek⁶ to speak
thị si⁶ to be; right; this; that
ho⁴ an interrogative particle; how? why? what?
ho⁴ an interrogative particle; how; why; what
hại hoi⁶ to injure; harm
thường soeng⁴ constant; usual; frequent
văn man⁴ to hear; to smell; read
vấn man⁴, man² to make known; to state
nhân jan⁴ a man; person
ngôn jin⁴ words; language; to speak; to express
ngôn jin⁴ words; language; to speak; to express; to say
thước coek³ the magpie; the jay
kim gam¹ now; the present time
giả ze² a particle of many uses imparting various shades of meaning - adjectival, participial etc. to words to which it is joined; that which
Expand All @@ -43,7 +43,7 @@ Traditional Vietnamese Jyutping English
tri zi¹ to know; to perceive; to be aware of
huống fong³ moreover; still more; how much more
而況 nhi huống ji⁴ fong³ still more; how much more
hồ fu⁴ an interrogative and exclamatory particle; an expletive
hồ fu⁴ an interrogative particle; an exclamatory particle; an expletive
bội bui³ behind; contrary
lei⁵ right
hành hang⁴ to act; to do
Expand Down

0 comments on commit b55eb1b

Please sign in to comment.