mirror of
https://github.com/chrislicodes/obsidian-chess-study.git
synced 2026-07-22 04:39:02 +00:00
chore: readme, license and co.
This commit is contained in:
parent
f4302c7d0f
commit
aebb62427d
24 changed files with 395 additions and 300 deletions
6
.prettierrc
Normal file
6
.prettierrc
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"proseWrap": "preserve",
|
||||
"semi": true
|
||||
}
|
||||
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal 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.
|
||||
95
README.md
95
README.md
|
|
@ -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:
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
After that the PGN viewer/editor will render and you are good to go:
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
BIN
imgs/chessify-codeblock.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
imgs/chessify-demo.gif
Normal file
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
BIN
imgs/chessify-modal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
47
reset.css
47
reset.css
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
48
src/main.css
48
src/main.css
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
61
src/main.tsx
61
src/main.tsx
|
|
@ -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) => ({
|
||||
...move,
|
||||
subMoves: [],
|
||||
shapes: [],
|
||||
comment: null,
|
||||
})),
|
||||
moves: chess.history({ verbose: true }).map((move) => ({
|
||||
...move,
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
89
styles.css
89
styles.css
File diff suppressed because one or more lines are too long
|
|
@ -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'));
|
||||
|
|
|
|||
Loading…
Reference in a new issue