Compare commits

..

No commits in common. "c7c1070f7448d2cf2ba4bf7585acc9907f63acbf" and "c49e62d052a24b387c58361066c27a74a2e808fc" have entirely different histories.

17 changed files with 112 additions and 531 deletions

View File

@ -1,125 +1,8 @@
Just an 異体字 of 刃... Dunno, doesn't make much sense to me
For phonetic purposes, used to say "ton" as in the unit of weight... that's it.
Kanji used for the names of weapons and stuff like that I guess just cuz it
looks kinda cool.
Puking of cats and dogs, also foul language. This is the simplification of 唚.
Doesn't seem to be all that used. Can't find examples of any words with this.
Only used in transliteration from other languages for "bi", that's kinda it.
It kinda makes sense doe.
Just a variaton on 叫 that isn't all that used it seems. Not worth it, it seems
from my little research.
Variant form of 假 which kinda seems to mean borrowing or something. Crazy.
No information on it. Probably old version of something that turned into
something with radicals or whatever.
To touch lightly, to poke (with a stick, or with whatever). Maybe this pops
up in the future, revive in such a case but it's not really seeming like a very
popular word from my search
Seems to be a historial version of 歴, not seen anywhere but dictionaries of
ancient stuff. Not really worth it
A covering flap in animals, such as a gill cover.
Not very interesting for me to learn
Not really used much. Whetstone, used in some expressions but failed to find
usages of them. Seems to be also a variant of 錯, which is interesting.
Old version of 崖 and 涯. I guess it used to mean kinda both and the radicals
were added later
Seems like it used only in the name of a specific place and in dialects. I can't
find youtube videos that use it even, really.
Not really used for anything. Simplification of 劌 which isn't really all that
used anyway.
Simplified variant of a kanji that isn't really used. Only one 熟語 in the
chinese dictionary, and the two in the Wiktionary are literary and archaic.
Really I should've learnt this one but oh well. A variant of 剏, but this is
accepted for the kanken.
Variant form of 劫, not really all that interesting.
Same as 剗, it's just a simplified version:
> Another variant of 鏟... how? I must be missing something.
Not used at all lol. It seems to be an 異体字, means whetstone. Seems like
to be treated kinda like 磨 in japanese or something
Only used in names, it's not the simplified version of 庫
To delete or to cut into blocks... not much else. There's no reason in learning
this. In japanese yet another kezuru lol.
There's no info online about it. It just means "to scoop" as in "えぐる"
(yet another one). Doesn't make much sense to learn.
(it's not cutting an arm as a punishment)
Another variant of 鏟... how? I must be missing something.
Unorthodox variant of 剮. Doesn't seem to be all that used, not worth it.
Seems to mean to cut flesh from bone
Chinese character, used in names of stuff, not much else. Doesn't seem to be
worth it.
Variant form of 鏟, apparently. It's only used in Taiwan and it kinda means
shoveling and leveling. Not worth the effort.
To cut, used in the word for castrating livestock. That's it. Can't find
usages or even modern usages.
Can't find really much info or relevancy. Can't find usage of it either.
Unused really. Changed over to other kanji like 協 and 捏
https://en.wiktionary.org/wiki/%E5%8A%A6
Simplified form of 勱, as far as I can tell it kinda means exherting oneself
and also as a simplification for 励む. There's no real value in it.
ladle for holding wine made from dried gourd (匏), used in ancient marriage
rituals. Not used at all
rituals.
Not very interesting but also it's the simplified version of 巹... Idk, a little
stretched.
It's mostly only used as a component, the origins are unclear but it's known
@ -185,6 +68,11 @@ Old variant/origin? of 陶, which is 常用漢字 / 名前に使える漢字 and
to kinda mean "pottery" , "clay" . It's used in 陶器[とうき].
Not interesting on its own
Old vase for holding water/wine. Not used aside from that.
https://en.wikipedia.org/wiki/Yi_(vessel)
REVISIT
Simplified form of 匭. Not of interest at all. Small box or something.

View File

@ -26878,6 +26878,5 @@
"䨻": "y/23050",
"𬚩": "y/28415",
"𠔻": "y/28412",
"𪚥": "y/28413",
"办": "y/13156"
"𪚥": "y/28413"
}

View File

@ -189,10 +189,6 @@
鄆潞碣媓嗉瓱嚕畤縉怵锻輗劓輟玷煆帕蒺箛吭笒偆晹偰毉昺啤冓罏秖節禎閶搨辤凞邗彀嚈噠
瑭潢昪煜嬀㬎芾忢鍳溉袗鸊鷉鱖陏鏧鰧讌糝箟髁糙譛离㷔壩芮斝蘄虢穌鼂瑇瑒耊找凳柰縧麈
盔撾鮊鱔黿攢驊騮驌駃騠騊駼顖綉扆拄炁拽憨鰣殮鄷壳猙扒倮璇戩瘖瘂髒豭瑗癯踽偬鐇滎鼇
盦惲翬琿龢芩暻犹洳溏沺呴棃筷豬駙珓崁⻆歃賾澂絋鶇幞揀颫犖鞢韴韈嵓齩髃𥇥𥆩䑛虗殱𤄃
姱裼蟫蚜炅佟綦虁剕甗絺塤綌璦菹貙詡偀蝪篶覬覦餤踹摔鷖躺腁霪睟摽焄鷆听哎珱昰鯳魵鮴
鞁莇筴黻倢伃栝蕡䗪惷蔛蕢鱉尃豨蓚塋郅堋竓纊騭栫祅喼抳犾罧揵檑餺飥邙刕醮羡愡敕兹滚
瘕軺蔲汛幗紱嘽膄崫砡槝糫薏鵟癭菔怔忡㾮鉀梍凞鈺蓪枘掫匾傕卬臧轀沚椴嵒轣噐搞您櫤誐
儗撇儵呕槫鳫裵澑猬杔膛鶴騸菇琇鐲靖杦畆鰩划呿秂瞪弸蕫皤唏凬帮摒棙岼湶砿飃拕泆瀅氂
鬭斁懟阨頥忼擕隳熛鄄渮濰紓蜇阴竽傈轌蕋駉盻禘鄹愀纆絏杇棖鍇騂魋忞葸悾勉忮踧踖紾飪
阼喭僎訒慝柙櫝肸訕耰蓧鼗璩靛坷煮糲
盦惲翬琿龢芩暻犹洳溏沺呴棃筷豬駙珓崁⻆歃賾澂拕泆瀅氂轀鬭斁鐲懟阨頥忼擕隳熛鄄渮纊
鶇濰帮紓幞蜇阴竽傈轌蕋駉盻禘鄹愀纆絏杇棖臧鍇騂魋忞葸悾黻勉忮踧踖絺綌紾飪阼喭僎訒
慝蕢柙櫝肸訕耰蓧鼗璩靛坷煮糲揀

View File

@ -1,72 +0,0 @@
<?php
namespace App\Command;
use App\Entity\KoreanProductionNote;
use App\Entity\KoreanSentenceNote;
use App\Service\AnkiService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand('app:create:korean:production', 'Create new listening Anki Cards')]
class CreateKoreanProductionCommand extends Command
{
public function __construct(
private AnkiService $ankiService,
) {
parent::__construct();
}
protected function configure(): void
{
//$this->addArgument(
// 'count',
// InputArgument::REQUIRED,
// 'Amount of cards to make',
//);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$ksns = $this->ankiService->getAllFromClass(KoreanSentenceNote::class);
$kpns = $this->ankiService->getAllFromClass(KoreanProductionNote::class);
$existentTerms = [];
foreach ($kpns as $productionNote) {
$existentTerms[$productionNote->getTerm()->getKanji()] = null;
}
//$newNotesCount = intval($input->getArgument('count'));
$newProductionNotes = [];
foreach ($ksns as $sentenceNote) {
foreach ($sentenceNote->getTerms() as $term) {
$termStr = $term->getKanji();
if (key_exists($termStr, $existentTerms)) continue;
$existentTerms[$term->getKanji()] = null;
$newProductionNotes[] = KoreanProductionNote::fromNote($sentenceNote, $term);
//if (count($newProductionNotes) >= $newNotesCount) break 2;
}
}
foreach ($newProductionNotes as $newNote) {
$this->ankiService->addNote($newNote);
}
printf(
<<<FMNT
max usage: %0.2f MiB
current usage: %0.2f MiB\n
FMNT,
memory_get_peak_usage() / 1024 / 1024,
memory_get_usage() / 1024 / 1024,
);
return Command::SUCCESS;
}
}

View File

@ -84,7 +84,7 @@ class CreateProductionCommand extends Command
$sNote = $this->ankiService->getNote($noteIds[array_key_last($noteIds)]);
$newSlNote = SentenceListeningNote::fromNote($sNote, $term);
if (!$this->ankiService->addNote($newSlNote)) {
if (!$this->ankiService->addNote($newSlNote, 'production')) {
throw new \Exception('Failed to add note!');
}
}

View File

@ -61,7 +61,7 @@ class KanjiController extends AbstractController
if (!$force and file_exists($cacheFile)) return require $cacheFile;
$ret = ['lists' => []];
$ret = [];
$lists = [
'sn' => $this->anki->getKnownSnKanjiCounts(),
@ -70,8 +70,6 @@ class KanjiController extends AbstractController
'ignored' => $this->getIgnoredList(),
];
$ret['lists'] = $lists;
// @formatter:off
$storedLists = [
'taiwan', 'bushu', 'kyoyou', 'kyuujitai', 'kanken', 'hsk', 'zhcn',
@ -79,19 +77,13 @@ class KanjiController extends AbstractController
];
// @formatter:on
foreach ($storedLists as $storedName) {
$ret['lists'][$storedName] = [];
$storedList = $this->charList->getList($storedName);
// Simple list
if (key_exists('chars', $storedList)) {
$ret['lists'][$storedName] = $storedList['chars'];
$lists[$storedName] = $storedList['chars'];
} else {
foreach ($storedList['sublists'] as $subname => $sublist) {
$ret['lists'][$storedName] = array_merge(
$ret['lists'][$storedName],
$sublist['chars'],
);
$lists["$storedName-$subname"] = $sublist['chars'];
}
}
@ -142,7 +134,6 @@ class KanjiController extends AbstractController
}
return $this->render(self::tmpl('grid'), [
'char_info' => $charInfo,
'characters' => $chars,
'completed' => $completedRows,
]);

View File

@ -1,75 +0,0 @@
<?php
namespace App\Entity;
class KoreanProductionNote extends Note
{
const MODEL_NAME = 'Korean production';
const DECK = '한국어::받아쓰기';
private ?array $mediaInfo = [];
private ?Term $term = null;
public static function fromNote(Note $origNote, Term $term): self
{
$slNote = new self();
foreach (get_object_vars($origNote) as $prop => $value) {
$slNote->$prop = $value;
}
// Related fields are updated using the setter
$slNote->setTerm($term);
// Reset relations and basic data
$slNote->id = null;
$slNote->model = self::MODEL_NAME;
$slNote->cardIds = [];
return $slNote;
}
// -------------------------------------------------- Getters & setters ---
public function getTerm(): Term
{
return $this->term;
}
public function setTerm(Term $term): static
{
$this->fields['Vocab'] = $term->getKanji();
$this->fields['VocabDef'] = $term->toAnkiVocabDef();
$this->fields['VocabAudio'] = $term->audio;
$this->fields['Sent'] = Note::stringHighlight(
$this->fields['Sent'],
$term->getKanji(),
);
$this->term = $term;
return $this;
}
// ------------------------------------------------------- Anki-related ---
/** @param array<string, string> $noteInfo */
public static function fromAnki(array $noteInfo): static
{
$note = parent::fromAnki($noteInfo);
if ($note->getModel() !== self::MODEL_NAME) {
throw new \Exception('Trying to parse wrong model');
}
$note->mediaInfo = Note::parseMediaInfo($note->fields['Notes']);
// Set VocabKanji field
$note->term = Term::fromNoteFields($note->fields)[0] ?? null;
if ($note->term === null) {
throw new \Exception("Couldn't get term for Listening card");
}
return $note;
}
// ---------------------------------------------------- Derived methods ---
}

View File

@ -1,123 +0,0 @@
<?php
namespace App\Entity;
class KoreanSentenceNote extends Note
{
const MODEL_NAME = 'Korean sentences';
private ?array $mediaInfo = [];
/** @var list<Term> */
private array $terms = [];
// -------------------------------------------------- Getters & setters ---
/** @return list<Term> */
public function getTerms(): array
{
return $this->terms;
}
/** @param list<Term> $terms */
public function setTerms(array $terms): static
{
$this->terms = $terms;
return $this;
}
// ------------------------------------------------------- Anki-related ---
/** @param array<string, string> $noteInfo */
public static function fromAnki(array $noteInfo): static
{
$note = parent::fromAnki($noteInfo);
if ($note->getModel() !== self::MODEL_NAME) {
throw new \Exception('Trying to parse wrong model');
}
$note->mediaInfo = Note::parseMediaInfo($note->fields['Notes']);
// Set VocabKanji field
$note->terms = Term::fromNoteFields($note->fields);
// If unable to, create them from the highlighted parts in the sentence
if (empty($note->terms)) {
foreach ($note->getHighlightedKanji() as $highlighedKanji) {
$term = new Term();
$term->kanji = $highlighedKanji;
$term->definitionEn = null;
$term->definitionJp = null;
$note->terms[] = $term;
}
}
return $note;
}
public function toAnki(): array
{
return array_merge(parent::toAnki(), [
'fields' => [
'VocabKanji' => join('', array_map(
fn(Term $x) => $x->getKanji(),
$this->terms,
)),
'VocabFurigana' => join('', array_map(
fn(Term $x) => $x->getReading() ?? '_',
$this->terms,
)),
'VocabDef' => join("<br>\n", array_map(
fn(Term $x) => $x->toAnkiVocabDef(),
$this->terms,
)),
],
]);
}
// ---------------------------------------------------- Derived methods ---
public function hasTerm(string $kanji): bool
{
foreach ($this->terms as $term) {
assert($term instanceof Term);
if ($term->kanji == $kanji) return true;
}
return false;
}
public function isSentKanjiHighlighted(): bool
{
return str_contains(
$this->fields['SentKanji'],
self::HIGHLIGHT_ATTR_KANJI,
);
}
/** Return an array of strings with the highlighted kanji in the SentKanji */
public function getHighlightedKanji(): array
{
$ret = [];
$matches = [];
// 1. Get all spans in the text
preg_match_all(
self::HIGHLIGHT_PATTERN,
$this->fields['SentKanji'],
$matches,
PREG_SET_ORDER,
);
// 2. Check the ones that match with the kanji color
foreach ($matches as $match) {
if ($match[1] === self::HIGHLIGHT_ATTR_KANJI) {
$ret[] = mb_trim($match[2]);
}
}
return $ret;
}
}

View File

@ -98,7 +98,7 @@ class Note
// -------------------------------------------------- Utility functions ---
protected static function stringHighlight(string $haystack, string $needle): string
protected static function stringHighlight(string $haystack, string $needle)
{
$replace = sprintf(
'<span %s>%s</span>',
@ -108,47 +108,4 @@ class Note
return str_replace($needle, $replace, strip_tags($haystack));
}
protected static function parseMediaInfo(string $notes): ?array
{
$matches = null;
// Parse the notes fields. It can be in the form of
// series-name_S01 EP07 (11h22m33s44ms)
// or
// movie-name EP (11h22m33s44ms)
if (1 !== preg_match(
'/(?<name>[0-9A-Za-z\-_]+)(_S)?(?<season>\d*) EP(?<episode>\d*) \((?<time>.*)\)/n',
$notes,
$matches,
)) {
return null;
}
// Remove number-indexed matches, cast numbers to integers
$matches = [
'name' => $matches['name'],
'time' => $matches['time'],
// NOTE: intval returns 0 if not a number, which is false-like
'season' => intval($matches['season']) ?: null,
'episode' => intval($matches['episode']) ?: null,
];
// Parse time into a DateInterval and replace it in the matches array
$time = new \DateInterval('PT0S');
preg_match('/(\d+)ms/', $matches['time'], $milliseconds);
preg_match('/(\d+)s/', $matches['time'], $seconds);
preg_match('/(\d+)m/', $matches['time'], $minutes);
preg_match('/(\d+)h/', $matches['time'], $hours);
if ($milliseconds[1] ?? false) $time->f = $milliseconds[1] * 1000;
if ($seconds[1] ?? false) $time->s = $seconds[1];
if ($minutes[1] ?? false) $time->i = $minutes[1];
if ($hours[1] ?? false) $time->h = $hours[1];
$matches['time'] = $time;
return $matches;
}
}

View File

@ -5,7 +5,6 @@ namespace App\Entity;
class SentenceListeningNote extends Note
{
const MODEL_NAME = 'Japanese sentences listening';
const DECK = '日本語::漢字';
private ?array $mediaInfo = [];
private ?Term $term = null;
@ -21,6 +20,7 @@ class SentenceListeningNote extends Note
$this->fields['VocabKanji'] = $term->getKanji();
$this->fields['VocabFurigana'] = $term->getReading();
$this->fields['VocabDef'] = $term->toAnkiVocabDef();
$this->fields['SentFurigana'] = ''; // We don't want to keep this
$this->fields['SentKanji'] = $this->stringHighlight(
$this->fields['SentKanji'],
$term->getKanji(),
@ -40,7 +40,7 @@ class SentenceListeningNote extends Note
throw new \Exception('Trying to parse wrong model');
}
$note->mediaInfo = Note::parseMediaInfo($note->fields['Notes']);
$note->mediaInfo = self::parseMediaInfo($note->fields['Notes']);
// Set VocabKanji field
$note->term = Term::fromNoteFields($note->fields)[0] ?? null;
@ -51,9 +51,9 @@ class SentenceListeningNote extends Note
return $note;
}
public static function fromNote(Note $origNote, Term $term): self
public static function fromNote(Note $origNote, Term $term): static
{
$slNote = new self();
$slNote = new static();
foreach (get_object_vars($origNote) as $prop => $value) {
$slNote->$prop = $value;
}
@ -83,4 +83,47 @@ class SentenceListeningNote extends Note
self::HIGHLIGHT_ATTR_KANJI,
);
}
private static function parseMediaInfo(string $notes): ?array
{
$matches = null;
// Parse the notes fields. It can be in the form of
// series-name_S01 EP07 (11h22m33s44ms)
// or
// movie-name EP (11h22m33s44ms)
if (1 !== preg_match(
'/(?<name>[0-9A-Za-z\-_]+)(_S)?(?<season>\d*) EP(?<episode>\d*) \((?<time>.*)\)/n',
$notes,
$matches,
)) {
return null;
}
// Remove number-indexed matches, cast numbers to integers
$matches = [
'name' => $matches['name'],
'time' => $matches['time'],
// NOTE: intval returns 0 if not a number, which is false-like
'season' => intval($matches['season']) ?: null,
'episode' => intval($matches['episode']) ?: null,
];
// Parse time into a DateInterval and replace it in the matches array
$time = new \DateInterval('PT0S');
preg_match('/(\d+)ms/', $matches['time'], $milliseconds);
preg_match('/(\d+)s/', $matches['time'], $seconds);
preg_match('/(\d+)m/', $matches['time'], $minutes);
preg_match('/(\d+)h/', $matches['time'], $hours);
if ($milliseconds[1] ?? false) $time->f = $milliseconds[1] * 1000;
if ($seconds[1] ?? false) $time->s = $seconds[1];
if ($minutes[1] ?? false) $time->i = $minutes[1];
if ($hours[1] ?? false) $time->h = $hours[1];
$matches['time'] = $time;
return $matches;
}
}

View File

@ -34,7 +34,7 @@ class SentenceNote extends Note
throw new \Exception('Trying to parse wrong model');
}
$note->mediaInfo = Note::parseMediaInfo($note->fields['Notes']);
$note->mediaInfo = self::parseMediaInfo($note->fields['Notes']);
// Set VocabKanji field
$note->terms = Term::fromNoteFields($note->fields);
@ -133,4 +133,47 @@ class SentenceNote extends Note
return $ret;
}
private static function parseMediaInfo(string $notes): ?array
{
$matches = null;
// Parse the notes fields. It can be in the form of
// series-name_S01 EP07 (11h22m33s44ms)
// or
// movie-name EP (11h22m33s44ms)
if (1 !== preg_match(
'/(?<name>[0-9A-Za-z\-_]+)(_S)?(?<season>\d*) EP(?<episode>\d*) \((?<time>.*)\)/n',
$notes,
$matches,
)) {
return null;
}
// Remove number-indexed matches, cast numbers to integers
$matches = [
'name' => $matches['name'],
'time' => $matches['time'],
// NOTE: intval returns 0 if not a number, which is false-like
'season' => intval($matches['season']) ?: null,
'episode' => intval($matches['episode']) ?: null,
];
// Parse time into a DateInterval and replace it in the matches array
$time = new \DateInterval('PT0S');
preg_match('/(\d+)ms/', $matches['time'], $milliseconds);
preg_match('/(\d+)s/', $matches['time'], $seconds);
preg_match('/(\d+)m/', $matches['time'], $minutes);
preg_match('/(\d+)h/', $matches['time'], $hours);
if ($milliseconds[1] ?? false) $time->f = $milliseconds[1] * 1000;
if ($seconds[1] ?? false) $time->s = $seconds[1];
if ($minutes[1] ?? false) $time->i = $minutes[1];
if ($hours[1] ?? false) $time->h = $hours[1];
$matches['time'] = $time;
return $matches;
}
}

View File

@ -7,7 +7,6 @@ class Term
public ?string $kanji;
public ?string $definitionJp;
public ?string $definitionEn;
public ?string $audio;
public function getReading(): ?string
{
@ -149,8 +148,6 @@ class Term
*/
public static function fromNoteFields(array $fields): array
{
$audios = explode('|', $fields['VocabAudio'] ?? '');
// -------------------- Trying to extract it with the modern syntax ---
// 言葉: word
// 上げる:上に動くこと。
@ -160,22 +157,13 @@ class Term
foreach (preg_split('|<br ?/?>|', $fields['VocabDef']) as $line) {
$terms[] = self::fromVocabDefLine(strip_tags($line));
};
// Assign audio
if (count($audios) === count($terms) and $terms[0] !== null) {
foreach (array_keys($audios) as $key) {
if ($terms[$key] === null) dd($fields);
$terms[$key]->audio = mb_trim(strip_tags($audios[$key]));
}
}
// If there's no nulls, everything went good
if (!in_array(null, $terms, true)) return $terms;
// ------------ Extracting failed, try to infer from other syntaxes ---
$kanjis = explode('', $fields['VocabKanji'] ?? $fields['Vocab'] ?? '');
$kanjis = explode('', $fields['VocabKanji']);
$defs = explode('', $fields['VocabDef']);
// Number of legacy definitions is different from number of kanji
if (count($kanjis) !== count($defs)) return [];

View File

@ -7,7 +7,7 @@ use App\Utils\Number;
class UnicodeNote extends Note
{
const string MODEL_NAME = 'Unicode';
const string DECK = '日本語::unicode';
const string DECK = 'unicode';
private ?int $codepoint = null;
/** @var Term[] */

View File

@ -2,8 +2,6 @@
namespace App\Service;
use App\Entity\KoreanProductionNote;
use App\Entity\KoreanSentenceNote;
use App\Entity\Note;
use App\Entity\SentenceListeningNote;
use App\Entity\SentenceNote;
@ -40,10 +38,8 @@ class AnkiService
/** The note's id is updated on success.
* @return bool True on success
*/
public function addNote(Note &$note, ?string $deckName = null): bool
public function addNote(Note &$note, string $deckName): bool
{
$deckName ??= constant(get_class($note) . '::DECK');
$note->setId($this->request('addNote', ['note' => [
'deckName' => $deckName,
'modelName' => $note->getModel(),
@ -76,24 +72,6 @@ class AnkiService
return $this->request('findNotes', ['query' => $query]);
}
/** @return list<int> */
public function getAllIdsFromClass(string $class): array
{
$query = sprintf('"note:%s"', constant("$class::MODEL_NAME"));
return $this->request('findNotes', ['query' => $query]);
}
/**
* @template T of object
* @param class-string<T> $class
* @return list<T>
*/
public function getAllFromClass(string $class): array
{
$ids = $this->getAllIdsFromClass($class);
return $this->getNotes($ids);
}
public function getAllSentenceNoteIds(): array
{
return $this->request(
@ -141,8 +119,6 @@ class AnkiService
UnicodeNote::MODEL_NAME => UnicodeNote::fromAnki($noteInfo),
SentenceNote::MODEL_NAME => SentenceNote::fromAnki($noteInfo),
SentenceListeningNote::MODEL_NAME => SentenceListeningNote::fromAnki($noteInfo),
KoreanSentenceNote::MODEL_NAME => KoreanSentenceNote::fromAnki($noteInfo),
KoreanProductionNote::MODEL_NAME => KoreanProductionNote::fromAnki($noteInfo),
default => throw new \Exception(sprintf(
'Unrecognized Note "%s" of type "%s"',
$noteInfo['noteId'],

View File

@ -18,7 +18,6 @@ class AppExtension extends AbstractExtension
// Reference: https://twig.symfony.com/doc/3.x/advanced.html#automatic-escaping
new TwigFilter('basename', basename(...)),
new TwigFilter('ruby', self::ankiRubyToHtml(...)),
new TwigFilter('intersect_key', array_intersect_key(...))
];
}

View File

@ -141,14 +141,5 @@
{% endif %}
{% endfor %}
</div>
<div class="bg-body border-top p-2 sticky-bottom">
{% set learnt_kanken = char_info.lists.unicode|intersect_key(char_info.lists.kanken)|length %}
{% set total_kanken = char_info.lists.kanken|length %}
Kanken {{ learnt_kanken }}/{{ total_kanken }} ({{ ((learnt_kanken/total_kanken) * 100)|number_format(2) }}%)
|
{% set sn_kanken = char_info.lists.sn|intersect_key(char_info.lists.kanken)|length %}
{{ sn_kanken }}/{{ total_kanken }} ({{ ((sn_kanken/total_kanken) * 100)|number_format(2) }}%)
</div>
</div>
{% endblock %}

View File

@ -39,26 +39,6 @@
Wiktionary JA
</a>
</li>
<li>
<a
class="text-primary"
href="{{ 'https://ja.wikipedia.org/w/index.php?' ~ {
search: char
}|url_encode }}"
>
Wikipedia JA
</a>
</li>
<li>
<a
class="text-primary"
href="{{ 'https://zh.wikipedia.org/w/index.php?' ~ {
search: char
}|url_encode }}"
>
Wikipedia ZH
</a>
</li>
<li>
<a
class="text-primary"