PGNs([]);
const chapterHeader = ref("White");
const lineHeader = ref("Black");
+const lineOrientation = ref("white");
const query = db.selectFrom("studies").select(["id", "name"]).where("id", "=", Number(route.params.studyId)).compile();
const study = await selectFirst(query);
@@ -63,58 +76,96 @@ function onSubmit() {
});
}
+// TODO: will currently only work with mainline
async function processPgn(pgn: string) {
- const game = new Chess();
- game.loadPgn(pgn);
+ const games = parsePgn(pgn);
+ games.forEach(async (game) => {
+ const pos = startingPosition(game.headers).unwrap();
+ const headers = game.headers;
+ const chapterName = headers.get(chapterHeader.value) ?? "";
+ const lineName = headers.get(lineHeader.value) ?? "";
- const headers = game.header();
- const chapterName = headers[chapterHeader.value];
- const lineName = lineHeader.value ? headers[lineHeader.value] : "";
+ let moves = "";
+ for (const node of game.moves.mainline()) {
+ moves = `${moves} ${node.san}`;
+ }
- // TODO same function in game index
- const history = game.history({ verbose: true });
- const moves = history
- .reduce((acc, move) => {
- return `${acc} ${move.san}`;
- }, "")
- .trim();
+ const chapterQuery = db
+ .insertInto("chapters")
+ .values({
+ name: chapterName,
+ study: Number(route.params.studyId),
+ })
+ .onConflict((oc) => oc.columns(["study", "name"]).doNothing())
+ .compile();
+ const chapterQueryResult = await execute(chapterQuery);
- const chapterQuery = db
- .insertInto("chapters")
- .values({
- name: chapterName,
- study: Number(route.params.studyId),
- })
- .onConflict((oc) => oc.columns(["study", "name"]).doNothing())
- .compile();
+ let chapterId: number;
+ if (chapterQueryResult.rowsAffected === 0) {
+ const chapterQuery = db
+ .selectFrom("chapters")
+ .select("id")
+ .where("study", "=", Number(route.params.studyId))
+ .where("name", "=", chapterName)
+ .compile();
+ chapterId = (await selectFirst(chapterQuery)).id;
+ } else {
+ chapterId = chapterQueryResult.lastInsertId;
+ }
- const chapterId = (await execute(chapterQuery)).lastInsertId;
+ const lineQuery = db
+ .insertInto("lines")
+ .values({
+ study: Number(route.params.studyId),
+ chapter: chapterId,
+ name: lineName,
+ pgn: makePgn(game),
+ moves,
+ orientation: lineOrientation.value,
+ })
+ .compile();
+ const lineQueryResult = await execute(lineQuery);
- const lineQuery = db
- .insertInto("lines")
- .values({ study: Number(route.params.studyId), chapter: chapterId, name: lineName, pgn, moves })
- .onConflict((oc) => oc.columns(["chapter", "moves"]).doNothing())
- .compile();
- const lineId = (await execute(lineQuery)).lastInsertId;
+ let lineId: number;
+ if (lineQueryResult.rowsAffected === 0) {
+ const lineQuery = db
+ .selectFrom("lines")
+ .select("id")
+ .where("chapter", "=", chapterId)
+ .where("name", "=", lineName)
+ .compile();
+ lineId = (await selectFirst(lineQuery)).id;
+ } else {
+ lineId = lineQueryResult.lastInsertId;
+ }
- const positions = history.map((move) => {
- return {
- fen: move.before,
- source: move.from,
- destination: move.to,
- san: move.san,
- study: Number(route.params.studyId),
- chapter: chapterId,
- line: lineId,
- };
- });
+ let positions = [];
+ for (const node of game.moves.mainline()) {
+ const move = parseSan(pos, node.san) as NormalMove;
+ if (!move) {
+ console.error("mainline includes illegal moves");
+ break;
+ }
+ positions.push({
+ fen: makeFen(pos.toSetup()),
+ source: makeSquare(move.from),
+ destination: makeSquare(move.to),
+ san: node.san,
+ study: Number(route.params.studyId),
+ chapter: chapterId,
+ line: lineId,
+ });
+ pos.play(move);
+ }
- const positionsQuery = db
- .insertInto("positions")
- .values(positions)
- .onConflict((oc) => oc.columns(["line", "fen"]).doNothing())
- .compile();
- execute(positionsQuery);
+ if (positions.length === 0) return;
+ const positionsQuery = db
+ .insertInto("positions")
+ .values(positions)
+ .onConflict((oc) => oc.columns(["line", "fen"]).doNothing())
+ .compile();
+ execute(positionsQuery);
+ });
}
definePage({
diff --git a/src/utilities/comment.ts b/src/utilities/comment.ts
new file mode 100644
index 0000000..bbc8046
--- /dev/null
+++ b/src/utilities/comment.ts
@@ -0,0 +1,20 @@
+export function formatComment(comment: string) {
+ // strip html tags including content assuming any html is malicious
+ const htmlPattern = /<([^> ]+)[^<>]*?>[^<>]*?<\/\1>/g;
+ let comment_ = comment.replace(htmlPattern, "");
+ // strip custom pgn comments
+ const customCommentPattern = /\[[^\]]+?\]/g;
+ comment_ = comment_.replace(customCommentPattern, "");
+ // unescape brackets
+ comment_ = comment_.replaceAll("@@StartBracket@@", "(").replaceAll("@@EndBracket@@", ")");
+ // remove fen information
+ const fenPattern = /@@StartFEN@@[^@]+?@@EndFEN@@/g;
+ comment_ = comment_.replaceAll(fenPattern, "");
+ // highlight chess moves
+ const chessMovePattern = /(([\d]{0,3}\.)?(\.{2,3})?[KQBNRP]?[a-h]?[1-8]?[x]?[a-h][1-8](=[NBRQK])?[+#]?)|0-0(-0)?/g;
+ comment_.replace(chessMovePattern, "
$1");
+
+ comment_ = comment_.trim();
+
+ return comment_ === "" ? undefined : comment_;
+}
diff --git a/src/utilities/move.ts b/src/utilities/move.ts
index 647cb11..0224380 100644
--- a/src/utilities/move.ts
+++ b/src/utilities/move.ts
@@ -1,6 +1,8 @@
import { ChessMove } from "@composables/useGameTree";
-import { Chess, Color, Move, PieceSymbol } from "chess.js";
-import { Color as CgColor, Dests, Key, Role } from "chessground/types";
+import { Key, Role } from "chessground/types";
+import { Chess } from "chessops/chess";
+import { chessgroundDests } from "chessops/compat";
+import { parseFen } from "chessops/fen";
export function isEnPassant(source: Key, destination: Key, pieceOnDestination: Role | undefined) {
return source[0] !== destination[0] && pieceOnDestination === "pawn";
@@ -15,52 +17,43 @@ export function toSAN(move: ChessMove) {
}
export function getPossibleMoves(fen: string) {
- const chess = new Chess();
- chess.load(fen);
-
- return toPossibleMoves(chess.moves({ verbose: true }));
-}
-
-export function toPossibleMoves(moves: Move[]): Dests {
- return moves.reduce((acc, move) => {
- if (!acc.has(move.from)) return acc.set(move.from, [move.to]);
- const dests = acc.get(move.from);
- dests?.push(move.to);
- return acc.set(move.from, dests!);
- }, new Map
());
-}
-
-export function toRole(piece: PieceSymbol): Role {
- const roles: Record = {
- q: "queen",
- r: "rook",
- b: "bishop",
- n: "knight",
- p: "pawn",
- k: "king",
- };
-
- return roles[piece];
-}
-
-export function toPiece(role: Role): PieceSymbol {
- const pieces: Record = {
- queen: "q",
- rook: "r",
- bishop: "b",
- knight: "n",
- pawn: "p",
- king: "k",
- };
-
- return pieces[role];
-}
-
-export function toColor(color: Color): CgColor {
- const colors: Record = {
- w: "white",
- b: "black",
- };
-
- return colors[color];
-}
+ const setup = parseFen(fen).unwrap();
+ const position = Chess.fromSetup(setup).unwrap();
+
+ return chessgroundDests(position);
+}
+
+// export function toRole(piece: PieceSymbol): Role {
+// const roles: Record = {
+// q: "queen",
+// r: "rook",
+// b: "bishop",
+// n: "knight",
+// p: "pawn",
+// k: "king",
+// };
+
+// return roles[piece];
+// }
+
+// export function toPiece(role: Role): PieceSymbol {
+// const pieces: Record = {
+// queen: "q",
+// rook: "r",
+// bishop: "b",
+// knight: "n",
+// pawn: "p",
+// king: "k",
+// };
+
+// return pieces[role];
+// }
+
+// export function toColor(color: Color): CgColor {
+// const colors: Record = {
+// w: "white",
+// b: "black",
+// };
+
+// return colors[color];
+// }
diff --git a/tailwind.config.cjs b/tailwind.config.cjs
index 7ba8d96..289073e 100644
--- a/tailwind.config.cjs
+++ b/tailwind.config.cjs
@@ -17,6 +17,6 @@ module.exports = {
},
},
},
- plugins: [require("@tailwindcss/forms"), require("tailwind-scrollbar")],
+ plugins: [require("@tailwindcss/forms")],
darkMode: ["class", '[data-theme="dark"]'],
};
diff --git a/vitest.config.ts b/vitest.config.ts
index 77a73cf..0fe16ca 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -1,5 +1,7 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
- test: {},
+ test: {
+ include: ["src/**/*.spec.ts"],
+ },
});