chore: readme, license and co.

This commit is contained in:
chrislicodes 2023-05-17 21:02:43 +02:00
parent f4302c7d0f
commit aebb62427d
24 changed files with 395 additions and 300 deletions

6
.prettierrc Normal file
View file

@ -0,0 +1,6 @@
{
"singleQuote": true,
"tabWidth": 2,
"proseWrap": "preserve",
"semi": true
}

21
LICENSE.txt Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Christoph Lindstädt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1 +1,94 @@
# Obsidian Chessify
<!-- omit in toc -->
# Chessify
> A chess study helper and PGN viewer/editor for [Obsidian](https://obsidian.md/).
With this plugin, you can either import PGNs or simply start a fresh new game. It allows you to add comments and arrows for each move, which will be persisted within a JSON File in your vault. Although it is not a full analysis board, it serves as a valuable tool to support your chess notetaking in [Obsidian](https://obsidian.md/).
<!-- omit in toc -->
## Table of contents
- [Motivation](#motivation)
- [Installation](#installation)
- [Usage](#usage)
- [Features](#features)
- [Settings](#settings)
- [Roadmap](#roadmap)
- [Tools Used](#tools-used)
- [Alternatives](#alternatives)
- [License](#license)
## Motivation
I love [Obsidian](https://obsidian.md/) and its tools, but managing screenshots of chess positions is getting out of hand and is also annoying to update. It would be great to have a PGN Viewer similar to the ones offered by [lichess.org](https://lichess.org/) or [chess.com](https://chess.com/) within [Obsidian](https://obsidian.md/). It should allow us to effortlessly view PGNs, add arrows and comments, while also persisting them inside the [Obsidian](https://obsidian.md/) vault. The currently existing [alternatives](#alternatives) primarily focus on visualizing FEN snapshots, which is why this side project started.
## Installation
Once you have disabled Safe Mode, you can find third-party plugins by navigating to Settings > Community Plugins > Browse and search for "Chessify". Once you have installed the plugin, you can access it under Settings > Community Plugins. Please note that you must enable the plugin to use it. You can also unsintall the plugin from there.
## Usage
To start, position your cursor where you want to place the PGN viewer/editor within your note. Execute the Obsidian command `Chessify: Insert PGN-Editor at cursor position`.
This action will trigger a modal window, where you have the option to paste your PGN or leave it empty for a fresh new game. Here is what it will look like:
![chessify-modal](imgs/chessify-modal.png)
Once you click `Submit`, Chessify will parse the PGN, generate a new JSON file in your vault located at `.obsidian/plugins/obsidian-chessify/storage/{id}.json`, and insert a chessify codeblock at the cursor's position. Here's an example of the chessify codeblock:
![chessify-codeblock](imgs/chessify-codeblock.png)
After that the PGN viewer/editor will render and you are good to go:
![chessify-codeblock](imgs/chessify-demo.gif)
## Features
- [x] Import PGNs
- [x] Store game state in JSON
- [x] Add custom PGN viewer
- [x] Only allow legal moves
- [x] Move through the moves via navigation buttons and direct click
- [x] Draw and sync shapes
- [x] Add and sync comments with Markdown support
## Settings
Here are the available settings for a `chessify` code block:
| Setting | Possible Values | Description |
| ------------------ | ------------------ | ------------------------------------------------ |
| `chessifyId` | Valid nanoid | Valid ID for a file stored in the plugin storage |
| `boardOrientation` | `white` \| `black` | Orientation of the board |
| `boardColor` | `green` \| `brown` | Color of the board |
You can permanently set some settings in the [Obsidian](https://obsidian.md/) plugin settings for Chessify.
## Roadmap
- [ ] Add support for variants
- [ ] Add view to manage stored games
- [ ] Add more styles
- [ ] Add more settings
## Tools Used
- Chess visuals are powered by [Chessground](https://github.com/lichess-org/chessground)
- Chess logic is powered by [Chess.js](https://github.com/jhlywa/chess.js)
- The markdown editor is powered by [TipTap](https://github.com/ueberdosis/tiptap)
- Icons are provided by [Lucide](https://github.com/lucide-icons/lucide)
- Everything is tied together by [React](https://github.com/facebook/react)
## Alternatives
If you want to have a look at FENs instead, check out these alternative Obsidian plugins:
- [SilentVoid13/Chesser](https://github.com/SilentVoid13/Chesser)
- [pmorim/obsidian-chess](https://github.com/pmorim/obsidian-chess)
- [THeK3nger/obsidian-chessboard](https://github.com/THeK3nger/obsidian-chessboard)
## License
[Chessify](https://github.com/chrislicodes/obsidian-chessify) is licensed under the MIT license. Refer to [LICENSE](https://github.com/chrislicodes/obsidian-chessify/blob/trunk/LICENSE.TXT) for more informations.

View file

@ -1,3 +1,3 @@
.green-board cg-board {
background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4PSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIgogICAgIHZpZXdCb3g9IjAgMCA4IDgiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyI+CjxnIGlkPSJhIj4KICA8ZyBpZD0iYiI+CiAgICA8ZyBpZD0iYyI+CiAgICAgIDxnIGlkPSJkIj4KICAgICAgICA8cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSIjZmZmZmRkIiBpZD0iZSIvPgogICAgICAgIDx1c2UgeD0iMSIgeT0iMSIgaHJlZj0iI2UiIHg6aHJlZj0iI2UiLz4KICAgICAgICA8cmVjdCB5PSIxIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSIjODZhNjY2IiBpZD0iZiIvPgogICAgICAgIDx1c2UgeD0iMSIgeT0iLTEiIGhyZWY9IiNmIiB4OmhyZWY9IiNmIi8+CiAgICAgIDwvZz4KICAgICAgPHVzZSB4PSIyIiBocmVmPSIjZCIgeDpocmVmPSIjZCIvPgogICAgPC9nPgogICAgPHVzZSB4PSI0IiBocmVmPSIjYyIgeDpocmVmPSIjYyIvPgogIDwvZz4KICA8dXNlIHk9IjIiIGhyZWY9IiNiIiB4OmhyZWY9IiNiIi8+CjwvZz4KPHVzZSB5PSI0IiBocmVmPSIjYSIgeDpocmVmPSIjYSIvPgo8L3N2Zz4K");
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4PSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIgogICAgIHZpZXdCb3g9IjAgMCA4IDgiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyI+CjxnIGlkPSJhIj4KICA8ZyBpZD0iYiI+CiAgICA8ZyBpZD0iYyI+CiAgICAgIDxnIGlkPSJkIj4KICAgICAgICA8cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSIjZmZmZmRkIiBpZD0iZSIvPgogICAgICAgIDx1c2UgeD0iMSIgeT0iMSIgaHJlZj0iI2UiIHg6aHJlZj0iI2UiLz4KICAgICAgICA8cmVjdCB5PSIxIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSIjODZhNjY2IiBpZD0iZiIvPgogICAgICAgIDx1c2UgeD0iMSIgeT0iLTEiIGhyZWY9IiNmIiB4OmhyZWY9IiNmIi8+CiAgICAgIDwvZz4KICAgICAgPHVzZSB4PSIyIiBocmVmPSIjZCIgeDpocmVmPSIjZCIvPgogICAgPC9nPgogICAgPHVzZSB4PSI0IiBocmVmPSIjYyIgeDpocmVmPSIjYyIvPgogIDwvZz4KICA8dXNlIHk9IjIiIGhyZWY9IiNiIiB4OmhyZWY9IiNiIi8+CjwvZz4KPHVzZSB5PSI0IiBocmVmPSIjYSIgeDpocmVmPSIjYSIvPgo8L3N2Zz4K');
}

View file

@ -1,7 +1,7 @@
import builtins from "builtin-modules";
import esbuild from "esbuild";
import process from "process";
import { renameStyles } from "./rename-styles.mjs";
import builtins from 'builtin-modules';
import esbuild from 'esbuild';
import process from 'process';
import { renameStyles } from './rename-styles.mjs';
const banner = `/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
@ -9,36 +9,36 @@ if you want to view the source, please visit the github repository of this plugi
*/
`;
const prod = process.argv[2] === "production";
const prod = process.argv[2] === 'production';
const context = await esbuild.context({
banner: {
js: banner,
},
entryPoints: ["src/main.tsx"],
entryPoints: ['src/main.tsx'],
bundle: true,
external: [
"obsidian",
"electron",
"@codemirror/autocomplete",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/search",
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
'obsidian',
'electron',
'@codemirror/autocomplete',
'@codemirror/collab',
'@codemirror/commands',
'@codemirror/language',
'@codemirror/lint',
'@codemirror/search',
'@codemirror/state',
'@codemirror/view',
'@lezer/common',
'@lezer/highlight',
'@lezer/lr',
...builtins,
],
format: "cjs",
target: "es2018",
logLevel: "info",
sourcemap: prod ? false : "inline",
format: 'cjs',
target: 'es2018',
logLevel: 'info',
sourcemap: prod ? false : 'inline',
treeShaking: true,
outfile: "main.js",
outfile: 'main.js',
plugins: [renameStyles],
});

BIN
imgs/chessify-codeblock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
imgs/chessify-demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
imgs/chessify-modal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -1,10 +1,10 @@
{
"id": "obsidian-chessify",
"name": "Chessify",
"version": "0.0.1",
"version": "0.1.0",
"minAppVersion": "0.15.0",
"description": "A chess study helper for Obsidian (https://obsidian.md)",
"author": "chrislicodes",
"description": "A chess study helper and PGN viewer for Obsidian.",
"author": "Christoph Lindstädt",
"authorUrl": "https://github.com/chrislicodes",
"isDesktopOnly": false
}

View file

@ -1,10 +1,12 @@
{
"name": "obsidian-chessify",
"version": "0.0.1",
"description": "A chess study helper for Obsidian (https://obsidian.md)",
"version": "0.1.0",
"description": "A chess study helper and PGN viewer for Obsidian",
"keywords": [
"chess",
"obsidian"
"obsidian",
"PGN",
"study"
],
"license": "MIT",
"author": "Christoph Lindstädt",

View file

@ -1,14 +1,14 @@
import { existsSync, renameSync } from "fs";
import { existsSync, renameSync } from 'fs';
export const renameStyles = {
name: "rename-styles",
name: 'rename-styles',
setup(build) {
build.onEnd(() => {
const { outfile } = build.initialOptions;
const outcss = outfile.replace(/\.js$/, ".css");
const fixcss = outfile.replace(/main\.js$/, "styles.css");
const outcss = outfile.replace(/\.js$/, '.css');
const fixcss = outfile.replace(/main\.js$/, 'styles.css');
if (existsSync(outcss)) {
console.log("Renaming", outcss, "to", fixcss);
console.log('Renaming', outcss, 'to', fixcss);
renameSync(outcss, fixcss);
}
});

View file

@ -1,47 +0,0 @@
/*
Josh's Custom CSS Reset
https://www.joshwcomeau.com/css/custom-css-reset/
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
* {
margin: 0;
}
html,
body {
height: 100%;
}
body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
input,
button,
textarea,
select {
font: inherit;
}
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}
#root,
#__next {
isolation: isolate;
}

View file

@ -1,9 +1,9 @@
import { App, MarkdownRenderChild } from "obsidian";
import * as React from "react";
import * as ReactDOM from "react-dom/client";
import { ChessifyDataAdapter, ChessifyFileData } from "src/utils";
import { Chessify } from "../components/react/Chessify";
import { ChessifyPluginSettings } from "./obsidian/SettingsTab";
import { App, MarkdownRenderChild } from 'obsidian';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { ChessifyDataAdapter, ChessifyFileData } from 'src/utils';
import { Chessify } from '../components/react/Chessify';
import { ChessifyPluginSettings } from './obsidian/SettingsTab';
export class ReactView extends MarkdownRenderChild {
root: ReactDOM.Root;

View file

@ -1,4 +1,4 @@
import { App, Modal, Setting } from "obsidian";
import { App, Modal, Setting } from 'obsidian';
export class PgnModal extends Modal {
pgn: string;
@ -12,21 +12,21 @@ export class PgnModal extends Modal {
onOpen() {
const { contentEl } = this;
contentEl.createEl("h1", {
text: "Paste the full PGN (leave empty for a new game):",
contentEl.createEl('h1', {
text: 'Paste the full PGN (leave empty for a new game):',
});
new Setting(contentEl).setName("PGN").addTextArea((text) =>
new Setting(contentEl).setName('PGN').addTextArea((text) =>
text
.onChange((value) => {
this.pgn = value;
})
.inputEl.setCssStyles({ width: "100%", height: "250px" })
.inputEl.setCssStyles({ width: '100%', height: '250px' })
);
new Setting(contentEl).addButton((btn) =>
btn
.setButtonText("Submit")
.setButtonText('Submit')
.setCta()
.onClick(() => {
this.close();

View file

@ -1,14 +1,14 @@
import { App, PluginSettingTab, Setting } from "obsidian";
import ChessifyPlugin from "src/main";
import { App, PluginSettingTab, Setting } from 'obsidian';
import ChessifyPlugin from 'src/main';
export interface ChessifyPluginSettings {
boardOrientation: "white" | "black";
boardColor: "green" | "brown";
boardOrientation: 'white' | 'black';
boardColor: 'green' | 'brown';
}
export const DEFAULT_SETTINGS: ChessifyPluginSettings = {
boardOrientation: "white",
boardColor: "green",
boardOrientation: 'white',
boardColor: 'green',
};
export class SettingsTab extends PluginSettingTab {
@ -24,38 +24,36 @@ export class SettingsTab extends PluginSettingTab {
containerEl.empty();
containerEl.createEl("h2", { text: "Obsidian Chessify Settings" });
containerEl.createEl('h2', { text: 'Obsidian Chessify Settings' });
new Setting(containerEl)
.setName("Board Orientation")
.setDesc("Sets the default orientation of the board")
.setName('Board Orientation')
.setDesc('Sets the default orientation of the board')
.addDropdown((dropdown) => {
dropdown.addOption("white", "White");
dropdown.addOption("black", "Black");
dropdown.addOption('white', 'White');
dropdown.addOption('black', 'Black');
dropdown
.setValue(this.plugin.settings.boardOrientation)
.onChange((orientation) => {
this.plugin.settings.boardOrientation = orientation as
| "white"
| "black";
| 'white'
| 'black';
this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName("Board Color")
.setDesc("Sets the default color of the board")
.setName('Board Color')
.setDesc('Sets the default color of the board')
.addDropdown((dropdown) => {
dropdown.addOption("green", "Green");
dropdown.addOption("brown", "Brown");
dropdown.addOption('green', 'Green');
dropdown.addOption('brown', 'Brown');
dropdown
.setValue(this.plugin.settings.boardColor)
.onChange((boardColor) => {
this.plugin.settings.boardColor = boardColor as
| "green"
| "brown";
this.plugin.settings.boardColor = boardColor as 'green' | 'brown';
this.plugin.saveSettings();
});
});

View file

@ -1,19 +1,19 @@
import { Chessground as ChessgroundApi } from "chessground";
import * as React from "react";
import { useEffect, useRef } from "react";
import { Chessground as ChessgroundApi } from 'chessground';
import * as React from 'react';
import { useEffect, useRef } from 'react';
import { Chess, Move } from "chess.js";
import { Api } from "chessground/api";
import { Config } from "chessground/config";
import { DrawShape } from "chessground/draw";
import { playOtherSide, toColor, toDests } from "src/utils";
import { Chess, Move } from 'chess.js';
import { Api } from 'chessground/api';
import { Config } from 'chessground/config';
import { DrawShape } from 'chessground/draw';
import { playOtherSide, toColor, toDests } from 'src/utils';
export interface ChessGroundSettings {
api: Api | null;
setApi: React.Dispatch<React.SetStateAction<Api>>;
chessifyId: string;
config?: Config;
boardColor?: "brown" | "green";
boardColor?: 'brown' | 'green';
chess: Chess;
setHistory: React.Dispatch<React.SetStateAction<Move[]>>;
setShapes: React.Dispatch<React.SetStateAction<DrawShape[][]>>;
@ -28,7 +28,7 @@ export const ChessgroundWrapper = React.memo(
api,
setApi,
chessifyId,
boardColor = "green",
boardColor = 'green',
config = {},
chess,
setHistory,
@ -54,10 +54,7 @@ export const ChessgroundWrapper = React.memo(
events: {
//Hook up the Chessground UI changes to our App State
after: (orig, dest, _metadata) => {
const handler = playOtherSide(
chessgroundApi,
chess
);
const handler = playOtherSide(chessgroundApi, chess);
setHistory(handler(orig, dest));
setMoveNumber((state) => state + 1);
@ -119,18 +116,17 @@ export const ChessgroundWrapper = React.memo(
return (
<div
style={{
height: "100%",
width: "100%",
height: '100%',
width: '100%',
}}
className={`${boardColor}-board`}
>
<div
ref={ref}
style={{
height: "100%",
width: "100%",
display:
"table" /* hack: round to full pixel size in chrome */,
height: '100%',
width: '100%',
display: 'table' /* hack: round to full pixel size in chrome */,
}}
/>
</div>
@ -138,4 +134,4 @@ export const ChessgroundWrapper = React.memo(
}
);
ChessgroundWrapper.displayName = "ChessgroundWrapper";
ChessgroundWrapper.displayName = 'ChessgroundWrapper';

View file

@ -1,19 +1,19 @@
import { JSONContent } from "@tiptap/react";
import { Chess, Move } from "chess.js";
import { Api } from "chessground/api";
import { DrawShape } from "chessground/draw";
import { App, Notice } from "obsidian";
import * as React from "react";
import { useCallback, useMemo, useState } from "react";
import { ChessifyPluginSettings } from "src/components/obsidian/SettingsTab";
import { JSONContent } from '@tiptap/react';
import { Chess, Move } from 'chess.js';
import { Api } from 'chessground/api';
import { DrawShape } from 'chessground/draw';
import { App, Notice } from 'obsidian';
import * as React from 'react';
import { useCallback, useMemo, useState } from 'react';
import { ChessifyPluginSettings } from 'src/components/obsidian/SettingsTab';
import {
ChessifyDataAdapter,
ChessifyFileData,
parseUserConfig,
} from "src/utils";
import { ChessGroundSettings, ChessgroundWrapper } from "./ChessgroundWrapper";
import { CommentSection } from "./CommentSection";
import { PgnViewer } from "./PgnViewer";
} from 'src/utils';
import { ChessGroundSettings, ChessgroundWrapper } from './ChessgroundWrapper';
import { CommentSection } from './CommentSection';
import { PgnViewer } from './PgnViewer';
export type ChessifyConfig = ChessGroundSettings;
@ -90,8 +90,7 @@ export const Chessify = ({
fen: move.after,
check: tempChess.isCheck(),
});
if (currentMove + 1 === history.length - 1)
setIsViewOnly(false);
if (currentMove + 1 === history.length - 1) setIsViewOnly(false);
return currentMove + 1;
});
}
@ -123,7 +122,7 @@ export const Chessify = ({
header: chessifyDataModified.header,
moves: chessLogic.history({ verbose: true }).map((move, index) => ({
...move,
subMoves: [],
variants: [],
shapes: shapes[index],
comment: comments[index],
})),
@ -131,7 +130,7 @@ export const Chessify = ({
await dataAdapter.saveFile(chessifyData, chessifyId);
new Notice("Save successfull!");
new Notice('Save successfull!');
}, [
chessLogic,
chessifyDataModified.header,
@ -145,14 +144,14 @@ export const Chessify = ({
<div className="border">
<div
style={{
display: "flex",
height: "450px",
display: 'flex',
height: '450px',
}}
>
<div
style={{
flex: "0 0 450px",
height: "100%",
flex: '0 0 450px',
height: '100%',
}}
>
<ChessgroundWrapper
@ -172,7 +171,7 @@ export const Chessify = ({
currentMoveShapes={shapes[currentMove]}
/>
</div>
<div style={{ flex: 1, height: "100%" }}>
<div style={{ flex: 1, height: '100%' }}>
<PgnViewer
history={history}
currentMove={currentMove}

View file

@ -1,7 +1,7 @@
import { EditorContent, JSONContent, useEditor } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import * as React from "react";
import { useEffect } from "react";
import { EditorContent, JSONContent, useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import * as React from 'react';
import { useEffect } from 'react';
interface CommentSectionProps {
currentMove: number;
@ -16,8 +16,7 @@ export const CommentSection = React.memo(
onUpdate: (state) => {
setComments((currentComments) => {
const currentCommentModified = [...currentComments];
currentCommentModified[currentMove] =
state.editor.getJSON();
currentCommentModified[currentMove] = state.editor.getJSON();
return currentCommentModified;
});
},
@ -40,4 +39,4 @@ export const CommentSection = React.memo(
}
);
CommentSection.displayName = "CommentSection";
CommentSection.displayName = 'CommentSection';

View file

@ -1,7 +1,7 @@
import { Move } from "chess.js";
import { ArrowLeft, ArrowRight, Save } from "lucide-react";
import * as React from "react";
import { useEffect, useMemo, useRef } from "react";
import { Move } from 'chess.js';
import { ArrowLeft, ArrowRight, Save } from 'lucide-react';
import * as React from 'react';
import { useEffect, useMemo, useRef } from 'react';
const chunkArray = <T,>(array: T[], chunkSize: number) => {
return array.reduce((resultArray, item, index) => {
@ -31,9 +31,9 @@ const MoveItem = ({
useEffect(() => {
if (ref.current && isCurrentMove) {
ref.current?.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "end",
behavior: 'smooth',
block: 'nearest',
inline: 'end',
});
}
}, [isCurrentMove]);
@ -41,7 +41,7 @@ const MoveItem = ({
return (
<p
className={`move-item ${
(isCurrentMove && "active") || ""
(isCurrentMove && 'active') || ''
} vertical-align`}
ref={ref}
onClick={(e) => {
@ -73,41 +73,33 @@ export const PgnViewer = React.memo(
const movePairs = useMemo(() => chunkArray(history, 2), [history]);
return (
<div style={{ width: "100%", height: "100%" }}>
<div style={{ height: "415px" }}>
<div style={{ width: '100%', height: '100%' }}>
<div style={{ height: '415px' }}>
<div
style={{
display: "grid",
gridTemplateColumns: "0.15fr 0.425fr 0.425fr",
gridAutoRows: "30px",
width: "100%",
maxHeight: "415px",
overflowY: "scroll",
display: 'grid',
gridTemplateColumns: '0.15fr 0.425fr 0.425fr',
gridAutoRows: '30px',
width: '100%',
maxHeight: '415px',
overflowY: 'scroll',
}}
>
{movePairs.map((pair, i) => {
const [wMove, bMove] = pair;
return (
<React.Fragment key={wMove.san + bMove?.san}>
<p className="move-indicator center">
{i + 1}
</p>
<p className="move-indicator center">{i + 1}</p>
<MoveItem
san={wMove.san}
isCurrentMove={i * 2 === currentMove}
onMoveItemClick={() =>
onMoveItemClick(i * 2)
}
onMoveItemClick={() => onMoveItemClick(i * 2)}
/>
{bMove && (
<MoveItem
san={bMove.san}
isCurrentMove={
i * 2 + 1 === currentMove
}
onMoveItemClick={() =>
onMoveItemClick(i * 2 + 1)
}
isCurrentMove={i * 2 + 1 === currentMove}
onMoveItemClick={() => onMoveItemClick(i * 2 + 1)}
/>
)}
</React.Fragment>
@ -117,12 +109,12 @@ export const PgnViewer = React.memo(
</div>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "100%",
gap: "4px",
height: "35px",
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
gap: '4px',
height: '35px',
}}
>
<button onClick={() => onBackButtonClick()}>
@ -132,7 +124,7 @@ export const PgnViewer = React.memo(
<ArrowRight />
</button>
<button onClick={() => onSaveButtonClick()}>
<Save strokeWidth={"1px"} />
<Save strokeWidth={'1px'} />
</button>
</div>
</div>
@ -140,4 +132,4 @@ export const PgnViewer = React.memo(
}
);
PgnViewer.displayName = "PgnViewer";
PgnViewer.displayName = 'PgnViewer';

View file

@ -78,3 +78,51 @@ button:active {
width: 100%;
height: 100%;
}
/*
Josh's Custom CSS Reset
https://www.joshwcomeau.com/css/custom-css-reset/
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
* {
margin: 0;
}
html,
body {
height: 100%;
}
body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
input,
button,
textarea,
select {
font: inherit;
}
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}
#root,
#__next {
isolation: isolate;
}

View file

@ -1,25 +1,24 @@
import { Chess } from "chess.js";
import { Editor, Notice, Plugin } from "obsidian";
import { ReactView } from "./components/ReactView";
import { PgnModal } from "./components/obsidian/PgnModal";
import { Chess } from 'chess.js';
import { Editor, Notice, Plugin } from 'obsidian';
import { ReactView } from './components/ReactView';
import { PgnModal } from './components/obsidian/PgnModal';
import {
ChessifyPluginSettings,
DEFAULT_SETTINGS,
SettingsTab,
} from "./components/obsidian/SettingsTab";
} from './components/obsidian/SettingsTab';
import {
ChessifyDataAdapter,
ChessifyFileData,
parseUserConfig,
} from "./utils";
} from './utils';
// these styles must be imported somewhere
import "assets/board/green.css";
import "chessground/assets/chessground.base.css";
import "chessground/assets/chessground.brown.css";
import "chessground/assets/chessground.cburnett.css";
import "../reset.css";
import "./main.css";
import 'assets/board/green.css';
import 'chessground/assets/chessground.base.css';
import 'chessground/assets/chessground.brown.css';
import 'chessground/assets/chessground.cburnett.css';
import './main.css';
export default class ChessifyPlugin extends Plugin {
settings: ChessifyPluginSettings;
@ -41,8 +40,8 @@ export default class ChessifyPlugin extends Plugin {
// Add command
this.addCommand({
id: "insert-chesser",
name: "Insert PGN-Editor at cursor position",
id: 'insert-chesser',
name: 'Insert PGN-Editor at cursor position',
editorCallback: (editor: Editor) => {
const cursorPosition = editor.getCursor();
@ -59,28 +58,24 @@ export default class ChessifyPlugin extends Plugin {
const chessifyFileData: ChessifyFileData = {
header: {
title: chess.header()["opening"] || null,
title: chess.header()['opening'] || null,
},
moves: chess
.history({ verbose: true })
.map((move) => ({
moves: chess.history({ verbose: true }).map((move) => ({
...move,
subMoves: [],
variants: [],
shapes: [],
comment: null,
})),
};
const id = await this.dataAdapter.saveFile(
chessifyFileData
);
const id = await this.dataAdapter.saveFile(chessifyFileData);
editor.replaceRange(
`\`\`\`chessify\nchessifyId: ${id}\n\`\`\``,
cursorPosition
);
} catch (e) {
new Notice("There was an error during PGN parsing.", 0);
new Notice('There was an error during PGN parsing.', 0);
}
};
@ -90,7 +85,7 @@ export default class ChessifyPlugin extends Plugin {
// Add chessify code block processor
this.registerMarkdownCodeBlockProcessor(
"chessify",
'chessify',
async (source, el, ctx) => {
const { chessifyId } = parseUserConfig(this.settings, source);
@ -122,19 +117,15 @@ export default class ChessifyPlugin extends Plugin {
}
);
console.log("Chessify successfully loaded");
console.log('Chessify successfully loaded');
}
async onunload() {
console.log("Chessify successfully unloaded");
console.log('Chessify successfully unloaded');
}
async loadSettings() {
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData()
);
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {

View file

@ -1,11 +1,11 @@
import { JSONContent } from "@tiptap/react";
import { Chess, Move, QUEEN, SQUARES, Square } from "chess.js";
import { Api } from "chessground/api";
import { Config } from "chessground/config";
import { DrawShape } from "chessground/draw";
import { nanoid } from "nanoid";
import { DataAdapter, parseYaml } from "obsidian";
import { ChessifyPluginSettings } from "src/components/obsidian/SettingsTab";
import { JSONContent } from '@tiptap/react';
import { Chess, Move, QUEEN, SQUARES, Square } from 'chess.js';
import { Api } from 'chessground/api';
import { Config } from 'chessground/config';
import { DrawShape } from 'chessground/draw';
import { nanoid } from 'nanoid';
import { DataAdapter, parseYaml } from 'obsidian';
import { ChessifyPluginSettings } from 'src/components/obsidian/SettingsTab';
//Chess Logic
type ChessifyAppConfig = ChessifyPluginSettings & {
@ -18,7 +18,7 @@ export const parseUserConfig = (
): ChessifyAppConfig => {
const chessifyConfig: ChessifyAppConfig = {
...settings,
chessifyId: "",
chessifyId: '',
};
try {
@ -27,12 +27,12 @@ export const parseUserConfig = (
...parseYaml(content),
};
} catch (e) {
throw Error("Something went wrong during parsing. :(");
throw Error('Something went wrong during parsing. :(');
}
};
export function toColor(chess: Chess) {
return chess.turn() === "w" ? "white" : "black";
return chess.turn() === 'w' ? 'white' : 'black';
}
export function toDests(chess: Chess): Map<Square, Square[]> {
@ -61,7 +61,7 @@ export function playOtherSide(cg: Api, chess: Chess) {
check: chess.isCheck(),
};
if (move.flags === "e" || move.promotion) {
if (move.flags === 'e' || move.promotion) {
//Handle En Passant && Promote to Queen by default
cg.set({
fen: chess.fen(),
@ -78,7 +78,7 @@ export function playOtherSide(cg: Api, chess: Chess) {
//Storage Logic
interface ChessifyMove extends Move {
subMoves: Move[];
variants: Move[][];
shapes: DrawShape[];
comment: JSONContent | null;
}

File diff suppressed because one or more lines are too long

View file

@ -1,14 +1,14 @@
import { readFileSync, writeFileSync } from "fs";
import { readFileSync, writeFileSync } from 'fs';
const targetVersion = process.env.npm_package_version;
// read minAppVersion from manifest.json and bump version to target version
let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
let manifest = JSON.parse(readFileSync('manifest.json', 'utf8'));
const { minAppVersion } = manifest;
manifest.version = targetVersion;
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
writeFileSync('manifest.json', JSON.stringify(manifest, null, '\t'));
// update versions.json with target version and minAppVersion from manifest.json
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
let versions = JSON.parse(readFileSync('versions.json', 'utf8'));
versions[targetVersion] = minAppVersion;
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));
writeFileSync('versions.json', JSON.stringify(versions, null, '\t'));