mirror of
https://github.com/patarapolw/cjdic2.git
synced 2026-11-09 17:39:35 +00:00
auto-furigana search
This commit is contained in:
1 parent
1e91f21ca4
commit
37c3adabfc
3 files changed
+95
-10
No files matched your search
@@ -53,7 +53,7 @@ impl YomitanDatabase {
|
|||||||
LEFT JOIN yomitan.def_tag_sets dt ON dt.id = t.def_tags_id
|
LEFT JOIN yomitan.def_tag_sets dt ON dt.id = t.def_tags_id
|
||||||
LEFT JOIN yomitan.rule_sets r ON r.id = t.rules_id
|
LEFT JOIN yomitan.rule_sets r ON r.id = t.rules_id
|
||||||
LEFT JOIN yomitan.term_tag_sets tt ON tt.id = t.term_tags_id
|
LEFT JOIN yomitan.term_tag_sets tt ON tt.id = t.term_tags_id
|
||||||
WHERE t.term GLOB ?1 {} t.reading GLOB ?2
|
WHERE LOWER(t.term) GLOB ?1 {} t.reading GLOB ?2
|
||||||
ORDER BY t.score DESC
|
ORDER BY t.score DESC
|
||||||
LIMIT ?3 OFFSET ?4
|
LIMIT ?3 OFFSET ?4
|
||||||
"#,
|
"#,
|
||||||
@@ -63,7 +63,9 @@ impl YomitanDatabase {
|
|||||||
let conn = self.db.conn.lock().unwrap();
|
let conn = self.db.conn.lock().unwrap();
|
||||||
|
|
||||||
let mut stmt = conn.prepare(&sql)?;
|
let mut stmt = conn.prepare(&sql)?;
|
||||||
let rows = stmt.query_map(params![q_term, q_reading, limit, offset], |r| {
|
let rows = stmt.query_map(
|
||||||
|
params![q_term.to_ascii_lowercase(), q_reading, limit, offset],
|
||||||
|
|r| {
|
||||||
Ok(YomitanRow {
|
Ok(YomitanRow {
|
||||||
term: r.get(0)?,
|
term: r.get(0)?,
|
||||||
reading: r.get(1)?,
|
reading: r.get(1)?,
|
||||||
@@ -75,7 +77,8 @@ impl YomitanDatabase {
|
|||||||
term_tags: r.get(7)?,
|
term_tags: r.get(7)?,
|
||||||
dict_title: r.get(8)?,
|
dict_title: r.get(8)?,
|
||||||
})
|
})
|
||||||
})?;
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
for r in rows {
|
for r in rows {
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ impl YomitanWriter {
|
|||||||
CREATE INDEX IF NOT EXISTS dictionaries_lang ON dictionaries (lang);
|
CREATE INDEX IF NOT EXISTS dictionaries_lang ON dictionaries (lang);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS terms_dict_id ON terms (dict_id);
|
CREATE INDEX IF NOT EXISTS terms_dict_id ON terms (dict_id);
|
||||||
CREATE INDEX IF NOT EXISTS terms_term ON terms (term);
|
CREATE INDEX IF NOT EXISTS terms_term_LOWER ON terms (LOWER(term));
|
||||||
CREATE INDEX IF NOT EXISTS terms_reading ON terms (reading);
|
CREATE INDEX IF NOT EXISTS terms_reading ON terms (reading);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS term_meta_dict_id ON term_meta (dict_id);
|
CREATE INDEX IF NOT EXISTS term_meta_dict_id ON term_meta (dict_id);
|
||||||
|
|||||||
+88
-6
@@ -1,6 +1,6 @@
|
|||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { CompositionEventHandler, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { Box, Button, Card, Group, Input, Stack } from "@chakra-ui/react";
|
import { Box, Button, Card, Group, Input, Stack } from "@chakra-ui/react";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
@@ -11,21 +11,56 @@ import { Provider } from "./components/ui/provider";
|
|||||||
function App() {
|
function App() {
|
||||||
const [q, setQ] = useState("");
|
const [q, setQ] = useState("");
|
||||||
const [entries, setEntries] = useState<any[]>([]);
|
const [entries, setEntries] = useState<any[]>([]);
|
||||||
|
const [searchTimeout, setSearchTimeout] = useState(0);
|
||||||
|
const [furigana, setFurigana] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
doSearch();
|
trySearch();
|
||||||
}, [q]);
|
}, [q]);
|
||||||
|
|
||||||
async function doSearch() {
|
async function trySearch() {
|
||||||
if (!q.trim()) {
|
if (!q.trim()) {
|
||||||
setEntries([]);
|
setEntries([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ender = q.endsWith(" ") ? "" : "*";
|
if (searchTimeout) {
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
const qTerm = q.trim() + ender;
|
setSearchTimeout(
|
||||||
const qReading = q.trim() + ender;
|
setTimeout(() => {
|
||||||
|
runSearch();
|
||||||
|
}, 250),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runSearch() {
|
||||||
|
const ender = /\p{Z}$/u.test(q) ? "" : "*";
|
||||||
|
|
||||||
|
const re = /\<(.+?)\>\[(.+?)\]/g;
|
||||||
|
|
||||||
|
let qTerm = "";
|
||||||
|
let qReading = "";
|
||||||
|
|
||||||
|
q.trim()
|
||||||
|
.split(re)
|
||||||
|
.map((s, i) => {
|
||||||
|
switch (i % 3) {
|
||||||
|
case 1:
|
||||||
|
qTerm += s;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
qReading += s;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qTerm += s;
|
||||||
|
qReading += s;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
qTerm += ender;
|
||||||
|
qReading += ender;
|
||||||
|
|
||||||
setEntries(
|
setEntries(
|
||||||
await invoke("search_yomitan", {
|
await invoke("search_yomitan", {
|
||||||
@@ -37,6 +72,51 @@ function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FURIGANA_REGEX = /^[\p{scx=Hiragana}\p{scx=Katakana}]+$/u;
|
||||||
|
const KANJI_REGEX = /([\p{sc=Han}\p{N}々〆ヵヶ]+)/u;
|
||||||
|
|
||||||
|
const updateFurigana: CompositionEventHandler = ({
|
||||||
|
data: compositionData,
|
||||||
|
}) => {
|
||||||
|
if (FURIGANA_REGEX.test(compositionData)) {
|
||||||
|
setFurigana(compositionData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addFurigana: CompositionEventHandler = ({ data: compositionData }) => {
|
||||||
|
const cleanedFuri = [...furigana.replace(/n/g, "ん")]
|
||||||
|
.map((c) => katakanaToHiragana(c))
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
let parts = compositionData.split(KANJI_REGEX);
|
||||||
|
if (parts.length === 1) return;
|
||||||
|
|
||||||
|
const hiraganaParts = parts.map((p) =>
|
||||||
|
[...p].map((c) => katakanaToHiragana(c)).join(""),
|
||||||
|
);
|
||||||
|
const regex = new RegExp(
|
||||||
|
`^${hiraganaParts.map((p, idx) => `(${idx & 1 ? ".+" : p})`).join("")}$`,
|
||||||
|
);
|
||||||
|
let rt: (string | null)[] = furigana.match(regex) || [];
|
||||||
|
if (!rt.length) {
|
||||||
|
parts = [compositionData];
|
||||||
|
rt = [null, cleanedFuri];
|
||||||
|
}
|
||||||
|
rt.shift();
|
||||||
|
|
||||||
|
const markup = parts
|
||||||
|
.map((p, idx) => (idx & 1 ? `<${p}>[${rt[idx]}]` : p))
|
||||||
|
.join("");
|
||||||
|
setQ(markup);
|
||||||
|
};
|
||||||
|
|
||||||
|
function katakanaToHiragana(k: string) {
|
||||||
|
let c = k.charCodeAt(0);
|
||||||
|
return c >= 12449 && c <= 12531
|
||||||
|
? String.fromCharCode(k.charCodeAt(0) - 96)
|
||||||
|
: k;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider>
|
<Provider>
|
||||||
<Stack maxW={"1000px"} margin={"0.5em auto"}>
|
<Stack maxW={"1000px"} margin={"0.5em auto"}>
|
||||||
@@ -50,6 +130,8 @@ function App() {
|
|||||||
id="greet-input"
|
id="greet-input"
|
||||||
value={q}
|
value={q}
|
||||||
onChange={(e) => setQ(e.currentTarget.value)}
|
onChange={(e) => setQ(e.currentTarget.value)}
|
||||||
|
onCompositionUpdate={updateFurigana}
|
||||||
|
onCompositionEnd={addFurigana}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
|
|||||||
Reference in new issue
Block a user