From 7898df6162c7cf42d7467cec045163c05e8af482 Mon Sep 17 00:00:00 2001 From: Jim Hodapp <james.hodapp@gmail.com> Date: Sat, 12 Oct 2024 17:06:49 -0500 Subject: [PATCH 01/14] Simple initial TipTap editor instance wrapped as the CoachingNotes component --- .vscode/settings.json | 3 + package-lock.json | 854 +++++++++++++++++- package.json | 11 + src/app/coaching-sessions/[id]/page.tsx | 145 ++- src/app/login/page.tsx | 4 +- .../ui/coaching-sessions/coaching-notes.tsx | 64 ++ .../ui/coaching-sessions/tiptap-editor.tsx | 191 ++++ src/lib/api/notes.ts | 321 +++---- src/styles/styles.scss | 95 ++ 9 files changed, 1435 insertions(+), 253 deletions(-) create mode 100644 src/components/ui/coaching-sessions/coaching-notes.tsx create mode 100644 src/components/ui/coaching-sessions/tiptap-editor.tsx create mode 100644 src/styles/styles.scss diff --git a/.vscode/settings.json b/.vscode/settings.json index 843828d..31cb34b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,4 +14,7 @@ "[html]": { "editor.formatOnSave": true }, + "tailwindCSS.experimental.classRegex": [ + "class:\\s*?[\"'`]([^\"'`]*).*?," + ] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d7d260c..e0b05d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,16 @@ "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", + "@tailwindcss/typography": "^0.5.15", + "@tiptap/extension-bubble-menu": "^2.8.0", + "@tiptap/extension-bullet-list": "^2.8.0", + "@tiptap/extension-heading": "^2.8.0", + "@tiptap/extension-list-item": "^2.8.0", + "@tiptap/extension-ordered-list": "^2.8.0", + "@tiptap/extension-underline": "^2.8.0", + "@tiptap/pm": "^2.8.0", + "@tiptap/react": "^2.8.0", + "@tiptap/starter-kit": "^2.8.0", "axios": "^1.6.7", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", @@ -38,6 +48,7 @@ "react": "^18", "react-day-picker": "^8.10.0", "react-dom": "^18", + "sass": "^1.79.4", "tailwind-merge": "^2.2.0", "tailwindcss-animate": "^1.0.7", "ts-luxon": "^4.5.2", @@ -483,6 +494,16 @@ "node": ">=14" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@radix-ui/number": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz", @@ -1602,6 +1623,12 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@remirror/core-constants": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", + "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", + "license": "MIT" + }, "node_modules/@rushstack/eslint-patch": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.2.tgz", @@ -1622,12 +1649,473 @@ "tslib": "^2.4.0" } }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.15.tgz", + "integrity": "sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==", + "license": "MIT", + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@tiptap/core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.8.0.tgz", + "integrity": "sha512-xsqDI4BNzYRWRtBq7+/38ThhqEr7uG9Njip1x+9/wgR3vWPBFnBkYJTz6jSxS35NRE6BSnERm4/B/vrLuY1Hdw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-blockquote": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.8.0.tgz", + "integrity": "sha512-m3CKrOIvV7fY1Ak2gYf5LkKiz6AHxHpg6wxfVaJvdBqXgLyVtHo552N+A4oSHOSRbB4AG9EBQ2NeBM8cdEQ4MA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.8.0.tgz", + "integrity": "sha512-U1YkZBxDkSLNvPNiqxB5g42IeJHr27C7zDb/yGQN2xL4UBeg4O9xVhCFfe32f6tLwivSL0dar4ScElpaCJuqow==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.8.0.tgz", + "integrity": "sha512-swg+myJPN60LduQvLMF4hVBqP5LOIN01INZBzBI8egz8QufqtSyRCgXl7Xcma0RT5xIXnZSG9XOqNFf2rtkjKA==", + "license": "MIT", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-bullet-list": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.8.0.tgz", + "integrity": "sha512-H4O2X0ozbc/ce9/XF1H98sqWVUdtt7jzy7hMBunwmY8ZxI4dHtcRkeg81CZbpKTqOqRrMCLWjE3M2tgiDXrDkA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/extension-list-item": "^2.7.0", + "@tiptap/extension-text-style": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.8.0.tgz", + "integrity": "sha512-VSFn3sFF6qPpOGkXFhik8oYRH5iByVJpFEFd/duIEftmS0MdPzkbSItOpN3mc9xsJ5dCX80LYaResSj5hr5zkA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.8.0.tgz", + "integrity": "sha512-POuA5Igx+Dto0DTazoBFAQTj/M/FCdkqRVD9Uhsxhv49swPyANTJRr05vgbgtHB+NDDsZfCawVh7pI0IAD/O0w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.8.0.tgz", + "integrity": "sha512-mp7Isx1sVc/ifeW4uW/PexGQ9exN3NRUOebSpnLfqXeWYk4y1RS1PA/3+IHkOPVetbnapgPjFx/DswlCP3XLjA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-dropcursor": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.8.0.tgz", + "integrity": "sha512-rAFvx44YuT6dtS1c+ALw0ROAGI16l5L1HxquL4hR1gtxDcTieST5xhw5bkshXlmrlfotZXPrhokzqA7qjhZtJw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.8.0.tgz", + "integrity": "sha512-H4QT61CrkLqisnGGC7zgiYmsl2jXPHl89yQCbdlkQN7aw11H7PltcJS2PJguL0OrRVJS/Mv/VTTUiMslmsEV5g==", + "license": "MIT", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-gapcursor": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.8.0.tgz", + "integrity": "sha512-Be1LWCmvteQInOnNVN+HTqc1XWsj1bCl+Q7et8qqNjtGtTaCbdCp8ppcH1SKJxNTM/RLUtPyJ8FDgOTj51ixCA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.8.0.tgz", + "integrity": "sha512-vqiIfviNiCmy/pJTHuDSCAGL2O4QDEdDmAvGJu8oRmElUrnlg8DbJUfKvn6DWQHNSQwRb+LDrwWlzAYj1K9u6A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.8.0.tgz", + "integrity": "sha512-4inWgrTPiqlivPmEHFOM5ck2UsmOsbKKPtqga6bALvWPmCv24S6/EBwFp8Jz4YABabXDnkviihmGu0LpP9D69w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-history": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.8.0.tgz", + "integrity": "sha512-u5YS0J5Egsxt8TUWMMAC3QhPZaak+IzQeyHch4gtqxftx96tprItY7AD/A3pGDF2uCSnN+SZrk6yVexm6EncDw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.8.0.tgz", + "integrity": "sha512-Sn/MI8WVFBoIYSIHA9NJryJIyCEzZdRysau8pC5TFnfifre0QV1ksPz2bgF+DyCD69ozQiRdBBHDEwKe47ZbfQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.8.0.tgz", + "integrity": "sha512-PwwSE2LTYiHI47NJnsfhBmPiLE8IXZYqaSoNPU6flPrk1KxEzqvRI1joKZBmD9wuqzmHJ93VFIeZcC+kfwi8ZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-list-item": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.8.0.tgz", + "integrity": "sha512-o7OGymGxB0B9x3x2prp3KBDYFuBYGc5sW69O672jk8G52DqhzzndgPnkk0qUn8nXAUKuDGbJmpmHVA2kagqnRg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-ordered-list": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.8.0.tgz", + "integrity": "sha512-sCvNbcTS1+5QTTXwUPFa10vf5I1pr8sGcOTIh0G+a5ZkS5+6FxT12k7VLzPt39QyNbOi+77U2o4Xr4XyaEkfSg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/extension-list-item": "^2.7.0", + "@tiptap/extension-text-style": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.8.0.tgz", + "integrity": "sha512-XgxxNNbuBF48rAGwv7/s6as92/xjm/lTZIGTq9aG13ClUKFtgdel7C33SpUCcxg3cO2WkEyllXVyKUiauFZw/A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.8.0.tgz", + "integrity": "sha512-ezkDiXxQ3ME/dDMMM7tAMkKRi6UWw7tIu+Mx7Os0z8HCGpVBk1gFhLlhEd8I5rJaPZr4tK1wtSehMA9bscFGQw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.8.0.tgz", + "integrity": "sha512-EDAdFFzWOvQfVy7j3qkKhBpOeE5thkJaBemSWfXI93/gMVc0ZCdLi24mDvNNgUHlT+RjlIoQq908jZaaxLKN2A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-text-style": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.8.0.tgz", + "integrity": "sha512-jJp0vcZ2Ty7RvIL0VU6dm1y+fTfXq1lN2GwtYzYM0ueFuESa+Qo8ticYOImyWZ3wGJGVrjn7OV9r0ReW0/NYkQ==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-underline": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.8.0.tgz", + "integrity": "sha512-1ouuHwZJphT8OosAmp6x8e+Wly3cUd1pNWBiOutJX+6QRGBXJnIKFCzn8YOTlWhg1YQigisG7dNF3YdlyuRNHw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/pm": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.8.0.tgz", + "integrity": "sha512-eMGpRooUMvKz/vOpnKKppApMSoNM325HxTdAJvTlVAmuHp5bOY5kyY1kfUlePRiVx1t1UlFcXs3kecFwkkBD3Q==", + "license": "MIT", + "dependencies": { + "prosemirror-changeset": "^2.2.1", + "prosemirror-collab": "^1.3.1", + "prosemirror-commands": "^1.6.0", + "prosemirror-dropcursor": "^1.8.1", + "prosemirror-gapcursor": "^1.3.2", + "prosemirror-history": "^1.4.1", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-markdown": "^1.13.0", + "prosemirror-menu": "^1.2.4", + "prosemirror-model": "^1.22.3", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.4.1", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.4.0", + "prosemirror-trailing-node": "^3.0.0", + "prosemirror-transform": "^1.10.0", + "prosemirror-view": "^1.33.10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/react": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.8.0.tgz", + "integrity": "sha512-o/aSCjO5Nu4MsNpTF+N1SzYzVQvvBiclmTOZX2E6usZ8jre5zmKfXHDSZnjGSRTK6z6kw5KW8wpjRQha03f9mg==", + "license": "MIT", + "dependencies": { + "@tiptap/extension-bubble-menu": "^2.8.0", + "@tiptap/extension-floating-menu": "^2.8.0", + "@types/use-sync-external-store": "^0.0.6", + "fast-deep-equal": "^3", + "use-sync-external-store": "^1.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tiptap/react/node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.8.0.tgz", + "integrity": "sha512-r7UwaTrECkQoheWVZKFDqtL5tBx07x7IFT+prfgnsVlYFutGWskVVqzCDvD3BDmrg5PzeCWYZrQGlPaLib7tjg==", + "license": "MIT", + "dependencies": { + "@tiptap/core": "^2.8.0", + "@tiptap/extension-blockquote": "^2.8.0", + "@tiptap/extension-bold": "^2.8.0", + "@tiptap/extension-bullet-list": "^2.8.0", + "@tiptap/extension-code": "^2.8.0", + "@tiptap/extension-code-block": "^2.8.0", + "@tiptap/extension-document": "^2.8.0", + "@tiptap/extension-dropcursor": "^2.8.0", + "@tiptap/extension-gapcursor": "^2.8.0", + "@tiptap/extension-hard-break": "^2.8.0", + "@tiptap/extension-heading": "^2.8.0", + "@tiptap/extension-history": "^2.8.0", + "@tiptap/extension-horizontal-rule": "^2.8.0", + "@tiptap/extension-italic": "^2.8.0", + "@tiptap/extension-list-item": "^2.8.0", + "@tiptap/extension-ordered-list": "^2.8.0", + "@tiptap/extension-paragraph": "^2.8.0", + "@tiptap/extension-strike": "^2.8.0", + "@tiptap/extension-text": "^2.8.0", + "@tiptap/pm": "^2.8.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.12.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", @@ -1662,6 +2150,12 @@ "@types/react": "*" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@typescript-eslint/parser": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", @@ -1879,8 +2373,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/aria-hidden": { "version": "1.2.4", @@ -2664,6 +3157,12 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2904,6 +3403,18 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -3075,7 +3586,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -3498,8 +4008,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.3.2", @@ -4001,6 +4510,12 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -4589,6 +5104,15 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4604,11 +5128,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/loose-envify": { "version": "1.4.0", @@ -4637,6 +5172,29 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5011,6 +5569,12 @@ "node": ">= 0.8.0" } }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", + "license": "MIT" + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -5310,6 +5874,201 @@ "react-is": "^16.13.1" } }, + "node_modules/prosemirror-changeset": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz", + "integrity": "sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==", + "license": "MIT", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.0.tgz", + "integrity": "sha512-xn1U/g36OqXn2tn5nGmvnnimAj/g1pUx2ypJJIe8WkVX83WyJVC5LTARaxZa2AtQRwntu9Jc5zXs9gL9svp/mg==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz", + "integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz", + "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz", + "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz", + "integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz", + "integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.1.tgz", + "integrity": "sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw==", + "license": "MIT", + "dependencies": { + "@types/markdown-it": "^14.0.0", + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.20.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz", + "integrity": "sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==", + "license": "MIT", + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.23.0.tgz", + "integrity": "sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ==", + "license": "MIT", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.3.tgz", + "integrity": "sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.19.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.4.1.tgz", + "integrity": "sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", + "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.5.0.tgz", + "integrity": "sha512-VMx4zlYWm7aBlZ5xtfJHpqa3Xgu3b7srV54fXYnXgsAcIGRqKSrhiK3f89omzzgaAgAtDOV4ImXnLKhVfheVNQ==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.1.2", + "prosemirror-model": "^1.8.1", + "prosemirror-state": "^1.3.1", + "prosemirror-transform": "^1.2.1", + "prosemirror-view": "^1.13.3" + } + }, + "node_modules/prosemirror-trailing-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", + "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", + "license": "MIT", + "dependencies": { + "@remirror/core-constants": "3.0.0", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.22.1", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.33.8" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.1.tgz", + "integrity": "sha512-jANxn50pNsBT7042mM4pxKpGyzgJWen/GrfCEQOPtHa5gLrQ2+0OgDtuQu2noG1X0+2cQ5uPsWqCRy7w7J+obw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.34.3", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.34.3.tgz", + "integrity": "sha512-mKZ54PrX19sSaQye+sef+YjBbNu2voNwLS1ivb6aD2IRmxRGW64HU9B644+7OfJStGLyxvOreKqEgfvXa91WIA==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -5324,6 +6083,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5593,6 +6361,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5650,6 +6424,51 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sass": { + "version": "1.79.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.79.4.tgz", + "integrity": "sha512-K0QDSNPXgyqO4GZq2HO5Q70TLxTH6cIT59RdoCHMivrC8rqzaTw5ab9prjz9KUN1El4FLXrBXJhik61JR4HcGg==", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "license": "MIT", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -6125,6 +6944,15 @@ "node": ">=0.8" } }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6288,6 +7116,12 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -6402,6 +7236,12 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index c539ea3..7a3e3e3 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,16 @@ "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", + "@tailwindcss/typography": "^0.5.15", + "@tiptap/extension-bubble-menu": "^2.8.0", + "@tiptap/extension-bullet-list": "^2.8.0", + "@tiptap/extension-heading": "^2.8.0", + "@tiptap/extension-list-item": "^2.8.0", + "@tiptap/extension-ordered-list": "^2.8.0", + "@tiptap/extension-underline": "^2.8.0", + "@tiptap/pm": "^2.8.0", + "@tiptap/react": "^2.8.0", + "@tiptap/starter-kit": "^2.8.0", "axios": "^1.6.7", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", @@ -39,6 +49,7 @@ "react": "^18", "react-day-picker": "^8.10.0", "react-dom": "^18", + "sass": "^1.79.4", "tailwind-merge": "^2.2.0", "tailwindcss-animate": "^1.0.7", "ts-luxon": "^4.5.2", diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index 3fdfaf5..d84ac2a 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -12,19 +12,18 @@ import { PresetActions } from "@/components/ui/preset-actions"; import { PresetSelector } from "@/components/ui/preset-selector"; import { current, future, past } from "@/data/presets"; import { useAppStateStore } from "@/lib/providers/app-state-store-provider"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { createNote, fetchNotesByCoachingSessionId, updateNote, } from "@/lib/api/notes"; -import { noteToString } from "@/types/note"; +import { defaultNote, Note, noteToString } from "@/types/note"; import { useAuthStore } from "@/lib/providers/auth-store-provider"; import { siteConfig } from "@/site.config"; import { CoachingSessionTitle } from "@/components/ui/coaching-sessions/coaching-session-title"; import { OverarchingGoalContainer } from "@/components/ui/coaching-sessions/overarching-goal-container"; -import { Id } from "@/types/general"; import { HoverCard, HoverCardContent, @@ -33,6 +32,10 @@ import { import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { LockClosedIcon, SymbolIcon } from "@radix-ui/react-icons"; +import { + CoachingNotes, + EditorRef, +} from "@/components/ui/coaching-sessions/coaching-notes"; // export const metadata: Metadata = { // title: "Coaching Session", @@ -40,13 +43,12 @@ import { LockClosedIcon, SymbolIcon } from "@radix-ui/react-icons"; // }; export default function CoachingSessionsPage() { - const [noteId, setNoteId] = useState<Id>(""); - const [note, setNote] = useState<string>(""); + const [note, setNote] = useState<Note>(defaultNote()); const [syncStatus, setSyncStatus] = useState<string>(""); const { userId } = useAuthStore((state) => ({ userId: state.userId })); - const { coachingSession, coachingRelationship } = useAppStateStore( - (state) => state - ); + const { coachingSession } = useAppStateStore((state) => state); + const [isLoading, setIsLoading] = useState(false); + const editorRef = useRef<EditorRef>(null); async function fetchNote() { if (!coachingSession.id) { @@ -55,48 +57,79 @@ export default function CoachingSessionsPage() { ); return; } + if (isLoading) { + console.debug( + "Not issuing a new Note fetch because a previous fetch is still in progress." + ); + } + + setIsLoading(true); await fetchNotesByCoachingSessionId(coachingSession.id) .then((notes) => { const note = notes[0]; if (notes.length > 0) { - console.trace("note: " + noteToString(note)); - setNoteId(note.id); - setNote(note.body); + console.trace("Fetched note: " + noteToString(note)); + setEditorContent(note.body); + setNote(note); setSyncStatus("Notes refreshed"); + setEditorFocussed(); } else { - console.trace("No Notes associated with this coachingSession.id"); + console.trace( + "No Notes associated with this coachingSession.id: " + + coachingSession.id + ); } }) .catch((err) => { console.error( - "Failed to fetch Note for current coaching session: " + err + "Failed to fetch Note for current coaching session id: " + + coachingSession.id + + ". Error: " + + err ); }); + + setIsLoading(false); } useEffect(() => { fetchNote(); - }, [coachingSession.id, noteId]); + }, []); - const handleInputChange = (value: string) => { - setNote(value); + const setEditorContent = (content: string) => { + editorRef.current?.setContent(`${content}`); + }; - if (noteId && coachingSession.id && userId) { - updateNote(noteId, coachingSession.id, userId, value) - .then((note) => { - console.trace("Updated Note: " + noteToString(note)); + const setEditorFocussed = () => { + editorRef.current?.setFocussed(); + }; + + const handleOnChange = (value: string) => { + console.debug("isLoading (before update/create): " + isLoading); + console.debug( + "coachingSession.id (before update/create): " + coachingSession.id + ); + console.debug("userId (before update/create): " + userId); + console.debug("value (before update/create): " + value); + console.debug("--------------------------------"); + + if (!isLoading && note.id && coachingSession.id && userId) { + updateNote(note.id, coachingSession.id, userId, value) + .then((updatedNote) => { + setNote(updatedNote); + console.trace("Updated Note: " + noteToString(updatedNote)); setSyncStatus("All changes saved"); }) .catch((err) => { setSyncStatus("Failed to save changes"); console.error("Failed to update Note: " + err); }); - } else if (!noteId && coachingSession.id && userId) { + } else if (!isLoading && !note.id && coachingSession.id && userId) { createNote(coachingSession.id, userId, value) - .then((note) => { - console.trace("Newly created Note: " + noteToString(note)); - setNoteId(note.id); + .then((createdNote) => { + setNote(createdNote); + console.trace("Newly created Note: " + noteToString(createdNote)); setSyncStatus("All changes saved"); }) .catch((err) => { @@ -145,7 +178,7 @@ export default function CoachingSessionsPage() { <div className="grid h-full items-stretch gap-6 md:grid-cols-[1fr_200px]"> <div className="flex-col space-y-4 sm:flex md:order-1"> <Tabs defaultValue="notes" className="flex-1"> - <TabsList className="grid flex w-128 grid-cols-2 justify-start"> + <TabsList className="flex w-128 grid-cols-2 justify-start"> <TabsTrigger value="notes">Notes</TabsTrigger> <TabsTrigger value="console">Console</TabsTrigger> <TabsTrigger value="coachs_notes" className="hidden"> @@ -158,8 +191,9 @@ export default function CoachingSessionsPage() { <TabsContent value="notes"> <div className="flex h-full flex-col space-y-4"> <CoachingNotes - value={note} - onChange={handleInputChange} + ref={editorRef} + value={note.body} + onChange={handleOnChange} onKeyDown={handleKeyDown} ></CoachingNotes> <p className="text-sm text-muted-foreground">{syncStatus}</p> @@ -210,60 +244,3 @@ export default function CoachingSessionsPage() { </> ); } - -// A debounced input CoachingNotes textarea component -// TODO: move this into the components dir -const CoachingNotes: React.FC<{ - value: string; - onChange: (value: string) => void; - onKeyDown: () => void; -}> = ({ value, onChange, onKeyDown }) => { - const WAIT_INTERVAL = 1000; - const [timer, setTimer] = useState<number | undefined>(undefined); - const [note, setNote] = useState<string>(value); - - // Make sure the internal value prop updates when the component interface's - // value prop changes. - useEffect(() => { - setNote(value); - }, [value]); - - const handleSessionNoteChange = ( - e: React.ChangeEvent<HTMLTextAreaElement> - ) => { - const newValue = e.target.value; - setNote(newValue); - - if (timer) { - clearTimeout(timer); - } - - const newTimer = window.setTimeout(() => { - onChange(newValue); - }, WAIT_INTERVAL); - - setTimer(newTimer); - }; - - const handleOnKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { - onKeyDown(); - }; - - useEffect(() => { - return () => { - if (timer) { - clearTimeout(timer); - } - }; - }, [timer]); - - return ( - <Textarea - placeholder="Session notes" - value={note} - className="p-4 min-h-[400px] md:min-h-[630px] lg:min-h-[630px]" - onChange={handleSessionNoteChange} - onKeyDown={handleOnKeyDown} - /> - ); -}; diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 6c9a881..38376da 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -8,8 +8,8 @@ import { UserAuthForm } from "@/components/user-auth-form"; import { siteConfig } from "@/site.config"; export const metadata: Metadata = { - title: "Authentication", - description: "Authentication forms built using the components.", + title: "Welcome to Refactor Coaching", + description: siteConfig.description, }; export default function AuthenticationPage() { diff --git a/src/components/ui/coaching-sessions/coaching-notes.tsx b/src/components/ui/coaching-sessions/coaching-notes.tsx new file mode 100644 index 0000000..0cc1d2f --- /dev/null +++ b/src/components/ui/coaching-sessions/coaching-notes.tsx @@ -0,0 +1,64 @@ +"use client"; + +import { forwardRef, useCallback, useEffect, useRef, useState } from "react"; +import { TipTapEditor } from "@/components/ui/coaching-sessions/tiptap-editor"; + +export interface EditorRef { + setContent: (content: string) => void; + setFocussed: () => void; +} + +interface CoachingNotesProps { + value: string; + onChange: (content: string) => void; + onKeyDown: () => void; +} + +const CoachingNotes = forwardRef<EditorRef, CoachingNotesProps>( + ({ value, onChange, onKeyDown }, ref) => { + const WAIT_INTERVAL = 1000; + const timerRef = useRef<number | undefined>(undefined); + const [note, setNote] = useState<string>(value); + + useEffect(() => { + setNote(value); + }, [value]); + + const handleSessionNoteChange = useCallback( + (newValue: string) => { + onKeyDown(); + setNote(newValue); + + if (timerRef.current) { + clearTimeout(timerRef.current); + } + + timerRef.current = window.setTimeout(() => { + console.debug( + "Calling onChange() from CoachingNotes::handleSessionNoteChange() timer" + ); + onChange(newValue); + }, WAIT_INTERVAL); + }, + [onKeyDown, onChange, WAIT_INTERVAL] + ); + + useEffect(() => { + return () => { + if (timerRef.current) { + clearTimeout(timerRef.current); + } + }; + }, []); + + return ( + <TipTapEditor + ref={ref} + editorContent={note} + onChange={handleSessionNoteChange} + /> + ); + } +); + +export { CoachingNotes }; diff --git a/src/components/ui/coaching-sessions/tiptap-editor.tsx b/src/components/ui/coaching-sessions/tiptap-editor.tsx new file mode 100644 index 0000000..81834f8 --- /dev/null +++ b/src/components/ui/coaching-sessions/tiptap-editor.tsx @@ -0,0 +1,191 @@ +"use client"; + +import { useEditor, EditorContent } from "@tiptap/react"; + +import StarterKit from "@tiptap/starter-kit"; +import BulletList from "@tiptap/extension-bullet-list"; +import ListItem from "@tiptap/extension-list-item"; +import OrderedList from "@tiptap/extension-ordered-list"; +import Underline from "@tiptap/extension-underline"; +import { + Bold, + Heading1, + Heading2, + Heading3, + Italic, + Underline as UnderlineIcon, + List, + ListOrdered, + Strikethrough, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { forwardRef, useImperativeHandle } from "react"; + +import "@/styles/styles.scss"; +import { EditorRef } from "./coaching-notes"; + +interface TipTapProps { + editorContent: string; + onChange: (content: string) => void; +} + +const TipTapEditor = forwardRef<EditorRef, TipTapProps>( + ({ editorContent, onChange }, ref) => { + const editor = useEditor( + { + extensions: [StarterKit, ListItem, Underline], + + autofocus: false, + immediatelyRender: false, + + editorProps: { + attributes: { + class: + "shadow appearance-none min-h-[150px] border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline", + }, + }, + + content: editorContent, + onUpdate: ({ editor }) => { + onChange(JSON.stringify(editor.getJSON())); + }, + }, + [] + ); + + useImperativeHandle(ref, () => ({ + setContent: (content: string) => { + editor?.commands.setContent(JSON.parse(content)); + }, + + setFocussed: () => { + editor?.chain().focus().run(); + }, + })); + + if (!editor) { + return null; + } + + return ( + <div className="flex flex-col justify-stretch min-h-[200px] border rounded border-b-0"> + <div className="flex items-center gap-0 mb-0"> + {/* Bold Button */} + <Button + variant="ghost" + onClick={() => editor.chain().focus().toggleBold().run()} + className={`p-2 rounded ${ + editor.isActive("bold") ? "bg-gray-200" : "" + }`} + title="Bold (Ctrl+B)" + > + <Bold className="h-4 w-4" /> + </Button> + + {/* Italic Button */} + <Button + variant="ghost" + onClick={() => editor.chain().focus().toggleItalic().run()} + className={`p-2 rounded ${ + editor.isActive("italic") ? "bg-gray-200" : "" + }`} + title="Italic (Ctrl+I)" + > + <Italic className="h-4 w-4" /> + </Button> + + {/* Underline Button */} + <Button + variant="ghost" + onClick={() => editor.chain().focus().toggleUnderline().run()} + className={`p-2 rounded ${ + editor.isActive("underline") ? "bg-gray-200" : "" + }`} + title="Italic (Ctrl+I)" + > + <UnderlineIcon className="h-4 w-4" /> + </Button> + + {/* Strikethrough Button */} + <Button + variant="ghost" + onClick={() => editor.chain().focus().toggleStrike().run()} + className={`p-2 rounded ${ + editor.isActive("strike") ? "bg-gray-200" : "" + }`} + title="Strike Through (Ctrl+Shift+X)" + > + <Strikethrough className="h-4 w-4" /> + </Button> + + {/* Heading 1 */} + <Button + variant="ghost" + onClick={() => + editor.chain().focus().toggleHeading({ level: 1 }).run() + } + className={`p-2 rounded ${ + editor.isActive("heading", { level: 1 }) ? "bg-gray-200" : "" + }`} + > + <Heading1 className="h-4 w-4" /> + </Button> + + {/* Heading */} + <Button + variant="ghost" + onClick={() => + editor.chain().focus().toggleHeading({ level: 2 }).run() + } + className={`p-2 rounded ${ + editor.isActive("heading", { level: 2 }) ? "bg-gray-200" : "" + }`} + > + <Heading2 className="h-4 w-4" /> + </Button> + + {/* Heading 3 */} + <Button + variant="ghost" + onClick={() => + editor.chain().focus().toggleHeading({ level: 3 }).run() + } + className={`p-2 rounded ${ + editor.isActive("heading", { level: 3 }) ? "bg-gray-200" : "" + }`} + > + <Heading3 className="h-4 w-4" /> + </Button> + + {/* Bullet List Button */} + <Button + variant="ghost" + onClick={() => editor.chain().focus().toggleBulletList().run()} + className={`p-2 rounded ${ + editor.isActive("bulletList") ? "bg-gray-200" : "" + }`} + title="Bullet List" + > + <List className="h-4 w-4" /> + </Button> + + {/* Ordered List Button */} + <Button + variant="ghost" + onClick={() => editor.chain().focus().toggleOrderedList().run()} + className={`p-2 rounded ${ + editor.isActive("orderedList") ? "bg-gray-200" : "" + }`} + title="Ordered List" + > + <ListOrdered className="h-4 w-4" /> + </Button> + </div> + {/* Editor Content */} + <EditorContent editor={editor} /> + </div> + ); + } +); + +export { TipTapEditor }; diff --git a/src/lib/api/notes.ts b/src/lib/api/notes.ts index 2254d19..cd85076 100644 --- a/src/lib/api/notes.ts +++ b/src/lib/api/notes.ts @@ -1,171 +1,172 @@ // Interacts with the note endpoints import { Id } from "@/types/general"; -import { defaultNote, isNote, isNoteArray, Note, noteToString, parseNote } from "@/types/note"; +import { + defaultNote, + isNote, + isNoteArray, + Note, + parseNote, +} from "@/types/note"; import { AxiosError, AxiosResponse } from "axios"; export const fetchNotesByCoachingSessionId = async ( - coachingSessionId: Id - ): Promise<Note[]> => { - const axios = require("axios"); - - var notes: Note[] = []; - var err: string = ""; - - const data = await axios - .get(`http://localhost:4000/notes`, { - params: { - coaching_session_id: coachingSessionId, - }, - withCredentials: true, - setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend - headers: { - "X-Version": "0.0.1", - }, - }) - .then(function (response: AxiosResponse) { - // handle success - var notes_data = response.data.data; - if (isNoteArray(notes_data)) { - notes_data.forEach((note_data: any) => { - notes.push(parseNote(note_data)) - }); - } - }) - .catch(function (error: AxiosError) { - // handle error - console.error(error.response?.status); - if (error.response?.status == 401) { - console.error("Retrieval of Note failed: unauthorized."); - err = "Retrieval of Note failed: unauthorized."; - } else if (error.response?.status == 404) { - console.error("Retrieval of Note failed: Note by coaching session Id (" + coachingSessionId + ") not found."); - err = "Retrieval of Note failed: Note by coaching session Id (" + coachingSessionId + ") not found."; - } else { - console.log(error); - console.error( - `Retrieval of Note by coaching session Id (` + coachingSessionId + `) failed.` - ); - err = - `Retrieval of Note by coaching session Id (` + coachingSessionId + `) failed.`; - } - }); - - if (err) - throw err; - - return notes; - }; + coachingSessionId: Id +): Promise<Note[]> => { + const axios = require("axios"); -export const createNote = async ( - coaching_session_id: Id, - user_id: Id, - body: string - ): Promise<Note> => { - const axios = require("axios"); - - const newNoteJson = { - coaching_session_id: coaching_session_id, - user_id: user_id, - body: body - }; - console.debug("newNoteJson: " + JSON.stringify(newNoteJson)); - // A full real note to be returned from the backend with the same body - var createdNote: Note = defaultNote(); - var err: string = ""; - - //var strNote: string = noteToString(note); - const data = await axios - .post(`http://localhost:4000/notes`, newNoteJson, { - withCredentials: true, - setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend - headers: { - "X-Version": "0.0.1", - "Content-Type": "application/json", - }, - }) - .then(function (response: AxiosResponse) { - // handle success - const noteStr = response.data.data; - if (isNote(noteStr)) { - createdNote = parseNote(noteStr); - } - }) - .catch(function (error: AxiosError) { - // handle error - console.error(error.response?.status); - if (error.response?.status == 401) { - console.error("Creation of Note failed: unauthorized."); - err = "Creation of Note failed: unauthorized."; - } else if (error.response?.status == 500) { - console.error( - "Creation of Note failed: internal server error." - ); - err = "Creation of Note failed: internal server error."; - } else { - console.log(error); - console.error(`Creation of new Note failed.`); - err = `Creation of new Note failed.`; - } + var notes: Note[] = []; + var err: string = ""; + + const data = await axios + .get(`http://localhost:4000/notes`, { + params: { + coaching_session_id: coachingSessionId, + }, + withCredentials: true, + setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend + headers: { + "X-Version": "0.0.1", + }, + }) + .then(function (response: AxiosResponse) { + // handle success + var notes_data = response.data.data; + if (isNoteArray(notes_data)) { + notes_data.forEach((note_data: any) => { + notes.push(parseNote(note_data)); + }); } - ); - - if (err) - throw err; - - return createdNote; + }) + .catch(function (error: AxiosError) { + // handle error + console.error(error.response?.status); + if (error.response?.status == 401) { + err = "Retrieval of Note failed: unauthorized."; + } else if (error.response?.status == 404) { + err = + "Retrieval of Note failed: Note by coaching session Id (" + + coachingSessionId + + ") not found."; + } else { + err = + `Retrieval of Note by coaching session Id (` + + coachingSessionId + + `) failed.`; + } + }); + + if (err) { + console.log(err); + throw err; + } + + return notes; +}; + +export const createNote = async ( + coaching_session_id: Id, + user_id: Id, + body: string +): Promise<Note> => { + const axios = require("axios"); + + const newNoteJson = { + coaching_session_id: coaching_session_id, + user_id: user_id, + body: body, }; + console.debug("newNoteJson: " + JSON.stringify(newNoteJson)); + // A full real note to be returned from the backend with the same body + var createdNote: Note = defaultNote(); + var err: string = ""; - export const updateNote = async ( - id: Id, - user_id: Id, - coaching_session_id: Id, - body: string, - ): Promise<Note> => { - const axios = require("axios"); - - var updatedNote: Note = defaultNote(); - var err: string = ""; - - const newNoteJson = { - coaching_session_id: coaching_session_id, - user_id: user_id, - body: body - }; - - const data = await axios - .put(`http://localhost:4000/notes/${id}`, newNoteJson, { - withCredentials: true, - setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend - headers: { - "X-Version": "0.0.1", - "Content-Type": "application/json", - }, - }) - .then(function (response: AxiosResponse) { - // handle success - if (isNote(response.data.data)) { - updatedNote = response.data.data; - } - }) - .catch(function (error: AxiosError) { - // handle error - console.error(error.response?.status); - if (error.response?.status == 401) { - console.error("Update of Note failed: unauthorized."); - err = "Update of Organization failed: unauthorized."; - } else if (error.response?.status == 500) { - console.error("Update of Organization failed: internal server error."); - err = "Update of Organization failed: internal server error."; - } else { - console.log(error); - console.error(`Update of new Organization failed.`); - err = `Update of new Organization failed.`; - } - }); - - if (err) - throw err; - - return updatedNote; + //var strNote: string = noteToString(note); + const data = await axios + .post(`http://localhost:4000/notes`, newNoteJson, { + withCredentials: true, + setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend + headers: { + "X-Version": "0.0.1", + "Content-Type": "application/json", + }, + }) + .then(function (response: AxiosResponse) { + // handle success + const noteStr = response.data.data; + if (isNote(noteStr)) { + createdNote = parseNote(noteStr); + } + }) + .catch(function (error: AxiosError) { + // handle error + console.error(error.response?.status); + if (error.response?.status == 401) { + err = "Creation of Note failed: unauthorized."; + } else if (error.response?.status == 500) { + err = "Creation of Note failed: internal server error."; + } else { + err = `Creation of new Note failed: ${error}`; + } + }); + + if (err) { + console.log(err); + throw err; + } + + return createdNote; +}; + +export const updateNote = async ( + id: Id, + user_id: Id, + coaching_session_id: Id, + body: string +): Promise<Note> => { + const axios = require("axios"); + + var updatedNote: Note = defaultNote(); + var err: string = ""; + + const newNoteJson = { + id: id, + coaching_session_id: coaching_session_id, + user_id: user_id, + body: body, }; + + const data = await axios + .put(`http://localhost:4000/notes/${id}`, newNoteJson, { + withCredentials: true, + setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend + headers: { + "X-Version": "0.0.1", + "Content-Type": "application/json", + }, + }) + .then(function (response: AxiosResponse) { + // handle success + if (isNote(response.data.data)) { + updatedNote = response.data.data; + } + }) + .catch(function (error: AxiosError) { + // handle error + console.error(error.response?.status); + if (error.response?.status == 401) { + err = "Update of Note failed: unauthorized."; + } else if (error.response?.status == 500) { + err = "Update of Note failed: internal server error."; + } else { + err = `Update of new Note failed: ${error}`; + } + }); + + if (err) { + console.log(err); + throw err; + } + + return updatedNote; +}; diff --git a/src/styles/styles.scss b/src/styles/styles.scss new file mode 100644 index 0000000..1d5a9f4 --- /dev/null +++ b/src/styles/styles.scss @@ -0,0 +1,95 @@ +/* Basic TipTap editor styles */ +.tiptap { + :first-child { + margin-top: 0; + } + + /* List styles */ + ul, + ol { + padding: 0 1rem; + margin: 1.0rem 1rem 1.25rem 0.4rem; + + li p { + margin-top: 0.25em; + margin-bottom: 0.25em; + } + } + + ul { list-style-type: disc; } + + ol { list-style-type: decimal; } + + /* Heading styles */ + h1, + h2, + h3, + h4, + h5, + h6 { + line-height: 1.1; + //margin-top: 2.5rem; + text-wrap: pretty; + } + + h1, + h2 { + margin-top: 1.0rem; + // margin-bottom: 1.5rem; + } + + h1 { + font-size: 1.4rem; + } + + h2 { + font-size: 1.2rem; + } + + h3 { + font-size: 1.1rem; + } + + h4, + h5, + h6 { + font-size: 1rem; + } + + /* Code and preformatted text styles */ + code { + background-color: var(--purple-light); + border-radius: 0.4rem; + color: var(--black); + font-size: 0.85rem; + padding: 0.25em 0.3em; + } + + pre { + background: var(--black); + border-radius: 0.5rem; + color: var(--white); + font-family: 'JetBrainsMono', monospace; + margin: 1.5rem 0; + padding: 0.75rem 1rem; + + code { + background: none; + color: inherit; + font-size: 0.8rem; + padding: 0; + } + } + + blockquote { + border-left: 3px solid var(--gray-3); + margin: 1.5rem 0; + padding-left: 1rem; + } + + hr { + border: none; + border-top: 1px solid var(--gray-2); + margin: 2rem 0; + } + } From ef2f305b5d04e5d601d2a41cbd1d6ed990370bfd Mon Sep 17 00:00:00 2001 From: Jim Hodapp <james.hodapp@gmail.com> Date: Sat, 12 Oct 2024 17:13:50 -0500 Subject: [PATCH 02/14] Set min-heights for different media breakpoints --- src/components/ui/coaching-sessions/tiptap-editor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ui/coaching-sessions/tiptap-editor.tsx b/src/components/ui/coaching-sessions/tiptap-editor.tsx index 81834f8..edd2a77 100644 --- a/src/components/ui/coaching-sessions/tiptap-editor.tsx +++ b/src/components/ui/coaching-sessions/tiptap-editor.tsx @@ -41,7 +41,7 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( editorProps: { attributes: { class: - "shadow appearance-none min-h-[150px] border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline", + "shadow appearance-none lg:min-h-[600px] sm:min-h-[200px] md:min-h-[400px] border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline", }, }, From 41448307a3caa65cc6a62a775dbd1e63c868ad4a Mon Sep 17 00:00:00 2001 From: Jim Hodapp <james.hodapp@gmail.com> Date: Sat, 12 Oct 2024 20:02:32 -0500 Subject: [PATCH 03/14] Some style/margin tweaks to the editor and toolbar. --- src/components/ui/coaching-sessions/tiptap-editor.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/ui/coaching-sessions/tiptap-editor.tsx b/src/components/ui/coaching-sessions/tiptap-editor.tsx index edd2a77..93a3510 100644 --- a/src/components/ui/coaching-sessions/tiptap-editor.tsx +++ b/src/components/ui/coaching-sessions/tiptap-editor.tsx @@ -41,7 +41,7 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( editorProps: { attributes: { class: - "shadow appearance-none lg:min-h-[600px] sm:min-h-[200px] md:min-h-[400px] border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline", + "shadow appearance-none lg:min-h-[600px] sm:min-h-[200px] md:min-h-[400px] rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline", }, }, @@ -68,8 +68,9 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( } return ( - <div className="flex flex-col justify-stretch min-h-[200px] border rounded border-b-0"> - <div className="flex items-center gap-0 mb-0"> + <div className="flex flex-col justify-stretch border rounded border-b-0"> + {/* Toolbar style */} + <div className="flex items-center gap-0 mt-1 mx-1 mb-0"> {/* Bold Button */} <Button variant="ghost" From fae7addbdd5212c7b8552b861614c54e6dd90d8a Mon Sep 17 00:00:00 2001 From: Jim Hodapp <james.hodapp@gmail.com> Date: Sat, 12 Oct 2024 20:16:40 -0500 Subject: [PATCH 04/14] Add Refactor logo to login page --- src/app/login/page.tsx | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 38376da..bd50509 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -6,6 +6,7 @@ import { cn } from "@/lib/utils"; import { buttonVariants } from "@/components/ui/button"; import { UserAuthForm } from "@/components/user-auth-form"; import { siteConfig } from "@/site.config"; +import { Icons } from "@/components/ui/icons"; export const metadata: Metadata = { title: "Welcome to Refactor Coaching", @@ -44,7 +45,7 @@ export default function AuthenticationPage() { <div className="relative hidden h-full flex-col bg-muted p-10 text-white lg:flex dark:border-r"> <div className="absolute inset-0 bg-zinc-900" /> <div className="relative z-20 flex items-center text-lg font-medium"> - <svg + {/* <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" @@ -55,7 +56,23 @@ export default function AuthenticationPage() { className="mr-2 h-6 w-6" > <path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" /> - </svg> + </svg> */} + <Link + href="/dashboard" + className="mr-2 flex items-center space-x-2" + > + <div + className={cn( + buttonVariants({ + variant: "ghost", + }), + "w-9 px-0" + )} + > + <Icons.refactor_logo className="h-7 w-7" /> + <span className="sr-only">Refactor</span> + </div> + </Link> {siteConfig.name} </div> <div className="relative z-20 mt-auto"> From 003eec66d912eb096d40ad9af1bcc364533814cb Mon Sep 17 00:00:00 2001 From: Jim Hodapp <james.hodapp@gmail.com> Date: Sat, 12 Oct 2024 20:22:03 -0500 Subject: [PATCH 05/14] Update the URL of the Refactor logo link --- src/app/login/page.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index bd50509..d15fd3c 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -45,20 +45,8 @@ export default function AuthenticationPage() { <div className="relative hidden h-full flex-col bg-muted p-10 text-white lg:flex dark:border-r"> <div className="absolute inset-0 bg-zinc-900" /> <div className="relative z-20 flex items-center text-lg font-medium"> - {/* <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth="2" - strokeLinecap="round" - strokeLinejoin="round" - className="mr-2 h-6 w-6" - > - <path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" /> - </svg> */} <Link - href="/dashboard" + href="https://www.refactorcoach.com" className="mr-2 flex items-center space-x-2" > <div From 1af4d7eb192deb49288395dbc5c2c06d0a9333c2 Mon Sep 17 00:00:00 2001 From: Jim Hodapp <james.hodapp@gmail.com> Date: Sun, 13 Oct 2024 18:40:02 -0500 Subject: [PATCH 06/14] Tweak editor styling further, add text highlighting and code blocks (style doesn't work correctly yet for this) --- package-lock.json | 14 ++++++ package.json | 1 + src/app/coaching-sessions/[id]/page.tsx | 2 +- .../ui/coaching-sessions/tiptap-editor.tsx | 43 +++++++++++++++++-- src/components/ui/site-header.tsx | 2 +- src/styles/styles.scss | 28 ++++++------ 6 files changed, 71 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index e0b05d1..f07bde6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "@tiptap/extension-bubble-menu": "^2.8.0", "@tiptap/extension-bullet-list": "^2.8.0", "@tiptap/extension-heading": "^2.8.0", + "@tiptap/extension-highlight": "^2.8.0", "@tiptap/extension-list-item": "^2.8.0", "@tiptap/extension-ordered-list": "^2.8.0", "@tiptap/extension-underline": "^2.8.0", @@ -1859,6 +1860,19 @@ "@tiptap/core": "^2.7.0" } }, + "node_modules/@tiptap/extension-highlight": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.8.0.tgz", + "integrity": "sha512-vyqX7D449nuARhI0AyRqtIZReFg3sfc/U/q1p3JOjtUoW6z2jmDTzshiKRrSg+Jf7Hhzj1pqwU+6+CpelPPDpA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, "node_modules/@tiptap/extension-history": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.8.0.tgz", diff --git a/package.json b/package.json index 7a3e3e3..d43b11c 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@tiptap/extension-bubble-menu": "^2.8.0", "@tiptap/extension-bullet-list": "^2.8.0", "@tiptap/extension-heading": "^2.8.0", + "@tiptap/extension-highlight": "^2.8.0", "@tiptap/extension-list-item": "^2.8.0", "@tiptap/extension-ordered-list": "^2.8.0", "@tiptap/extension-underline": "^2.8.0", diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index d84ac2a..ba3ea8c 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -95,7 +95,7 @@ export default function CoachingSessionsPage() { useEffect(() => { fetchNote(); - }, []); + }, [coachingSession.id]); const setEditorContent = (content: string) => { editorRef.current?.setContent(`${content}`); diff --git a/src/components/ui/coaching-sessions/tiptap-editor.tsx b/src/components/ui/coaching-sessions/tiptap-editor.tsx index 93a3510..78c44e7 100644 --- a/src/components/ui/coaching-sessions/tiptap-editor.tsx +++ b/src/components/ui/coaching-sessions/tiptap-editor.tsx @@ -4,6 +4,7 @@ import { useEditor, EditorContent } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import BulletList from "@tiptap/extension-bullet-list"; +import Highlight from "@tiptap/extension-highlight"; import ListItem from "@tiptap/extension-list-item"; import OrderedList from "@tiptap/extension-ordered-list"; import Underline from "@tiptap/extension-underline"; @@ -12,11 +13,13 @@ import { Heading1, Heading2, Heading3, + Highlighter, Italic, Underline as UnderlineIcon, List, ListOrdered, Strikethrough, + Braces, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { forwardRef, useImperativeHandle } from "react"; @@ -33,7 +36,14 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( ({ editorContent, onChange }, ref) => { const editor = useEditor( { - extensions: [StarterKit, ListItem, Underline], + extensions: [ + StarterKit, + ListItem, + Underline, + Highlight, + BulletList, + OrderedList, + ], autofocus: false, immediatelyRender: false, @@ -41,7 +51,9 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( editorProps: { attributes: { class: - "shadow appearance-none lg:min-h-[600px] sm:min-h-[200px] md:min-h-[400px] rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline", + // Make this responsive to light/dark mode + // Also is the background what's preventing the codeblock background color from working? + "shadow appearance-none lg:min-h-[440px] sm:min-h-[200px] md:min-h-[350px] rounded w-full py-2 px-3 bg-inherit text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline", }, }, @@ -114,11 +126,23 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( className={`p-2 rounded ${ editor.isActive("strike") ? "bg-gray-200" : "" }`} - title="Strike Through (Ctrl+Shift+X)" + title="Strike Through" > <Strikethrough className="h-4 w-4" /> </Button> + {/* Highlight Button */} + <Button + variant="ghost" + onClick={() => editor.chain().focus().toggleHighlight().run()} + className={`p-2 rounded ${ + editor.isActive("highlight") ? "bg-gray-200" : "" + }`} + title="Highlight Text" + > + <Highlighter className="h-4 w-4" /> + </Button> + {/* Heading 1 */} <Button variant="ghost" @@ -128,6 +152,7 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( className={`p-2 rounded ${ editor.isActive("heading", { level: 1 }) ? "bg-gray-200" : "" }`} + title="Heading1" > <Heading1 className="h-4 w-4" /> </Button> @@ -141,6 +166,7 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( className={`p-2 rounded ${ editor.isActive("heading", { level: 2 }) ? "bg-gray-200" : "" }`} + title="Heading2" > <Heading2 className="h-4 w-4" /> </Button> @@ -154,6 +180,7 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( className={`p-2 rounded ${ editor.isActive("heading", { level: 3 }) ? "bg-gray-200" : "" }`} + title="Heading3" > <Heading3 className="h-4 w-4" /> </Button> @@ -181,6 +208,16 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( > <ListOrdered className="h-4 w-4" /> </Button> + + <Button + variant="ghost" + onClick={() => editor.chain().focus().toggleCodeBlock().run()} + className={`p-2 rounded ${ + editor.isActive("codeBlock") ? "is-active" : "" + }`} + > + <Braces className="h-4 w-4" /> + </Button> </div> {/* Editor Content */} <EditorContent editor={editor} /> diff --git a/src/components/ui/site-header.tsx b/src/components/ui/site-header.tsx index 1e97c57..102e50a 100644 --- a/src/components/ui/site-header.tsx +++ b/src/components/ui/site-header.tsx @@ -19,7 +19,7 @@ export function SiteHeader() { return ( <header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"> - <div className="flex h-14 pl-4 max-w-screen-2xl items-start"> + <div className="flex h-14 pl-4 pt-2 max-w-screen-2xl items-start"> <MainNav /> <MobileNav /> <div className="flex flex-1 items-center justify-between space-x-2 md:justify-end"> diff --git a/src/styles/styles.scss b/src/styles/styles.scss index 1d5a9f4..918ee32 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -8,7 +8,7 @@ ul, ol { padding: 0 1rem; - margin: 1.0rem 1rem 1.25rem 0.4rem; + margin: 0.5rem 1.0rem 0.5rem 0.4rem; li p { margin-top: 0.25em; @@ -66,20 +66,20 @@ } pre { - background: var(--black); - border-radius: 0.5rem; - color: var(--white); - font-family: 'JetBrainsMono', monospace; - margin: 1.5rem 0; - padding: 0.75rem 1rem; - - code { - background: none; - color: inherit; - font-size: 0.8rem; - padding: 0; + background: var(--black); + border-radius: 0.5rem; + color: var(--white); + font-family: 'JetBrainsMono', monospace; + margin: 1.5rem 0; + padding: 0.75rem 1rem; + + code { + background: none; + color: inherit; + font-size: 0.8rem; + padding: 0; + } } - } blockquote { border-left: 3px solid var(--gray-3); From e4beccc2c8ec94f16b6d63bc199e94a79b04597a Mon Sep 17 00:00:00 2001 From: Jim Hodapp <james.hodapp@gmail.com> Date: Tue, 15 Oct 2024 22:53:44 -0500 Subject: [PATCH 07/14] Code block with syntax highlighting working, but styling not quite correct yet --- package-lock.json | 86 ++++++- package.json | 1 + src/app/coaching-sessions/[id]/page.tsx | 6 +- .../ui/coaching-sessions/code-block.tsx | 39 +++ .../ui/coaching-sessions/tiptap-editor.tsx | 33 ++- src/styles/code-block.scss | 14 ++ src/styles/styles.scss | 235 ++++++++++++------ 7 files changed, 325 insertions(+), 89 deletions(-) create mode 100644 src/components/ui/coaching-sessions/code-block.tsx create mode 100644 src/styles/code-block.scss diff --git a/package-lock.json b/package-lock.json index f07bde6..bce0ed3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "@tailwindcss/typography": "^0.5.15", "@tiptap/extension-bubble-menu": "^2.8.0", "@tiptap/extension-bullet-list": "^2.8.0", + "@tiptap/extension-code-block-lowlight": "^2.8.0", "@tiptap/extension-heading": "^2.8.0", "@tiptap/extension-highlight": "^2.8.0", "@tiptap/extension-list-item": "^2.8.0", @@ -1776,6 +1777,23 @@ "@tiptap/pm": "^2.7.0" } }, + "node_modules/@tiptap/extension-code-block-lowlight": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.8.0.tgz", + "integrity": "sha512-6RRGtzmRXlUrxJXz6cuTIpGTeMe+0Mz3XBQaZ0t7Y7PgbBDFj9tw9+LeO4eHLS/SraX4Y+P73HzKe03AdfQ95A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/extension-code-block": "^2.7.0", + "@tiptap/pm": "^2.7.0", + "highlight.js": "^11", + "lowlight": "^2 || ^3" + } + }, "node_modules/@tiptap/extension-document": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.8.0.tgz", @@ -2102,6 +2120,16 @@ "url": "https://github.com/sponsors/ueberdosis" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -2164,6 +2192,13 @@ "@types/react": "*" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT", + "peer": true + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", @@ -3344,7 +3379,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "engines": { "node": ">=6" } @@ -3354,6 +3388,20 @@ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "peer": true, + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -4515,6 +4563,16 @@ "node": ">= 0.4" } }, + "node_modules/highlight.js": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz", + "integrity": "sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -5170,6 +5228,32 @@ "loose-envify": "cli.js" } }, + "node_modules/lowlight": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.1.0.tgz", + "integrity": "sha512-CEbNVoSikAxwDMDPjXlqlFYiZLkDJHwyGu/MfOsJnF3d7f3tds5J3z8s/l9TMXhzfsJCCJEAsD78842mwmg0PQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "highlight.js": "~11.9.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lowlight/node_modules/highlight.js": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz", + "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/lru-cache": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", diff --git a/package.json b/package.json index d43b11c..3e25f93 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@tailwindcss/typography": "^0.5.15", "@tiptap/extension-bubble-menu": "^2.8.0", "@tiptap/extension-bullet-list": "^2.8.0", + "@tiptap/extension-code-block-lowlight": "^2.8.0", "@tiptap/extension-heading": "^2.8.0", "@tiptap/extension-highlight": "^2.8.0", "@tiptap/extension-list-item": "^2.8.0", diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index ba3ea8c..6c34138 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -153,7 +153,7 @@ export default function CoachingSessionsPage() { return ( <> - <div className="h-full flex-col md:flex"> + <div className="flex-col h-full md:flex"> <div className="flex flex-col items-start justify-between space-y-2 py-4 px-4 sm:flex-row sm:items-center sm:space-y-0 md:h-16"> <CoachingSessionTitle locale={siteConfig.locale} @@ -177,7 +177,7 @@ export default function CoachingSessionsPage() { <div className="row-span-1 h-full py-4 px-4"> <div className="grid h-full items-stretch gap-6 md:grid-cols-[1fr_200px]"> <div className="flex-col space-y-4 sm:flex md:order-1"> - <Tabs defaultValue="notes" className="flex-1"> + <Tabs defaultValue="notes"> <TabsList className="flex w-128 grid-cols-2 justify-start"> <TabsTrigger value="notes">Notes</TabsTrigger> <TabsTrigger value="console">Console</TabsTrigger> @@ -189,7 +189,7 @@ export default function CoachingSessionsPage() { </TabsTrigger> </TabsList> <TabsContent value="notes"> - <div className="flex h-full flex-col space-y-4"> + <div className="flex-col h-full space-y-4"> <CoachingNotes ref={editorRef} value={note.body} diff --git a/src/components/ui/coaching-sessions/code-block.tsx b/src/components/ui/coaching-sessions/code-block.tsx new file mode 100644 index 0000000..6b198f2 --- /dev/null +++ b/src/components/ui/coaching-sessions/code-block.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import { NodeViewProps, NodeViewWrapper, NodeViewContent } from "@tiptap/react"; + +import "@/styles/code-block.scss"; + +const CodeBlock: React.FC<NodeViewProps> = ({ + node, + updateAttributes, + extension, +}) => { + const language = node.attrs.language || "auto"; + + return ( + <NodeViewWrapper className="code-block"> + <select + contentEditable={false} + defaultValue={language} + onChange={(event: React.ChangeEvent<HTMLSelectElement>) => + updateAttributes({ language: event.target.value }) + } + > + <option value="auto">auto</option> + <option disabled>—</option> + {extension.options.lowlight + .listLanguages() + .map((lang: string, index: number) => ( + <option key={index} value={lang}> + {lang} + </option> + ))} + </select> + <pre> + <NodeViewContent as="code" /> + </pre> + </NodeViewWrapper> + ); +}; + +export default CodeBlock; diff --git a/src/components/ui/coaching-sessions/tiptap-editor.tsx b/src/components/ui/coaching-sessions/tiptap-editor.tsx index 78c44e7..73f4662 100644 --- a/src/components/ui/coaching-sessions/tiptap-editor.tsx +++ b/src/components/ui/coaching-sessions/tiptap-editor.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEditor, EditorContent } from "@tiptap/react"; +import { useEditor, EditorContent, ReactNodeViewRenderer } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import BulletList from "@tiptap/extension-bullet-list"; @@ -8,6 +8,7 @@ import Highlight from "@tiptap/extension-highlight"; import ListItem from "@tiptap/extension-list-item"; import OrderedList from "@tiptap/extension-ordered-list"; import Underline from "@tiptap/extension-underline"; +import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; import { Bold, Heading1, @@ -24,9 +25,27 @@ import { import { Button } from "@/components/ui/button"; import { forwardRef, useImperativeHandle } from "react"; -import "@/styles/styles.scss"; import { EditorRef } from "./coaching-notes"; +import css from "highlight.js/lib/languages/css"; +import js from "highlight.js/lib/languages/javascript"; +import ts from "highlight.js/lib/languages/typescript"; +import html from "highlight.js/lib/languages/xml"; +// Load all languages with "all" or common languages with "common" +import { all, createLowlight } from "lowlight"; + +// eslint-disable-next-line +import CodeBlock from "@/components/ui/coaching-sessions/code-block"; + +import "@/styles/styles.scss"; + +const lowlight = createLowlight(all); + +lowlight.register("html", html); +lowlight.register("css", css); +lowlight.register("js", js); +lowlight.register("ts", ts); + interface TipTapProps { editorContent: string; onChange: (content: string) => void; @@ -43,6 +62,11 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( Highlight, BulletList, OrderedList, + CodeBlockLowlight.extend({ + addNodeView() { + return ReactNodeViewRenderer(CodeBlock); + }, + }).configure({ lowlight }), ], autofocus: false, @@ -53,7 +77,8 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( class: // Make this responsive to light/dark mode // Also is the background what's preventing the codeblock background color from working? - "shadow appearance-none lg:min-h-[440px] sm:min-h-[200px] md:min-h-[350px] rounded w-full py-2 px-3 bg-inherit text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline", + // "tiptap ProseMirror", + "tiptap ProseMirror shadow appearance-none lg:min-h-[440px] sm:min-h-[200px] md:min-h-[350px] rounded w-full py-2 px-3 bg-inherit text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline", }, }, @@ -80,7 +105,7 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( } return ( - <div className="flex flex-col justify-stretch border rounded border-b-0"> + <div className="border rounded"> {/* Toolbar style */} <div className="flex items-center gap-0 mt-1 mx-1 mb-0"> {/* Bold Button */} diff --git a/src/styles/code-block.scss b/src/styles/code-block.scss new file mode 100644 index 0000000..a63ac30 --- /dev/null +++ b/src/styles/code-block.scss @@ -0,0 +1,14 @@ +.tiptap { + .code-block { + position: relative; + + select { + position: absolute; + background-color: white; + border: round($number: 5); + //background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="Black" d="M7 10l5 5 5-5z"/></svg>'); + right: 0.5rem; + top: 0.5rem; + } + } + } \ No newline at end of file diff --git a/src/styles/styles.scss b/src/styles/styles.scss index 918ee32..3be1ad2 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -1,95 +1,168 @@ /* Basic TipTap editor styles */ .tiptap { - :first-child { - margin-top: 0; - } - - /* List styles */ - ul, - ol { - padding: 0 1rem; - margin: 0.5rem 1.0rem 0.5rem 0.4rem; - - li p { - margin-top: 0.25em; - margin-bottom: 0.25em; - } + :first-child { + margin-top: 0; + } + + /* List styles */ + ul, + ol { + padding: 0 1rem; + margin: 0.5rem 1rem 0.5rem 0.4rem; + + li p { + margin-top: 0.25em; + margin-bottom: 0.25em; } + } + + ul { + list-style-type: disc; + } + + ol { + list-style-type: decimal; + } + + /* Heading styles */ + h1, + h2, + h3, + h4, + h5, + h6 { + line-height: 1.1; + //margin-top: 2.5rem; + text-wrap: pretty; + } + + h1, + h2 { + margin-top: 1rem; + margin-bottom: 1rem; + } + + h1 { + font-size: 1.4rem; + } + + h2 { + font-size: 1.2rem; + } + + h3 { + font-size: 1.0rem; + } - ul { list-style-type: disc; } - - ol { list-style-type: decimal; } - - /* Heading styles */ - h1, - h2, - h3, - h4, - h5, - h6 { - line-height: 1.1; - //margin-top: 2.5rem; - text-wrap: pretty; + h4, + h5, + h6 { + font-size: 1rem; + } + + pre { + background: black; + border-radius: 0.5rem; + color: white; + font-family: "JetBrainsMono", monospace; + margin: 1.5rem 0; + padding: 0.75rem 1rem; + + code { + background: none; + color: inherit; + font-size: 0.8rem; + padding: 0; } - - h1, - h2 { - margin-top: 1.0rem; - // margin-bottom: 1.5rem; + + /* Code styling */ + .hljs-comment, + .hljs-quote { + color: #616161; } - - h1 { - font-size: 1.4rem; + + .hljs-variable, + .hljs-template-variable, + .hljs-attribute, + .hljs-tag, + .hljs-name, + .hljs-regexp, + .hljs-link, + .hljs-name, + .hljs-selector-id, + .hljs-selector-class { + color: #f98181; } - - h2 { - font-size: 1.2rem; + + .hljs-number, + .hljs-meta, + .hljs-built_in, + .hljs-builtin-name, + .hljs-literal, + .hljs-type, + .hljs-params { + color: #fbbc88; } - - h3 { - font-size: 1.1rem; + + .hljs-string, + .hljs-symbol, + .hljs-bullet { + color: #b9f18d; } - - h4, - h5, - h6 { - font-size: 1rem; + + .hljs-title, + .hljs-section { + color: #faf594; } - - /* Code and preformatted text styles */ - code { - background-color: var(--purple-light); - border-radius: 0.4rem; - color: var(--black); - font-size: 0.85rem; - padding: 0.25em 0.3em; + + .hljs-keyword, + .hljs-selector-tag { + color: #70cff8; } - - pre { - background: var(--black); - border-radius: 0.5rem; - color: var(--white); - font-family: 'JetBrainsMono', monospace; - margin: 1.5rem 0; - padding: 0.75rem 1rem; - - code { - background: none; - color: inherit; - font-size: 0.8rem; - padding: 0; - } - } - - blockquote { - border-left: 3px solid var(--gray-3); - margin: 1.5rem 0; - padding-left: 1rem; + + .hljs-emphasis { + font-style: italic; } - - hr { - border: none; - border-top: 1px solid var(--gray-2); - margin: 2rem 0; + + .hljs-strong { + font-weight: 700; } } + + /* Code and preformatted text styles */ + // code { + // background-color: var(--purple-light); + // border-radius: 0.4rem; + // color: var(--black); + // font-size: 0.85rem; + // padding: 0.25em 0.3em; + // } + + // pre { + // background: var(--black); + // border-radius: 0.5rem; + // color: var(--white); + // font-family: 'JetBrainsMono', monospace; + // margin: 1.5rem 0; + // padding: 0.75rem 1rem; + + // code { + // background: none; + // color: inherit; + // font-size: 0.8rem; + // padding: 0; + // } + // } + + // blockquote { + // border-left: 3px solid var(--gray-3); + // margin: 1.5rem 0; + // padding-left: 1rem; + // } + + hr { + border: none; + border-top: 1px solid var(--gray-2); + margin: 2rem 0; + } +} From d2c703f91324b2bdee45f60d7f9102608eef23b2 Mon Sep 17 00:00:00 2001 From: Jim Hodapp <james.hodapp@gmail.com> Date: Tue, 15 Oct 2024 23:03:00 -0500 Subject: [PATCH 08/14] Take care of build errors --- package-lock.json | 9 +++++---- src/app/coaching-sessions/[id]/page.tsx | 2 +- src/components/ui/coaching-sessions/coaching-notes.tsx | 2 ++ src/components/ui/coaching-sessions/tiptap-editor.tsx | 2 ++ 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index bce0ed3..3835d36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2820,9 +2820,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001612", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz", - "integrity": "sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==", + "version": "1.0.30001668", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001668.tgz", + "integrity": "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==", "funding": [ { "type": "opencollective", @@ -2836,7 +2836,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "4.1.2", diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index 6c34138..e997151 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -95,7 +95,7 @@ export default function CoachingSessionsPage() { useEffect(() => { fetchNote(); - }, [coachingSession.id]); + }, [coachingSession.id, isLoading]); const setEditorContent = (content: string) => { editorRef.current?.setContent(`${content}`); diff --git a/src/components/ui/coaching-sessions/coaching-notes.tsx b/src/components/ui/coaching-sessions/coaching-notes.tsx index 0cc1d2f..b270bf9 100644 --- a/src/components/ui/coaching-sessions/coaching-notes.tsx +++ b/src/components/ui/coaching-sessions/coaching-notes.tsx @@ -61,4 +61,6 @@ const CoachingNotes = forwardRef<EditorRef, CoachingNotesProps>( } ); +CoachingNotes.displayName = "CoachingNotes"; + export { CoachingNotes }; diff --git a/src/components/ui/coaching-sessions/tiptap-editor.tsx b/src/components/ui/coaching-sessions/tiptap-editor.tsx index 73f4662..4ac881c 100644 --- a/src/components/ui/coaching-sessions/tiptap-editor.tsx +++ b/src/components/ui/coaching-sessions/tiptap-editor.tsx @@ -251,4 +251,6 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( } ); +TipTapEditor.displayName = "TipTapEditor"; + export { TipTapEditor }; From 8a65343ae88d992c3ce90a9407720f879de74590 Mon Sep 17 00:00:00 2001 From: Jim Hodapp <james.hodapp@gmail.com> Date: Fri, 18 Oct 2024 10:37:56 -0500 Subject: [PATCH 09/14] Add a styled language selector to set code block syntax highlighting for chosen language. Also enable use of tailwindcss tags and classes in scss files. --- package-lock.json | 28 ++++++++-- package.json | 1 + postcss.config.js | 2 + .../ui/coaching-sessions/code-block.tsx | 25 ++++----- .../programming-language-selector.tsx | 52 +++++++++++++++++++ .../ui/coaching-sessions/tiptap-editor.tsx | 7 +-- src/styles/code-block.scss | 38 +++++++++----- src/styles/styles.scss | 48 +++++------------ 8 files changed, 130 insertions(+), 71 deletions(-) create mode 100644 src/components/ui/coaching-sessions/programming-language-selector.tsx diff --git a/package-lock.json b/package-lock.json index 3835d36..39ded47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,6 +64,7 @@ "eslint": "^8", "eslint-config-next": "14.1.0", "postcss": "^8", + "postcss-import": "^16.1.0", "tailwindcss": "^3.3.0", "typescript": "^5" } @@ -5840,16 +5841,18 @@ } }, "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.0.tgz", + "integrity": "sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==", + "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "postcss": "^8.0.0" @@ -7009,6 +7012,23 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tailwindcss/node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", diff --git a/package.json b/package.json index 3e25f93..824da6b 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "eslint": "^8", "eslint-config-next": "14.1.0", "postcss": "^8", + "postcss-import": "^16.1.0", "tailwindcss": "^3.3.0", "typescript": "^5" } diff --git a/postcss.config.js b/postcss.config.js index 12a703d..32bebda 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,6 +1,8 @@ module.exports = { plugins: { + 'postcss-import': {}, tailwindcss: {}, + 'postcss-nested': {}, autoprefixer: {}, }, }; diff --git a/src/components/ui/coaching-sessions/code-block.tsx b/src/components/ui/coaching-sessions/code-block.tsx index 6b198f2..8e532b6 100644 --- a/src/components/ui/coaching-sessions/code-block.tsx +++ b/src/components/ui/coaching-sessions/code-block.tsx @@ -1,5 +1,6 @@ import React from "react"; import { NodeViewProps, NodeViewWrapper, NodeViewContent } from "@tiptap/react"; +import { ProgrammingLanguageSelector } from "./programming-language-selector"; import "@/styles/code-block.scss"; @@ -12,23 +13,15 @@ const CodeBlock: React.FC<NodeViewProps> = ({ return ( <NodeViewWrapper className="code-block"> - <select - contentEditable={false} - defaultValue={language} - onChange={(event: React.ChangeEvent<HTMLSelectElement>) => - updateAttributes({ language: event.target.value }) - } - > - <option value="auto">auto</option> - <option disabled>—</option> - {extension.options.lowlight + <ProgrammingLanguageSelector + languages={extension.options.lowlight .listLanguages() - .map((lang: string, index: number) => ( - <option key={index} value={lang}> - {lang} - </option> - ))} - </select> + .map((lang: string, index: number) => lang)} + placeholder={language} + onSelect={(languageTitle: string) => { + updateAttributes({ language: languageTitle }); + }} + ></ProgrammingLanguageSelector> <pre> <NodeViewContent as="code" /> </pre> diff --git a/src/components/ui/coaching-sessions/programming-language-selector.tsx b/src/components/ui/coaching-sessions/programming-language-selector.tsx new file mode 100644 index 0000000..763ba29 --- /dev/null +++ b/src/components/ui/coaching-sessions/programming-language-selector.tsx @@ -0,0 +1,52 @@ +"use client"; + +import * as React from "react"; +import { PopoverProps } from "@radix-ui/react-popover"; + +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +import "@/styles/code-block.scss"; + +interface ProgrammingLanguageSelectorProps extends PopoverProps { + languages: string[]; + placeholder: string; + onSelect: (language: string) => void; +} + +export function ProgrammingLanguageSelector({ + languages, + placeholder, + onSelect, + ...props +}: ProgrammingLanguageSelectorProps) { + const [selectedLanguage, setSelectedLanguage] = + React.useState<string>(placeholder); + + return ( + <Select + onValueChange={(language) => { + setSelectedLanguage(language); + onSelect(language); + }} + > + <SelectTrigger> + <SelectValue placeholder={selectedLanguage}> + {selectedLanguage} + </SelectValue> + </SelectTrigger> + <SelectContent> + {languages.map((language) => ( + <SelectItem value={language}>{language}</SelectItem> + ))} + </SelectContent> + </Select> + ); +} diff --git a/src/components/ui/coaching-sessions/tiptap-editor.tsx b/src/components/ui/coaching-sessions/tiptap-editor.tsx index 4ac881c..29895ed 100644 --- a/src/components/ui/coaching-sessions/tiptap-editor.tsx +++ b/src/components/ui/coaching-sessions/tiptap-editor.tsx @@ -23,7 +23,7 @@ import { Braces, } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { forwardRef, useImperativeHandle } from "react"; +import { forwardRef, useCallback, useEffect, useImperativeHandle } from "react"; import { EditorRef } from "./coaching-notes"; @@ -75,10 +75,7 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( editorProps: { attributes: { class: - // Make this responsive to light/dark mode - // Also is the background what's preventing the codeblock background color from working? - // "tiptap ProseMirror", - "tiptap ProseMirror shadow appearance-none lg:min-h-[440px] sm:min-h-[200px] md:min-h-[350px] rounded w-full py-2 px-3 bg-inherit text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline", + "tiptap ProseMirror shadow appearance-none lg:min-h-[440px] sm:min-h-[200px] md:min-h-[350px] rounded w-full py-2 px-3 bg-inherit text-black dark:text-white text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline", }, }, diff --git a/src/styles/code-block.scss b/src/styles/code-block.scss index a63ac30..b54c566 100644 --- a/src/styles/code-block.scss +++ b/src/styles/code-block.scss @@ -1,14 +1,28 @@ +@import "tailwindcss/base"; +@import "tailwindcss/components"; +@import "tailwindcss/utilities"; + .tiptap { - .code-block { - position: relative; - - select { - position: absolute; - background-color: white; - border: round($number: 5); - //background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="Black" d="M7 10l5 5 5-5z"/></svg>'); - right: 0.5rem; - top: 0.5rem; - } + .code-block { + position: relative; + + button { + // Only show on code-block hover + @apply invisible; + position: absolute; + @apply text-black dark:text-white; + @apply bg-white dark:bg-black; + height: 1.3rem; + width: 8rem; + right: 0.5rem; + top: 0.5rem; + } + + &:hover { + button { + // Show the button on code-block hover + @apply visible; } - } \ No newline at end of file + } + } +} diff --git a/src/styles/styles.scss b/src/styles/styles.scss index 3be1ad2..02601e4 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -1,3 +1,9 @@ +@import "tailwindcss/base"; +@import "tailwindcss/components"; +@import "tailwindcss/utilities"; + +// Your custom SCSS styles here + /* Basic TipTap editor styles */ .tiptap { :first-child { @@ -32,7 +38,6 @@ h5, h6 { line-height: 1.1; - //margin-top: 2.5rem; text-wrap: pretty; } @@ -51,7 +56,7 @@ } h3 { - font-size: 1.0rem; + font-size: 1rem; } h4, @@ -61,11 +66,11 @@ } pre { - background: black; + background: rgb(46, 43, 41); border-radius: 0.5rem; color: white; font-family: "JetBrainsMono", monospace; - margin: 1.5rem 0; + margin: 1.0rem 0; padding: 0.75rem 1rem; code { @@ -129,36 +134,11 @@ } } - /* Code and preformatted text styles */ - // code { - // background-color: var(--purple-light); - // border-radius: 0.4rem; - // color: var(--black); - // font-size: 0.85rem; - // padding: 0.25em 0.3em; - // } - - // pre { - // background: var(--black); - // border-radius: 0.5rem; - // color: var(--white); - // font-family: 'JetBrainsMono', monospace; - // margin: 1.5rem 0; - // padding: 0.75rem 1rem; - - // code { - // background: none; - // color: inherit; - // font-size: 0.8rem; - // padding: 0; - // } - // } - - // blockquote { - // border-left: 3px solid var(--gray-3); - // margin: 1.5rem 0; - // padding-left: 1rem; - // } + blockquote { + border-left: 3px solid var(--gray-3); + margin: 1.5rem 0; + padding-left: 1rem; + } hr { border: none; From 03744a0622ecdb53b0e30be81af6e79fca4cb9cf Mon Sep 17 00:00:00 2001 From: Jim Hodapp <james.hodapp@gmail.com> Date: Fri, 18 Oct 2024 11:08:47 -0500 Subject: [PATCH 10/14] Address warnings, no longer user TipTap StarterKit extension preferring to explicitly list all extensions used. --- .../programming-language-selector.tsx | 6 ++-- .../ui/coaching-sessions/tiptap-editor.tsx | 36 ++++++++++++------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/components/ui/coaching-sessions/programming-language-selector.tsx b/src/components/ui/coaching-sessions/programming-language-selector.tsx index 763ba29..52bed72 100644 --- a/src/components/ui/coaching-sessions/programming-language-selector.tsx +++ b/src/components/ui/coaching-sessions/programming-language-selector.tsx @@ -43,8 +43,10 @@ export function ProgrammingLanguageSelector({ </SelectValue> </SelectTrigger> <SelectContent> - {languages.map((language) => ( - <SelectItem value={language}>{language}</SelectItem> + {languages.map((language, index) => ( + <SelectItem key={index.toString()} value={language}> + {language} + </SelectItem> ))} </SelectContent> </Select> diff --git a/src/components/ui/coaching-sessions/tiptap-editor.tsx b/src/components/ui/coaching-sessions/tiptap-editor.tsx index 29895ed..700a785 100644 --- a/src/components/ui/coaching-sessions/tiptap-editor.tsx +++ b/src/components/ui/coaching-sessions/tiptap-editor.tsx @@ -2,20 +2,26 @@ import { useEditor, EditorContent, ReactNodeViewRenderer } from "@tiptap/react"; -import StarterKit from "@tiptap/starter-kit"; +import Bold from "@tiptap/extension-bold"; import BulletList from "@tiptap/extension-bullet-list"; +import Document from "@tiptap/extension-document"; +import Heading from "@tiptap/extension-heading"; import Highlight from "@tiptap/extension-highlight"; +import Italic from "@tiptap/extension-italic"; import ListItem from "@tiptap/extension-list-item"; import OrderedList from "@tiptap/extension-ordered-list"; +import Paragraph from "@tiptap/extension-paragraph"; +import Strike from "@tiptap/extension-strike"; +import Text from "@tiptap/extension-text"; import Underline from "@tiptap/extension-underline"; import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; import { - Bold, + Bold as BoldIcon, Heading1, Heading2, Heading3, Highlighter, - Italic, + Italic as ItalicIcon, Underline as UnderlineIcon, List, ListOrdered, @@ -23,7 +29,7 @@ import { Braces, } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { forwardRef, useCallback, useEffect, useImperativeHandle } from "react"; +import { forwardRef, useImperativeHandle } from "react"; import { EditorRef } from "./coaching-notes"; @@ -56,17 +62,23 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( const editor = useEditor( { extensions: [ - StarterKit, - ListItem, - Underline, - Highlight, BulletList, - OrderedList, CodeBlockLowlight.extend({ addNodeView() { return ReactNodeViewRenderer(CodeBlock); }, }).configure({ lowlight }), + Bold, + Document, + Heading, + Highlight, + Italic, + ListItem, + OrderedList, + Paragraph, + Strike, + Text, + Underline, ], autofocus: false, @@ -114,7 +126,7 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( }`} title="Bold (Ctrl+B)" > - <Bold className="h-4 w-4" /> + <BoldIcon className="h-4 w-4" /> </Button> {/* Italic Button */} @@ -126,7 +138,7 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( }`} title="Italic (Ctrl+I)" > - <Italic className="h-4 w-4" /> + <ItalicIcon className="h-4 w-4" /> </Button> {/* Underline Button */} @@ -136,7 +148,7 @@ const TipTapEditor = forwardRef<EditorRef, TipTapProps>( className={`p-2 rounded ${ editor.isActive("underline") ? "bg-gray-200" : "" }`} - title="Italic (Ctrl+I)" + title="Underline (Ctrl+U)" > <UnderlineIcon className="h-4 w-4" /> </Button> From 0063554adbabbcbab2a0b4b3dbed6c7f92a7d346 Mon Sep 17 00:00:00 2001 From: Jim Hodapp <james.hodapp@gmail.com> Date: Fri, 18 Oct 2024 11:15:32 -0500 Subject: [PATCH 11/14] Hide unnecessary tabs for MVP feature set. --- src/app/coaching-sessions/[id]/page.tsx | 4 +++- .../ui/coaching-sessions/overarching-goal-container.tsx | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/coaching-sessions/[id]/page.tsx b/src/app/coaching-sessions/[id]/page.tsx index e997151..85b6c8d 100644 --- a/src/app/coaching-sessions/[id]/page.tsx +++ b/src/app/coaching-sessions/[id]/page.tsx @@ -180,7 +180,9 @@ export default function CoachingSessionsPage() { <Tabs defaultValue="notes"> <TabsList className="flex w-128 grid-cols-2 justify-start"> <TabsTrigger value="notes">Notes</TabsTrigger> - <TabsTrigger value="console">Console</TabsTrigger> + <TabsTrigger value="console" className="hidden"> + Console + </TabsTrigger> <TabsTrigger value="coachs_notes" className="hidden"> <div className="flex gap-2 items-start"> <LockClosedIcon className="mt-1" /> diff --git a/src/components/ui/coaching-sessions/overarching-goal-container.tsx b/src/components/ui/coaching-sessions/overarching-goal-container.tsx index 767456f..4d6f712 100644 --- a/src/components/ui/coaching-sessions/overarching-goal-container.tsx +++ b/src/components/ui/coaching-sessions/overarching-goal-container.tsx @@ -215,10 +215,12 @@ const OverarchingGoalContainer: React.FC<{ <div className="grid flex-1 items-start gap-4 sm:py-0 md:gap-8"> <Tabs defaultValue="agreements"> <div className="flex items-center"> - <TabsList className="grid grid-cols-3"> + <TabsList className="grid grid-cols-2"> <TabsTrigger value="agreements">Agreements</TabsTrigger> <TabsTrigger value="actions">Actions</TabsTrigger> - <TabsTrigger value="program">Program</TabsTrigger> + <TabsTrigger value="program" className="hidden"> + Program + </TabsTrigger> </TabsList> </div> <TabsContent value="agreements"> From 0c05c897d4f9b550b21a400784645e308167a6b6 Mon Sep 17 00:00:00 2001 From: Jim Hodapp <james.hodapp@gmail.com> Date: Fri, 18 Oct 2024 11:22:35 -0500 Subject: [PATCH 12/14] Remove unused imports and fix other warning. --- .../ui/coaching-sessions/coaching-session-title.tsx | 9 +-------- src/types/general.ts | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/components/ui/coaching-sessions/coaching-session-title.tsx b/src/components/ui/coaching-sessions/coaching-session-title.tsx index c88f11b..d6b3e4a 100644 --- a/src/components/ui/coaching-sessions/coaching-session-title.tsx +++ b/src/components/ui/coaching-sessions/coaching-session-title.tsx @@ -7,14 +7,7 @@ import { SessionTitle, SessionTitleStyle, } from "@/types/session-title"; -import { - CoachingRelationshipWithUserNames, - coachingRelationshipWithUserNamesToString, -} from "@/types/coaching_relationship_with_user_names"; -import { - CoachingSession, - coachingSessionToString, -} from "@/types/coaching-session"; +import { CoachingRelationshipWithUserNames } from "@/types/coaching_relationship_with_user_names"; import { useAppStateStore } from "@/lib/providers/app-state-store-provider"; const CoachingSessionTitle: React.FC<{ diff --git a/src/types/general.ts b/src/types/general.ts index 96ed079..85b93f1 100644 --- a/src/types/general.ts +++ b/src/types/general.ts @@ -47,12 +47,5 @@ export function actionStatusToString(actionStatus: ItemStatus): string { /// return a valid DateTime object instance. export function getDateTimeFromString(dateTime: string): DateTime { const dt = dateTime.trim(); - if (dt.length == 0) { - console.warn( - "Return DateTime.now() since input dateTime string was empty." - ); - return DateTime.now(); - } - - return DateTime.fromISO(dt); + return dt.trim().length > 0 ? DateTime.fromISO(dt) : DateTime.now(); } From 7762efcd4aef00419aa7c11b1dc2c29789d40f06 Mon Sep 17 00:00:00 2001 From: Jim Hodapp <james.hodapp@gmail.com> Date: Fri, 18 Oct 2024 11:26:41 -0500 Subject: [PATCH 13/14] Remove another warning. --- src/components/ui/user-nav.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ui/user-nav.tsx b/src/components/ui/user-nav.tsx index bf42dfa..1998df3 100644 --- a/src/components/ui/user-nav.tsx +++ b/src/components/ui/user-nav.tsx @@ -50,7 +50,7 @@ export function UserNav() { <DropdownMenuTrigger asChild> <Button variant="ghost" className="relative mx-2 h-8 w-8 rounded-full"> <Avatar className="h-9 w-9"> - <AvatarImage src="/avatars/03.png" alt="@jhodapp" /> + {/* <AvatarImage src="/avatars/03.png" alt="@jhodapp" /> */} <AvatarFallback> {userFirstLastLettersToString( userSession.first_name, From bbcff41559ed4cc48edad3801035cab81d8b2a2c Mon Sep 17 00:00:00 2001 From: Jim Hodapp <james.hodapp@gmail.com> Date: Fri, 18 Oct 2024 12:45:16 -0500 Subject: [PATCH 14/14] Update src/lib/api/notes.ts Remove commented out line. --- src/lib/api/notes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/api/notes.ts b/src/lib/api/notes.ts index cd85076..9f1eeb7 100644 --- a/src/lib/api/notes.ts +++ b/src/lib/api/notes.ts @@ -81,7 +81,6 @@ export const createNote = async ( var createdNote: Note = defaultNote(); var err: string = ""; - //var strNote: string = noteToString(note); const data = await axios .post(`http://localhost:4000/notes`, newNoteJson, { withCredentials: true,