diff --git a/.eslintrc.js b/.eslintrc.js index df31ee5..249a7fa 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -11,6 +11,7 @@ const TypescriptRules = { '@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off', '@typescript-eslint/explicit-member-accessibility': [ 'error', { @@ -70,6 +71,8 @@ const TypescriptRules = { '@typescript-eslint/no-magic-numbers': ['error', { ignore: [-1, 0, 1] }], '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/parameter-properties': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', }; module.exports = { @@ -99,6 +102,12 @@ module.exports = { project: './tsconfig.eslint.json', }, overrides: [ + { + files: ['crates/**/*.js'], + parserOptions: { + project: null, + }, + }, { files: ['**/*.{js,jsx}'], rules: { diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..f6e229a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,216 @@ +name: Build + +on: + push: + branches: [ main, master ] + tags: + - 'v*' + pull_request: + branches: [ main, master ] + workflow_dispatch: + +jobs: + test: + name: Test on ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Install cargo-make + run: cargo install cargo-make + + - name: Run tests + run: pnpm ts:check && pnpm lint && cd ./crates && cargo make ci + + build-macos: + name: Build macOS + runs-on: macos-latest + if: startsWith(github.ref, 'refs/tags/') + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + targets: x86_64-apple-darwin,aarch64-apple-darwin + + - name: Install Rust targets + run: | + rustup target add x86_64-apple-darwin + rustup target add aarch64-apple-darwin + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build macOS + run: pnpm build:macos + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: mds-macos + path: dist/mds-macos.zip + retention-days: 90 + + build-windows: + name: Build Windows + runs-on: windows-latest + if: startsWith(github.ref, 'refs/tags/') + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + target: x86_64-pc-windows-msvc + + - name: Install Rust target + run: rustup target add x86_64-pc-windows-msvc + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build Windows + run: | + cd crates + node build.js windows --msvc + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: mds-windows + path: dist/mds-windows.zip + retention-days: 90 + + deploy-pages: + name: Deploy to GitHub Pages + needs: [create-release] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + permissions: + contents: read + pages: write + id-token: write + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build client + env: + GITHUB_PAGES_BASE_PATH: /${{ github.event.repository.name }}/ + run: | + cd client + pnpm build + + - name: Create .nojekyll file + run: touch dist/.nojekyll + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: dist + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + + create-release: + name: Create Release + needs: [build-macos, build-windows] + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Download macOS artifact + uses: actions/download-artifact@v4 + with: + name: mds-macos + path: dist/ + + - name: Download Windows artifact + uses: actions/download-artifact@v4 + with: + name: mds-windows + path: dist/ + + - name: Get version + id: version + run: | + VERSION=${GITHUB_REF#refs/tags/} + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: | + dist/mds-macos.zip + dist/mds-windows.zip + tag_name: ${{ steps.version.outputs.version }} + name: Release ${{ steps.version.outputs.version }} + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.gitignore b/.gitignore index cecfee9..43f6850 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,15 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies -/node_modules -/.pnp +node_modules +.pnp .pnp.js # testing -/coverage +coverage # production -/build +build # misc .DS_Store @@ -27,3 +27,64 @@ dist config.json .vscode + +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +editor-settings.json +MDS-Server.app +mds-macos.zip diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..a2483cb --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +pnpm ts:check && pnpm lint && cd ./crates && cargo make commit diff --git a/README.md b/README.md index df3da6a..71a11fd 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,74 @@ -# Local Markdown Editor +# Markdown Editor -- This is a web-based WYSIWYG markdown Editor without the need of database to store the markdown files, since it only needs to access the local file system by specifying the root path of the documents. +[![Build](https://github.com/s-elo/Markdown-editor/actions/workflows/build.yml/badge.svg)](https://github.com/s-elo/Markdown-editor/actions/workflows/build.yml) -- It uses [milkdown](https://milkdown.dev/getting-started) and [react-codemirror](https://uiwjs.github.io/react-codemirror/) to edit and display the local markdown files. It is featured by react with ts and less for the client UI and a node server with ts. +A web-based WYSIWYG markdown editor that works with your local files. Simply specify the root path of your documents to start editing. -- [How to use](#set-up) +**🌐 [Try it online](https://s-elo.github.io/Markdown-editor)** -## Features +Built with [Milkdown](https://milkdown.dev/getting-started) and [React CodeMirror](https://uiwjs.github.io/react-codemirror/) for editing and displaying local markdown files. -Apart from some common features of milkdown and react-codemirror: +## Key Features -- `Saving`: synchronizing with the local file system after editing +- **WYSIWYG Editing**: Rich markdown editing experience with Milkdown +- **Dual Editor Mode**: Switch between WYSIWYG and raw markdown (CodeMirror) with real-time sync +- **File Operations**: Create, rename, copy, move, and delete files and folders directly from the editor +- **Git Integration**: Commit, push, pull, and manage git changes if your document root is a git repository +- **Search**: Fast file name and content search across your documents based on [ripgrep + ](https://github.com/BurntSushi/ripgrep) +- **Image Management**: Upload and manage images stored locally. -- `Keyboard shortcuts`: shortcut for saving and read-edit mode switch - -- `Code mirror sync`: you can edit and sync in the milkdown or the code mirror with pure markdown syntax - -- `Sync position`: you can sync the position at the code mirror by double clicking the milkdown editor - -- `File Operations`: you can do some common file operaitons that will be sync to the local file system currently including adding new files and folders, renaming, copying, cutting, pasting and deleting +## Development -- `Github Sync`: if the local root document path has a git repo, it should be able to sync the files from the editor page +### Prerequisites -- `Navigation`: it has a menu for navigation +- [Rust](https://www.rust-lang.org/tools/install) +- [Node.js](https://nodejs.org/) (v20+) +- [pnpm](https://pnpm.io/) -- `Decent Search`: it should be able to search the docs quickly via some defined tags and the docs content +### Setup -- `Image storage`: currently using aliyun OSS as image storage, you might need to config your aliyun account +```bash +# Install dependencies +pnpm install +``` -![](./figures/demo1.gif) +### Run Development Server -![](./figures/demo2.gif) +The project consists of two main components: -## Set up +- **Rust Server** (`crates/server`): Handles file operations, git sync, and search +- **React Client** (`client`): Web UI built with React and TypeScript -### 1. install deps +Start both components: ```bash -yarn +pnpm dev ``` -### 2. configs(optional) +This will automatically start the Rust server and React client with hot-reload. -Add a config.json at the root path +### Build -```json -{ - "docRootPath": "the doc root path", - "ignoreDirs": [".git", "imgs"], - // (for aliyun OSS) - "region": "oss-cn-shenzhen", - "accessKeyId": "your accessKeyId", - "accessKeySecret": "your accessKeySecret", - "bucket": "your bucket name" -} +```bash +# Build both server and client +pnpm build ``` -> or you can just set the configs at the setting +### Release -### 3. compile and bundle the code +Formal release: ```bash -yarn build +pnpm release patch +pnpm release minor +pnpm release major ``` -### 4. open the document page - -> Before opening the page, make sure the code is bundled. - -- run the server at terminal - - ```bash - yarn open - ``` - -- or create a shortcut link - - 1. for window - - > After the bundling, you can just click the run.bat to open the documents. The bat file is actually for window shortcut so that you can open from your desktop. - > you can create a desktop shortcut by linking the run.bat or run.vbs file. - > The run.vbs is to hide the command window when you click the shortchut from your desktop. - - 2. for mac - > make sure the project path in run.scpt file is corrent, defualt path is ~/Markdown-editor. you can change to your own clone path. - > then save the run.scpt file as application file so that you can just double click it to open the editor. - -## Development - -There are two main components. One is the `node server` for doc file operations; another is the `client` for documentation UI. They are developed mainly using react and typescripts. Once start, the node server and client will be auto run. +Pre-release: ```bash -yarn start +pnpm release patch --alpha +pnpm release minor --beta +pnpm release major --rc ``` diff --git a/client/.gitignore b/client/.gitignore deleted file mode 100644 index 4d29575..0000000 --- a/client/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/client/config-overrides.js b/client/config-overrides.js deleted file mode 100644 index 9ec355a..0000000 --- a/client/config-overrides.js +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const path = require('path'); -const { override, addWebpackAlias } = require('customize-cra'); -const addLessLoader = require('customize-cra-less-loader'); - -module.exports = override( - addLessLoader(), - addWebpackAlias({ - ['@']: path.resolve(__dirname, './src'), - }), -); diff --git a/client/package.json b/client/package.json index b4679ff..9b42c61 100644 --- a/client/package.json +++ b/client/package.json @@ -1,71 +1,61 @@ { "name": "client", - "version": "0.1.0", + "version": "1.0.0", "private": true, + "scripts": { + "dev": "cross-env SERVER_PORT=3024 rsbuild dev --port 4000", + "build": "cross-env SERVER_PORT=7024 rsbuild build", + "preview": "rsbuild preview", + "ts:check": "tsc --noEmit" + }, "dependencies": { - "@codemirror/lang-markdown": "^0.19.6", - "@codemirror/language-data": "^0.19.2", - "@milkdown/core": "6.1.3", - "@milkdown/plugin-diagram": "6.1.3", - "@milkdown/plugin-emoji": "^6.1.3", - "@milkdown/plugin-history": "^6.1.3", - "@milkdown/plugin-indent": "^6.1.3", - "@milkdown/plugin-listener": "^6.1.3", - "@milkdown/plugin-menu": "^6.1.3", - "@milkdown/plugin-prism": "^6.1.3", - "@milkdown/plugin-slash": "^6.1.3", - "@milkdown/plugin-tooltip": "^6.1.3", - "@milkdown/plugin-upload": "^6.1.3", - "@milkdown/preset-commonmark": "^6.1.3", - "@milkdown/preset-gfm": "^6.1.3", - "@milkdown/prose": "^6.1.3", - "@milkdown/react": "^6.1.3", - "@milkdown/theme-nord": "^6.1.3", - "@milkdown/theme-tokyo": "^6.1.3", - "@milkdown/utils": "6.1.3", + "@codemirror/lang-markdown": "0.19.6", + "@codemirror/language": "^6.11.3", + "@codemirror/language-data": "0.19.2", + "@emotion/css": "11.13.5", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@milkdown/crepe": "7.18.0", + "@milkdown/kit": "7.18.0", + "@milkdown/react": "7.18.0", + "@milkdown/utils": "7.18.0", + "@mui/icons-material": "^7.2.0", "@reduxjs/toolkit": "^1.7.1", - "@types/react": "^17.0.30", - "@types/react-dom": "^17.0.9", - "@types/react-router-dom": "^5.3.1", + "@uiw/codemirror-theme-github": "^4.24.2", "@uiw/react-codemirror": "4.5.3", + "@uiw/react-split": "^5.9.3", "clipboard": "^2.0.11", - "cross-env": "^7.0.3", - "react": "17.0.2", - "react-dom": "17.0.2", - "react-redux": "^7.2.6", - "react-router-dom": "^5.3.0", - "react-scripts": "^5.0.1", - "remark-directive": "^2.0.1" - }, - "scripts": { - "start": "cross-env PORT=4000 react-app-rewired start", - "build": "react-app-rewired build", - "test": "react-app-rewired test", - "eject": "react-scripts eject" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] + "mdast-util-from-markdown": "^2.0.2", + "mdast-util-to-markdown": "^2.1.2", + "mermaid": "^11.11.0", + "micromark-extension-highlight-mark": "^1.2.0", + "micromark-util-types": "^2.0.2", + "primeicons": "^7.0.0", + "primereact": "^10.9.6", + "prosemirror-inputrules": "1.5.1", + "react": "19.1.0", + "react-complex-tree": "^2.6.0", + "react-dom": "19.1.0", + "react-redux": "^9.2.0", + "react-router-dom": "^7.6.1", + "refractor": "4.9.0", + "remark-directive": "^4.0.0", + "remark-highlight-mark": "^1.4.0", + "unified": "^11.0.5", + "unist-util-add": "^1.2.0", + "unist-util-visit": "^5.0.0", + "uuid": "^13.0.0" }, "devDependencies": { - "customize-cra": "^1.0.0", - "customize-cra-less-loader": "^2.0.0", - "less": "^4.1.2", - "less-loader": "^11.0.0", - "react-app-rewired": "^2.2.1", - "react-error-overlay": "6.0.9", - "typescript": "^4.4.4" - }, - "resolutions": { - "react-error-overlay": "6.0.9" - }, - "proxy": "http://localhost:3024" -} + "@rsbuild/core": "^1.3.21", + "@rsbuild/plugin-less": "^1.2.4", + "@rsbuild/plugin-react": "^1.3.1", + "@rsbuild/plugin-sass": "^1.3.1", + "@types/mdast": "^4.0.4", + "@types/node": "^24.10.1", + "@types/react": "^19.1.6", + "@types/react-dom": "^19.1.5", + "@types/react-redux": "7.1.34", + "typescript": "^5.8.3" + } +} \ No newline at end of file diff --git a/client/public/404.html b/client/public/404.html new file mode 100644 index 0000000..98cec74 --- /dev/null +++ b/client/public/404.html @@ -0,0 +1,45 @@ + + + + + Markdown Editor + + + + diff --git a/client/public/favicon.ico b/client/public/favicon.ico deleted file mode 100644 index c2c86b8..0000000 Binary files a/client/public/favicon.ico and /dev/null differ diff --git a/client/public/fonts/Rubik/Rubik-Black.ttf b/client/public/fonts/Rubik/Rubik-Black.ttf new file mode 100644 index 0000000..a135092 Binary files /dev/null and b/client/public/fonts/Rubik/Rubik-Black.ttf differ diff --git a/client/public/fonts/Rubik/Rubik-BlackItalic.ttf b/client/public/fonts/Rubik/Rubik-BlackItalic.ttf new file mode 100644 index 0000000..fd1f869 Binary files /dev/null and b/client/public/fonts/Rubik/Rubik-BlackItalic.ttf differ diff --git a/client/public/fonts/Rubik/Rubik-Bold.ttf b/client/public/fonts/Rubik/Rubik-Bold.ttf new file mode 100644 index 0000000..f9c4221 Binary files /dev/null and b/client/public/fonts/Rubik/Rubik-Bold.ttf differ diff --git a/client/public/fonts/Rubik/Rubik-BoldItalic.ttf b/client/public/fonts/Rubik/Rubik-BoldItalic.ttf new file mode 100644 index 0000000..1a7b205 Binary files /dev/null and b/client/public/fonts/Rubik/Rubik-BoldItalic.ttf differ diff --git a/client/public/fonts/Rubik/Rubik-ExtraBold.ttf b/client/public/fonts/Rubik/Rubik-ExtraBold.ttf new file mode 100644 index 0000000..07d11ef Binary files /dev/null and b/client/public/fonts/Rubik/Rubik-ExtraBold.ttf differ diff --git a/client/public/fonts/Rubik/Rubik-ExtraBoldItalic.ttf b/client/public/fonts/Rubik/Rubik-ExtraBoldItalic.ttf new file mode 100644 index 0000000..871a0d4 Binary files /dev/null and b/client/public/fonts/Rubik/Rubik-ExtraBoldItalic.ttf differ diff --git a/client/public/fonts/Rubik/Rubik-Italic.ttf b/client/public/fonts/Rubik/Rubik-Italic.ttf new file mode 100644 index 0000000..2bb3a87 Binary files /dev/null and b/client/public/fonts/Rubik/Rubik-Italic.ttf differ diff --git a/client/public/fonts/Rubik/Rubik-Light.ttf b/client/public/fonts/Rubik/Rubik-Light.ttf new file mode 100644 index 0000000..974f35a Binary files /dev/null and b/client/public/fonts/Rubik/Rubik-Light.ttf differ diff --git a/client/public/fonts/Rubik/Rubik-LightItalic.ttf b/client/public/fonts/Rubik/Rubik-LightItalic.ttf new file mode 100644 index 0000000..0bb5ae8 Binary files /dev/null and b/client/public/fonts/Rubik/Rubik-LightItalic.ttf differ diff --git a/client/public/fonts/Rubik/Rubik-Medium.ttf b/client/public/fonts/Rubik/Rubik-Medium.ttf new file mode 100644 index 0000000..a82ab78 Binary files /dev/null and b/client/public/fonts/Rubik/Rubik-Medium.ttf differ diff --git a/client/public/fonts/Rubik/Rubik-MediumItalic.ttf b/client/public/fonts/Rubik/Rubik-MediumItalic.ttf new file mode 100644 index 0000000..7205c56 Binary files /dev/null and b/client/public/fonts/Rubik/Rubik-MediumItalic.ttf differ diff --git a/client/public/fonts/Rubik/Rubik-Regular.ttf b/client/public/fonts/Rubik/Rubik-Regular.ttf new file mode 100644 index 0000000..f596412 Binary files /dev/null and b/client/public/fonts/Rubik/Rubik-Regular.ttf differ diff --git a/client/public/fonts/Rubik/Rubik-SemiBold.ttf b/client/public/fonts/Rubik/Rubik-SemiBold.ttf new file mode 100644 index 0000000..8a95c22 Binary files /dev/null and b/client/public/fonts/Rubik/Rubik-SemiBold.ttf differ diff --git a/client/public/fonts/Rubik/Rubik-SemiBoldItalic.ttf b/client/public/fonts/Rubik/Rubik-SemiBoldItalic.ttf new file mode 100644 index 0000000..07adbfa Binary files /dev/null and b/client/public/fonts/Rubik/Rubik-SemiBoldItalic.ttf differ diff --git a/client/public/fonts/Rubik/font.css b/client/public/fonts/Rubik/font.css new file mode 100644 index 0000000..23ecb41 --- /dev/null +++ b/client/public/fonts/Rubik/font.css @@ -0,0 +1,97 @@ +@font-face { + font-family: 'Rubik'; + src: url('./Rubik-Regular.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'Rubik'; + src: url('./Rubik-Light.ttf') format('truetype'); + font-weight: 300; + font-style: normal; +} + +@font-face { + font-family: 'Rubik'; + src: url('./Rubik-Medium.ttf') format('truetype'); + font-weight: 500; + font-style: normal; +} + +@font-face { + font-family: 'Rubik'; + src: url('./Rubik-SemiBold.ttf') format('truetype'); + font-weight: 600; + font-style: normal; +} + +@font-face { + font-family: 'Rubik'; + src: url('./Rubik-Bold.ttf') format('truetype'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: 'Rubik'; + src: url('./Rubik-ExtraBold.ttf') format('truetype'); + font-weight: 800; + font-style: normal; +} + +@font-face { + font-family: 'Rubik'; + src: url('./Rubik-Black.ttf') format('truetype'); + font-weight: 900; + font-style: normal; +} + +@font-face { + font-family: 'Rubik'; + src: url('./Rubik-Italic.ttf') format('truetype'); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: 'Rubik'; + src: url('./Rubik-LightItalic.ttf') format('truetype'); + font-weight: 300; + font-style: italic; +} + +@font-face { + font-family: 'Rubik'; + src: url('./Rubik-MediumItalic.ttf') format('truetype'); + font-weight: 500; + font-style: italic; +} + +@font-face { + font-family: 'Rubik'; + src: url('./Rubik-SemiBoldItalic.ttf') format('truetype'); + font-weight: 600; + font-style: italic; +} + +@font-face { + font-family: 'Rubik'; + src: url('./Rubik-BoldItalic.ttf') format('truetype'); + font-weight: bold; + font-style: italic; +} + +@font-face { + font-family: 'Rubik'; + src: url('./Rubik-ExtraBoldItalic.ttf') format('truetype'); + font-weight: 800; + font-style: italic; +} + +@font-face { + font-family: 'Rubik'; + src: url('./Rubik-BlackItalic.ttf') format('truetype'); + font-weight: 900; + font-style: italic; +} diff --git a/client/public/index.html b/client/public/index.html index 5ea9760..142eff3 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -2,34 +2,50 @@ - + - - - + - + - - - - - - DOC + + Markdown Editor + + diff --git a/client/public/logo.svg b/client/public/logo.svg new file mode 100644 index 0000000..2192b35 --- /dev/null +++ b/client/public/logo.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/client/public/logo192.png b/client/public/logo192.png deleted file mode 100644 index fa313ab..0000000 Binary files a/client/public/logo192.png and /dev/null differ diff --git a/client/public/logo512.png b/client/public/logo512.png deleted file mode 100644 index bd5d4b5..0000000 Binary files a/client/public/logo512.png and /dev/null differ diff --git a/client/public/manifest.json b/client/public/manifest.json index 080d6c7..38bf2ae 100644 --- a/client/public/manifest.json +++ b/client/public/manifest.json @@ -1,20 +1,20 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "Markdown Editor", + "name": "Markdown Editor", "icons": [ { - "src": "favicon.ico", + "src": "logo.svg", "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" + "type": "image/svg+xml" }, { - "src": "logo192.png", - "type": "image/png", + "src": "logo.svg", + "type": "image/svg+xml", "sizes": "192x192" }, { - "src": "logo512.png", - "type": "image/png", + "src": "logo.svg", + "type": "image/svg+xml", "sizes": "512x512" } ], diff --git a/client/public/prism.css b/client/public/prism.css deleted file mode 100644 index 3cad71f..0000000 --- a/client/public/prism.css +++ /dev/null @@ -1,446 +0,0 @@ -/** - * One Dark theme for prism.js - * Based on Atom's One Dark theme: https://github.com/atom/atom/tree/master/packages/one-dark-syntax - */ - -/** - * One Dark colours (accurate as of commit 8ae45ca on 6 Sep 2018) - * From colors.less - * --mono-1: hsl(220, 14%, 71%); - * --mono-2: hsl(220, 9%, 55%); - * --mono-3: hsl(220, 10%, 40%); - * --hue-1: hsl(187, 47%, 55%); - * --hue-2: hsl(207, 82%, 66%); - * --hue-3: hsl(286, 60%, 67%); - * --hue-4: hsl(95, 38%, 62%); - * --hue-5: hsl(355, 65%, 65%); - * --hue-5-2: hsl(5, 48%, 51%); - * --hue-6: hsl(29, 54%, 61%); - * --hue-6-2: hsl(39, 67%, 69%); - * --syntax-fg: hsl(220, 14%, 71%); - * --syntax-bg: hsl(220, 13%, 18%); - * --syntax-gutter: hsl(220, 14%, 45%); - * --syntax-guide: hsla(220, 14%, 71%, 0.15); - * --syntax-accent: hsl(220, 100%, 66%); - * From syntax-variables.less - * --syntax-selection-color: hsl(220, 13%, 28%); - * --syntax-gutter-background-color-selected: hsl(220, 13%, 26%); - * --syntax-cursor-line: hsla(220, 100%, 80%, 0.04); - */ - -code[class*="language-"], -pre[class*="language-"] { - background: hsl(220, 13%, 18%); - color: hsl(220, 14%, 71%); - text-shadow: 0 1px rgba(0, 0, 0, 0.3); - font-family: "Fira Code", "Fira Mono", Menlo, Consolas, "DejaVu Sans Mono", - monospace; - direction: ltr; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - line-height: 1.5; - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - -/* Selection */ -code[class*="language-"]::-moz-selection, -code[class*="language-"] *::-moz-selection, -pre[class*="language-"] *::-moz-selection { - background: hsl(220, 13%, 28%); - color: inherit; - text-shadow: none; -} - -code[class*="language-"]::selection, -code[class*="language-"] *::selection, -pre[class*="language-"] *::selection { - background: hsl(220, 13%, 28%); - color: inherit; - text-shadow: none; -} - -/* Code blocks */ -pre[class*="language-"] { - padding: 1em; - margin: 0.5em 0; - overflow: auto; - border-radius: 0.3em; -} - -/* Inline code */ -:not(pre) > code[class*="language-"] { - padding: 0.2em 0.3em; - border-radius: 0.3em; - white-space: normal; -} - -/* Print */ -@media print { - code[class*="language-"], - pre[class*="language-"] { - text-shadow: none; - } -} - -.token.comment, -.token.prolog, -.token.cdata { - color: hsl(220, 10%, 40%); -} - -.token.property, -.token.tag, -.token.symbol, -.token.deleted, -.token.important { - color: hsl(355, 65%, 65%); -} - -.token.doctype, -.token.punctuation, -.token.entity { - color: hsl(220, 14%, 71%); -} - -.token.attr-name, -.token.class-name, -.token.boolean, -.token.constant, -.token.number, -.token.atrule { - color: hsl(29, 54%, 61%); -} - -.token.keyword { - color: hsl(286, 60%, 67%); -} - -.token.selector, -.token.string, -.token.char, -.token.builtin, -.token.inserted, -.token.regex, -.token.attr-value, -.token.attr-value > .token.punctuation { - color: hsl(95, 38%, 62%); -} - -.token.variable, -.token.operator, -.token.function { - color: hsl(207, 82%, 66%); -} - -.token.url { - color: hsl(187, 47%, 55%); -} - -/* HTML overrides */ -.token.attr-value > .token.punctuation.attr-equals, -.token.special-attr > .token.attr-value > .token.value.css { - color: hsl(220, 14%, 71%); -} - -/* CSS overrides */ -.language-css .token.selector { - color: hsl(355, 65%, 65%); -} - -.language-css .token.property { - color: hsl(220, 14%, 71%); -} - -.language-css .token.function, -.language-css .token.url > .token.function { - color: hsl(187, 47%, 55%); -} - -.language-css .token.url > .token.string.url { - color: hsl(95, 38%, 62%); -} - -.language-css .token.important, -.language-css .token.atrule .token.rule { - color: hsl(286, 60%, 67%); -} - -/* JS overrides */ -.language-javascript .token.operator { - color: hsl(286, 60%, 67%); -} - -.language-javascript - .token.template-string - > .token.interpolation - > .token.interpolation-punctuation.punctuation { - color: hsl(5, 48%, 51%); -} - -/* JSON overrides */ -.language-json .token.operator { - color: hsl(220, 14%, 71%); -} - -.language-json .token.null.keyword { - color: hsl(29, 54%, 61%); -} - -/* MD overrides */ -.language-markdown .token.url, -.language-markdown .token.url > .token.operator, -.language-markdown .token.url-reference.url > .token.string { - color: hsl(220, 14%, 71%); -} - -.language-markdown .token.url > .token.content { - color: hsl(207, 82%, 66%); -} - -.language-markdown .token.url > .token.url, -.language-markdown .token.url-reference.url { - color: hsl(187, 47%, 55%); -} - -.language-markdown .token.blockquote.punctuation, -.language-markdown .token.hr.punctuation { - color: hsl(220, 10%, 40%); - font-style: italic; -} - -.language-markdown .token.code-snippet { - color: hsl(95, 38%, 62%); -} - -.language-markdown .token.bold .token.content { - color: hsl(29, 54%, 61%); -} - -.language-markdown .token.italic .token.content { - color: hsl(286, 60%, 67%); -} - -.language-markdown .token.strike .token.content, -.language-markdown .token.strike .token.punctuation, -.language-markdown .token.list.punctuation, -.language-markdown .token.title.important > .token.punctuation { - color: hsl(355, 65%, 65%); -} - -/* General */ -.token.bold { - font-weight: bold; -} - -.token.comment, -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} - -.token.namespace { - opacity: 0.8; -} - -/* Plugin overrides */ -/* Selectors should have higher specificity than those in the plugins' default stylesheets */ - -/* Show Invisibles plugin overrides */ -.token.token.tab:not(:empty):before, -.token.token.cr:before, -.token.token.lf:before, -.token.token.space:before { - color: hsla(220, 14%, 71%, 0.15); - text-shadow: none; -} - -/* Toolbar plugin overrides */ -/* Space out all buttons and move them away from the right edge of the code block */ -div.code-toolbar > .toolbar.toolbar > .toolbar-item { - margin-right: 0.4em; -} - -/* Styling the buttons */ -div.code-toolbar > .toolbar.toolbar > .toolbar-item > button, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > a, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > span { - background: hsl(220, 13%, 26%); - color: hsl(220, 9%, 55%); - padding: 0.1em 0.4em; - border-radius: 0.3em; -} - -div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus { - background: hsl(220, 13%, 28%); - color: hsl(220, 14%, 71%); -} - -/* Line Highlight plugin overrides */ -/* The highlighted line itself */ -.line-highlight.line-highlight { - background: hsla(220, 100%, 80%, 0.04); -} - -/* Default line numbers in Line Highlight plugin */ -.line-highlight.line-highlight:before, -.line-highlight.line-highlight[data-end]:after { - background: hsl(220, 13%, 26%); - color: hsl(220, 14%, 71%); - padding: 0.1em 0.6em; - border-radius: 0.3em; - box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.2); /* same as Toolbar plugin default */ -} - -/* Hovering over a linkable line number (in the gutter area) */ -/* Requires Line Numbers plugin as well */ -pre[id].linkable-line-numbers.linkable-line-numbers - span.line-numbers-rows - > span:hover:before { - background-color: hsla(220, 100%, 80%, 0.04); -} - -/* Line Numbers and Command Line plugins overrides */ -/* Line separating gutter from coding area */ -.line-numbers.line-numbers .line-numbers-rows, -.command-line .command-line-prompt { - border-right-color: hsla(220, 14%, 71%, 0.15); -} - -/* Stuff in the gutter */ -.line-numbers .line-numbers-rows > span:before, -.command-line .command-line-prompt > span:before { - color: hsl(220, 14%, 45%); -} - -/* Match Braces plugin overrides */ -/* Note: Outline colour is inherited from the braces */ -.rainbow-braces .token.token.punctuation.brace-level-1, -.rainbow-braces .token.token.punctuation.brace-level-5, -.rainbow-braces .token.token.punctuation.brace-level-9 { - color: hsl(355, 65%, 65%); -} - -.rainbow-braces .token.token.punctuation.brace-level-2, -.rainbow-braces .token.token.punctuation.brace-level-6, -.rainbow-braces .token.token.punctuation.brace-level-10 { - color: hsl(95, 38%, 62%); -} - -.rainbow-braces .token.token.punctuation.brace-level-3, -.rainbow-braces .token.token.punctuation.brace-level-7, -.rainbow-braces .token.token.punctuation.brace-level-11 { - color: hsl(207, 82%, 66%); -} - -.rainbow-braces .token.token.punctuation.brace-level-4, -.rainbow-braces .token.token.punctuation.brace-level-8, -.rainbow-braces .token.token.punctuation.brace-level-12 { - color: hsl(286, 60%, 67%); -} - -/* Diff Highlight plugin overrides */ -/* Taken from https://github.com/atom/github/blob/master/styles/variables.less */ -pre.diff-highlight > code .token.token.deleted:not(.prefix), -pre > code.diff-highlight .token.token.deleted:not(.prefix) { - background-color: hsla(353, 100%, 66%, 0.15); -} - -pre.diff-highlight > code .token.token.deleted:not(.prefix)::-moz-selection, -pre.diff-highlight > code .token.token.deleted:not(.prefix) *::-moz-selection, -pre > code.diff-highlight .token.token.deleted:not(.prefix)::-moz-selection, -pre > code.diff-highlight .token.token.deleted:not(.prefix) *::-moz-selection { - background-color: hsla(353, 95%, 66%, 0.25); -} - -pre.diff-highlight > code .token.token.deleted:not(.prefix)::selection, -pre.diff-highlight > code .token.token.deleted:not(.prefix) *::selection, -pre > code.diff-highlight .token.token.deleted:not(.prefix)::selection, -pre > code.diff-highlight .token.token.deleted:not(.prefix) *::selection { - background-color: hsla(353, 95%, 66%, 0.25); -} - -pre.diff-highlight > code .token.token.inserted:not(.prefix), -pre > code.diff-highlight .token.token.inserted:not(.prefix) { - background-color: hsla(137, 100%, 55%, 0.15); -} - -pre.diff-highlight > code .token.token.inserted:not(.prefix)::-moz-selection, -pre.diff-highlight > code .token.token.inserted:not(.prefix) *::-moz-selection, -pre > code.diff-highlight .token.token.inserted:not(.prefix)::-moz-selection, -pre > code.diff-highlight .token.token.inserted:not(.prefix) *::-moz-selection { - background-color: hsla(135, 73%, 55%, 0.25); -} - -pre.diff-highlight > code .token.token.inserted:not(.prefix)::selection, -pre.diff-highlight > code .token.token.inserted:not(.prefix) *::selection, -pre > code.diff-highlight .token.token.inserted:not(.prefix)::selection, -pre > code.diff-highlight .token.token.inserted:not(.prefix) *::selection { - background-color: hsla(135, 73%, 55%, 0.25); -} - -/* Previewers plugin overrides */ -/* Based on https://github.com/atom-community/atom-ide-datatip/blob/master/styles/atom-ide-datatips.less and https://github.com/atom/atom/blob/master/packages/one-dark-ui */ -/* Border around popup */ -.prism-previewer.prism-previewer:before, -.prism-previewer-gradient.prism-previewer-gradient div { - border-color: hsl(224, 13%, 17%); -} - -/* Angle and time should remain as circles and are hence not included */ -.prism-previewer-color.prism-previewer-color:before, -.prism-previewer-gradient.prism-previewer-gradient div, -.prism-previewer-easing.prism-previewer-easing:before { - border-radius: 0.3em; -} - -/* Triangles pointing to the code */ -.prism-previewer.prism-previewer:after { - border-top-color: hsl(224, 13%, 17%); -} - -.prism-previewer-flipped.prism-previewer-flipped.after { - border-bottom-color: hsl(224, 13%, 17%); -} - -/* Background colour within the popup */ -.prism-previewer-angle.prism-previewer-angle:before, -.prism-previewer-time.prism-previewer-time:before, -.prism-previewer-easing.prism-previewer-easing { - background: hsl(219, 13%, 22%); -} - -/* For angle, this is the positive area (eg. 90deg will display one quadrant in this colour) */ -/* For time, this is the alternate colour */ -.prism-previewer-angle.prism-previewer-angle circle, -.prism-previewer-time.prism-previewer-time circle { - stroke: hsl(220, 14%, 71%); - stroke-opacity: 1; -} - -/* Stroke colours of the handle, direction point, and vector itself */ -.prism-previewer-easing.prism-previewer-easing circle, -.prism-previewer-easing.prism-previewer-easing path, -.prism-previewer-easing.prism-previewer-easing line { - stroke: hsl(220, 14%, 71%); -} - -/* Fill colour of the handle */ -.prism-previewer-easing.prism-previewer-easing circle { - fill: transparent; -} diff --git a/client/public/robots.txt b/client/public/robots.txt deleted file mode 100644 index 01b0f9a..0000000 --- a/client/public/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * diff --git a/client/rsbuild-plugins/plugin-prime-themes.ts b/client/rsbuild-plugins/plugin-prime-themes.ts new file mode 100644 index 0000000..ef37d89 --- /dev/null +++ b/client/rsbuild-plugins/plugin-prime-themes.ts @@ -0,0 +1,102 @@ +import fs from 'fs'; +import path from 'path'; + +import type { RsbuildPlugin } from '@rsbuild/core'; + +/** Theme names to copy from primereact/resources/themes (only these are used at runtime) */ +const PRIME_THEMES = ['lara-dark-blue', 'lara-light-blue'] as const; + +const PRIME_THEMES_SOURCE = 'node_modules/primereact/resources/themes'; + +/* eslint-disable @typescript-eslint/naming-convention -- file extensions for MIME */ +const MIME: Record = { + '.css': 'text/css', + '.woff2': 'font/woff2', + '.woff': 'font/woff', +}; +/* eslint-enable @typescript-eslint/naming-convention */ + +/** + * Rsbuild plugin: PrimeReact themes as build-time assets only. + * - Build: copies theme files from node_modules to dist/themes/ (no themes in public or git). + * - Dev: serves /themes/* from node_modules via middleware. + */ +export function pluginPrimeThemes(options?: { themes?: string[] }): RsbuildPlugin { + const themes = options?.themes ?? [...PRIME_THEMES]; + + return { + name: 'plugin-prime-themes', + setup(api) { + api.modifyRsbuildConfig((config) => { + const root = config.root ?? process.cwd(); + + // Build: copy PrimeReact themes from node_modules to dist/themes/ + const existingCopy = config.output?.copy; + const copyPatterns = themes.map((name) => ({ + from: path.join(root, PRIME_THEMES_SOURCE, name), + to: `themes/${name}`, + })); + const mergedCopy = existingCopy + ? Array.isArray(existingCopy) + ? [...existingCopy, ...copyPatterns] + : [existingCopy, ...copyPatterns] + : copyPatterns; + const output = config.output ?? {}; + config.output = { ...output, copy: mergedCopy as typeof output.copy }; + + // Dev: serve /themes/* from node_modules (no public/themes needed) + const themesDir = path.resolve(root, PRIME_THEMES_SOURCE); + const existingMiddlewares = config.dev?.setupMiddlewares; + const setup = (middlewares: { push: (...handlers: unknown[]) => void }, context: unknown) => { + if (existingMiddlewares) { + const fns = Array.isArray(existingMiddlewares) ? existingMiddlewares : [existingMiddlewares]; + fns.forEach((fn) => { + (fn as (m: typeof middlewares, c: typeof context) => void)(middlewares, context); + }); + } + middlewares.push( + ( + req: { url?: string }, + res: { setHeader: (a: string, b: string) => void; end: (data?: Buffer) => void }, + next: () => void, + ) => { + const url = req.url?.split('?')[0] ?? ''; + if (!url.startsWith('/themes/')) { + next(); + return; + } + const subpath = url.slice('/themes/'.length); + if (!subpath || subpath.includes('..')) { + next(); + return; + } + const file = path.join(themesDir, subpath); + const resolved = path.normalize(path.resolve(file)); + const allowed = path.normalize(path.resolve(themesDir)); + if (!resolved.startsWith(allowed) || !fs.existsSync(resolved)) { + next(); + return; + } + const stat = fs.statSync(resolved); + if (!stat.isFile()) { + next(); + return; + } + const ext = path.extname(resolved); + const mime = MIME[ext] ?? 'application/octet-stream'; + res.setHeader('Content-Type', mime); + res.end(fs.readFileSync(resolved)); + }, + ); + }; + const dev = config.dev ?? {}; + config.dev = { + ...dev, + setupMiddlewares: (existingMiddlewares + ? [...(Array.isArray(existingMiddlewares) ? existingMiddlewares : [existingMiddlewares]), setup] + : [setup]) as typeof dev.setupMiddlewares, + }; + }); + }, + }; +} diff --git a/client/rsbuild.config.ts b/client/rsbuild.config.ts new file mode 100644 index 0000000..3dd17b3 --- /dev/null +++ b/client/rsbuild.config.ts @@ -0,0 +1,49 @@ +import { defineConfig } from '@rsbuild/core'; +import { pluginReact } from '@rsbuild/plugin-react'; +import { pluginSass } from '@rsbuild/plugin-sass'; + +import pkgJson from './package.json'; +import { pluginPrimeThemes } from './rsbuild-plugins/plugin-prime-themes'; + +const defaultPort = 3024; +const SERVER_PORT = process.env.SERVER_PORT ?? defaultPort; + +// Base path for GitHub Pages (set via GITHUB_PAGES_BASE_PATH env var) +// For project pages: /repo-name/ +// For user/organization pages: / +const basePath = process.env.GITHUB_PAGES_BASE_PATH ?? '/'; +console.log(`Using base path: "${basePath}"`); + +const version = pkgJson.version; + +export default defineConfig({ + plugins: [pluginReact(), pluginSass(), pluginPrimeThemes()], + html: { + template: './public/index.html', + }, + output: { + distPath: { + root: '../dist', + }, + assetPrefix: basePath, + }, + source: { + define: { + // eslint-disable-next-line @typescript-eslint/naming-convention + __GITHUB_PAGES_BASE_PATH__: JSON.stringify(basePath), + // eslint-disable-next-line @typescript-eslint/naming-convention + __VERSION__: JSON.stringify(version), + // eslint-disable-next-line @typescript-eslint/naming-convention + __SERVER_PORT__: JSON.stringify(SERVER_PORT), + }, + }, + server: { + proxy: { + // eslint-disable-next-line @typescript-eslint/naming-convention + '/api': { + target: `http://127.0.0.1:${SERVER_PORT}`, + }, + }, + open: true, + }, +}); diff --git a/client/src/App.less b/client/src/App.less deleted file mode 100644 index fd7a9a5..0000000 --- a/client/src/App.less +++ /dev/null @@ -1,81 +0,0 @@ -@import url('./utils/utils.less'); - -* { - margin: 0; - padding: 0; - // font-size: 16px; - box-sizing: border-box; -} -body { - font-family: 'LXGW WenKai Screen R', Calibri, Arial, sans-serif; -} -*::-webkit-scrollbar { - width: 10px; - min-height: 10px; - border-radius: 15px; - background-color: transparent; - &:hover { - opacity: 0.7; - } -} -*::-webkit-scrollbar-corner { - background-color: transparent; -} -*::-webkit-scrollbar-track { - border-radius: 15px; - background-color: transparent; -} -*::-webkit-scrollbar-thumb { - box-shadow: inset 0 0 2px rgb(114, 166, 226); - border-radius: 15px; - background-color: transparent; - &:hover { - background-color: rgb(149, 146, 146); - } -} - -.input { - // width: 100%; - // height: 4rem; - padding: 0 10px; - outline: none; - // border-radius: 5px; - border: 0.5px solid #e7e0e0; - // margin-bottom: 10px; - // font-size: 1.5rem; - position: relative; - &:focus { - // border: 1px solid #4cade6; - // box-shadow: 0 0 2px 2px #62b5ec; - border-color: #62b5ec; - } -} -.btn { - width: fit-content; - height: fit-content; - padding: 10px; - outline: none; - border: none; - border-radius: 5px; - cursor: pointer; - text-decoration: none; - display: block; -} - -.container { - width: 100vw; - height: 100vh; - background-color: @backgroundColor; - // #ECEFF4 - // #252932 - position: relative; - display: flex; - justify-content: flex-start; - align-items: flex-start; - transition: @transition; - // overflow: auto; - .content-area { - width: 60rem; - height: fit-content; - } -} diff --git a/client/src/App.scss b/client/src/App.scss new file mode 100644 index 0000000..f85e101 --- /dev/null +++ b/client/src/App.scss @@ -0,0 +1,83 @@ +@use './utils/utils.scss' as *; + +* { + box-sizing: border-box; +} + +body { + margin: 0; +} + +.input { + // width: 100%; + // height: 4rem; + padding: 0 10px; + outline: none; + // border-radius: 5px; + border: 0.5px solid #e7e0e0; + // margin-bottom: 10px; + // font-size: 1.5rem; + position: relative; + &:focus { + // border: 1px solid #4cade6; + // box-shadow: 0 0 2px 2px #62b5ec; + border-color: #62b5ec; + } +} +.btn { + width: fit-content; + height: fit-content; + padding: 10px; + outline: none; + border: none; + border-radius: 5px; + cursor: pointer; + text-decoration: none; + display: block; +} + +.app-container { + display: flex; + height: calc(100vh - 26px); +} + +.app-loading-container { + @include flex-center(); + height: 100vh; + width: 100vw; +} + +#container { + width: calc(100vw - 45px); + background-color: $backgroundColor; + .content-area { + width: 60rem; + height: fit-content; + } + .split-bar { + &:hover { + background-color: rgba(255, 255, 255, 0.3); + } + } +} + +.tool-tip { + .p-tooltip-text { + font-size: 14px; + } +} + +.p-scrollpanel-content { + padding: 0; +} +.p-scrollpanel-bar { + background-color: rgba(#f3f5f6, 0.7); +} + +.p-inputtext { + box-shadow: none; +} + +.p-confirm-dialog-message { + margin: 0; +} diff --git a/client/src/App.tsx b/client/src/App.tsx index cedcaa3..fd9fded 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,19 +1,74 @@ -import React from 'react'; +import Split from '@uiw/react-split'; +import { PrimeReactProvider } from 'primereact/api'; +import { ConfirmDialog } from 'primereact/confirmdialog'; +import { FC, useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; -import EditorContainer from './components/EditorContainer/EditorContainer'; -import Menu from './components/Menu/MenuContainer'; -import { useShortCut } from './utils/hooks/tools'; +import { EditorContainer } from './components/EditorContainer/EditorContainer'; +import { Footer } from './components/Footer/Footer'; +import { Menu } from './components/Menu/Menu'; +import { Sidebar } from './components/Sidebar/Sidebar'; +import { SplitBar } from './components/SplitBar'; +import { APP_VERSION } from './constants'; +import { selectMenuCollapse } from './redux-feature/globalOptsSlice'; +import { useCheckServer, useWarnUnsavedOnUnload } from './utils/hooks/reduxHooks'; -import './App.less'; +import './App.scss'; -// eslint-disable-next-line @typescript-eslint/naming-convention -export default function App() { - useShortCut(); +export const App: FC = () => { + useWarnUnsavedOnUnload(); + + const { isLoading, isError, isSuccess, data: serverCheckRes } = useCheckServer(); + const menuCollapse = useSelector(selectMenuCollapse); + const navigate = useNavigate(); + + const showMenu = !isError && !menuCollapse; + + useEffect(() => { + if (isError) { + void navigate('/internal/guide'); + return; + } + + if (!isLoading && serverCheckRes?.version !== APP_VERSION) { + void navigate('/internal/version-mismatch'); + } + }, [isError, isSuccess, serverCheckRes]); + + if (isLoading) { + return ( +
+ +
+ ); + } return ( -
- - -
+ + +
+ + + {/* re-rendering menu to select current doc */} + {showMenu && ( +
+ +
+ )} +
+ +
+
+
+