1
0
mirror of https://github.com/vbalien/voca.git synced 2026-01-26 20:36:21 +09:00

refactoring

This commit is contained in:
2022-02-26 03:16:54 +09:00
parent 0bc8aee9ff
commit 0467dd90e1
17 changed files with 47320 additions and 165 deletions

52
src/crawl.ts Normal file
View File

@@ -0,0 +1,52 @@
import { path } from "./deps.ts";
import { JSONType, Voca } from "./types.ts";
const __dirname = path.dirname(path.fromFileUrl(import.meta.url));
async function getVocaList(levels: number[], day: number): Promise<Voca[]> {
const re =
/<p class="word">(?:\d+?)\. (?<word>.+?)<\/p>.*?<span class="af_answer">(?<answer>.+?)<\/span>/gis;
const res = await fetch(
`https://www.hackers.co.kr/?c=s_toeic/new_voca_toeic_testpaper/toeic_study/new_paper&mode=new_view&level=${
levels.join(",")
}&level_type=&lang_text=2&question=1000&day3=${day}&day4=${day}&day_auto=N&index=1`,
);
const body = await res.text();
const matches = [...body.matchAll(re)].map((m) => ({
...(m.groups as unknown as Voca),
}));
return matches;
}
const levels = [6, 7, 8, 9];
const days = [...Array(30)].map((_, i) => i + 1);
const result: JSONType = { data: [] };
for (const level of levels) {
for (const day of days) {
console.log(`GET: ${level}-${day}`);
const voca_list = await getVocaList([level], day);
voca_list.sort((a, b) => {
const wordA = a.word.toUpperCase();
const wordB = b.word.toUpperCase();
if (wordA < wordB) {
return -1;
}
if (wordA > wordB) {
return 1;
}
return 0;
});
result.data.push({
level,
day,
voca_list,
});
}
}
Deno.writeTextFileSync(
path.join(__dirname, "../doc/", "voca.json"),
JSON.stringify(result),
);

2
src/deps.ts Normal file
View File

@@ -0,0 +1,2 @@
export * as path from "https://deno.land/std@0.127.0/path/mod.ts";
export * from "./website/deps.ts";

14
src/types.ts Normal file
View File

@@ -0,0 +1,14 @@
export type Voca = {
word: string;
answer: string;
};
export type VocaChunk = {
level: number;
day: number;
voca_list: Voca[];
};
export type JSONType = {
data: VocaChunk[];
};

10
src/website/app.tsx Normal file
View File

@@ -0,0 +1,10 @@
import { React } from "./deps.ts";
import DownloadForm from "./components/download-form.tsx";
export default function App() {
return (
<div>
<DownloadForm />
</div>
);
}

View File

@@ -0,0 +1,99 @@
import { React } from "../deps.ts";
import { downloadVocaQuiz } from "../pdf.ts";
export default function DownloadForm() {
const [levels, setLevels] = React.useState<number[]>([6, 7]);
const [day, setDay] = React.useState<number>(1);
const handleSubmit: React.FormEventHandler = React.useCallback((ev) => {
downloadVocaQuiz(levels.sort(), day);
ev.preventDefault();
}, [levels, day]);
const handleDayChange: React.FormEventHandler<HTMLSelectElement> = (ev) => {
const select = ev.target as HTMLSelectElement;
setDay(Number.parseInt(select.value));
};
const handleLevelChange: React.FormEventHandler<HTMLInputElement> = (ev) => {
const checkbox = ev.target as HTMLInputElement;
if (checkbox.checked) {
setLevels((prev) => [...prev, Number.parseInt(checkbox.value)]);
} else {
setLevels((prev) =>
prev.filter((val) => val !== Number.parseInt(checkbox.value))
);
}
};
return (
<>
<form onSubmit={handleSubmit}>
<fieldset>
<legend> </legend>
<div>
<input
type="checkbox"
id="10"
value="10"
checked={levels.find((val) => val === 10) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="10"> </label>
</div>
<div>
<input
type="checkbox"
id="6"
value="6"
checked={levels.find((val) => val === 6) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="6">(550+), (650+)</label>
</div>
<div>
<input
type="checkbox"
id="7"
value="7"
checked={levels.find((val) => val === 7) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="7">(750+)</label>
</div>
<div>
<input
type="checkbox"
id="8"
value="8"
checked={levels.find((val) => val === 8) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="8">(850+)</label>
</div>
<div>
<input
type="checkbox"
id="9"
value="9"
checked={levels.find((val) => val === 9) !== undefined}
onChange={handleLevelChange}
/>
<label htmlFor="9">(900+)</label>
</div>
</fieldset>
<div>
<label htmlFor="day"></label>
<select id="day" value={day} onChange={handleDayChange}>
{[...Array(30)].map((_, i) => <option value={i + 1}>{i + 1}
</option>)}
</select>
</div>
<div>
<button type="submit">PDF </button>
</div>
</form>
</>
);
}

5
src/website/deps.ts Normal file
View File

@@ -0,0 +1,5 @@
export { PDFDocument, PDFPage, rgb } from "https://esm.sh/pdf-lib@^1.11.1";
export { default as fontkit } from "https://esm.sh/@pdf-lib/fontkit@^1.0.0";
export { default as React } from "https://esm.sh/react@17";
export { default as ReactDOM } from "https://esm.sh/react-dom@17";
export { default as shuffle } from "https://esm.sh/lodash@4.2/shuffle?no-check";

7
src/website/mod.ts Normal file
View File

@@ -0,0 +1,7 @@
import { ReactDOM } from "./deps.ts";
import app from "./app.tsx";
export function render() {
const rootEl = document.getElementById("root");
ReactDOM.render(app(), rootEl);
}

110
src/website/pdf.ts Normal file
View File

@@ -0,0 +1,110 @@
import { fontkit, PDFDocument, PDFPage, rgb, shuffle } from "./deps.ts";
import { JSONType, Voca } from "../types.ts";
function downloadBuffer(buffer: Uint8Array, fileName: string) {
const a: HTMLAnchorElement = document.createElement("a");
const blob = new Blob([buffer], { type: "application/octet-stream" });
a.href = URL.createObjectURL(blob);
a.download = fileName;
document.body.appendChild(a);
a.style.display = "none";
a.click();
a.remove();
}
export async function makePdf(voca_list: Voca[]) {
const pdfDoc = await PDFDocument.create();
const fontBytes = await fetch("./NanumGothic.ttf").then((res) =>
res.arrayBuffer()
);
pdfDoc.registerFontkit(fontkit);
const customFont = await pdfDoc.embedFont(fontBytes);
const fontSize = 12;
const margin = 20;
const perColumn = 25;
const perPage = 50;
let page!: PDFPage;
let col = -1;
let yCursor = 1;
// 문제 생성
for (let i = 0; i < voca_list.length; ++i) {
const data = voca_list[i];
if (i % perPage === 0) {
page = pdfDoc.addPage();
yCursor = 1;
col = -1;
}
if (i % perColumn === 0) {
col++;
yCursor = 1;
}
page.drawText(`${i + 1}. ${data.word}`, {
x: margin + (page.getWidth() / 2) * col,
y: page.getHeight() - (fontSize + margin) * yCursor,
size: fontSize,
font: customFont,
color: rgb(0, 0, 0),
});
page.drawText("_________________", {
x: margin + (page.getWidth() / 2) * col + 140,
y: page.getHeight() - (fontSize + margin) * yCursor - 3,
size: fontSize,
font: customFont,
color: rgb(0, 0, 0),
});
yCursor++;
}
// 답 생성
for (let i = 0; i < voca_list.length; ++i) {
const data = voca_list[i];
if (i % perPage === 0) {
page = pdfDoc.addPage();
yCursor = 1;
col = -1;
}
if (i % perColumn === 0) {
col++;
yCursor = 1;
}
page.drawText(`${i + 1}. ${data.word}`, {
x: margin + (page.getWidth() / 2) * col,
y: page.getHeight() - (fontSize + margin) * yCursor,
size: fontSize,
font: customFont,
color: rgb(0, 0, 0),
});
page.drawText("_________________", {
x: margin + (page.getWidth() / 2) * col + 140,
y: page.getHeight() - (fontSize + margin) * yCursor - 3,
size: fontSize,
font: customFont,
color: rgb(0, 0, 0),
});
page.drawText(data.answer, {
x: margin + (page.getWidth() / 2) * col + 140,
y: page.getHeight() - (fontSize + margin) * yCursor,
size: 8,
font: customFont,
color: rgb(1, 0, 0),
});
yCursor++;
}
const pdfBytes = await pdfDoc.save();
return pdfBytes;
}
export async function downloadVocaQuiz(levels: number[], day: number) {
const json: JSONType = await (await fetch("/voca.json")).json();
const voca_list = json.data.filter((chunk) =>
levels.includes(chunk.level) && chunk.day === day
).flatMap((chunk) => chunk.voca_list);
const bufferPdf = await makePdf(shuffle(voca_list));
downloadBuffer(bufferPdf, "voca.pdf");
}