diff --git a/package-lock.json b/package-lock.json index 12b47b6..c8ad743 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,9 +13,16 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", + "@xeger/quill-image-actions": "^0.7.2", + "@xeger/quill-image-formats": "^0.7.2", "framer-motion": "^11.0.8", "next": "14.1.3", "quill": "^1.3.7", + "quill-image-drop-module": "^1.0.3", + "quill-image-edit-module": "^0.1.6", + "quill-image-resize-module": "^3.0.0", + "quill-image-resize-module-react": "^3.0.0", + "quill-resize-module": "^1.2.4", "react": "^18", "react-dom": "^18", "react-icons": "^5.0.1", @@ -1678,6 +1685,29 @@ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", "devOptional": true }, + "node_modules/@xeger/quill-image-actions": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@xeger/quill-image-actions/-/quill-image-actions-0.7.2.tgz", + "integrity": "sha512-OhaZnYCie9J1ZLx2+Nsznj+qkdHn9Yx7q3iOCCKFs6kAzcZUVBPv2fMjK+nzURySLyCVQOP3Ur2mZx1yYqgppw==", + "dependencies": { + "core-js": "^3.0", + "deepmerge": "^4.2" + }, + "peerDependencies": { + "quill": "^1.3.6" + } + }, + "node_modules/@xeger/quill-image-formats": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@xeger/quill-image-formats/-/quill-image-formats-0.7.2.tgz", + "integrity": "sha512-L9beiJKWzjHxBJEZB21I8YjFgOHYROsrfFMvIqP+kjc/u/MecHY1mXzR9VySLr+Qvj55t+vkKKuJ0ZPKvQSaPA==", + "dependencies": { + "core-js": "^3.0" + }, + "peerDependencies": { + "quill": "^1.3.6" + } + }, "node_modules/@zag-js/dom-query": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/@zag-js/dom-query/-/dom-query-0.16.0.tgz", @@ -1858,6 +1888,16 @@ "toggle-selection": "^1.0.6" } }, + "node_modules/core-js": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.0.tgz", + "integrity": "sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -1905,6 +1945,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -2520,6 +2568,55 @@ "node": ">=0.10" } }, + "node_modules/quill-image-drop-module": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/quill-image-drop-module/-/quill-image-drop-module-1.0.3.tgz", + "integrity": "sha512-HP0Y2kb3nQk1QbRKZzEe1j3mArRQerN5B/U/MlXrOnxmhy3m/xYmVv0YoE13vWnGnBOIcoXGJ/9fi7l6AwsP8Q==", + "dependencies": { + "quill": "^1.2.2" + } + }, + "node_modules/quill-image-edit-module": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/quill-image-edit-module/-/quill-image-edit-module-0.1.6.tgz", + "integrity": "sha512-yB5YiXqX8Scxxk9AaqKRAekqUB/WHFqrFHWDq6E1+Q/KjIFAmYTB8az1XuXiz8M7M/LL5689p5ZdqyYppS8R/Q==", + "dependencies": { + "deepmerge": "^4.2.2" + } + }, + "node_modules/quill-image-resize-module": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quill-image-resize-module/-/quill-image-resize-module-3.0.0.tgz", + "integrity": "sha512-1TZBnUxU/WIx5dPyVjQ9yN7C6mLZSp04HyWBEMqT320DIq4MW4JgzlOPDZX5ZpBM3bU6sacU4kTLUc8VgYQZYw==", + "dependencies": { + "lodash": "^4.17.4", + "quill": "^1.2.2", + "raw-loader": "^0.5.1" + } + }, + "node_modules/quill-image-resize-module-react": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quill-image-resize-module-react/-/quill-image-resize-module-react-3.0.0.tgz", + "integrity": "sha512-3jVChLoXh+fwEELx3OswOEEuF+1KU3r/B9RAqZ//s+d+UMduVZzUepU1g/XoxjKoBJvWD2lJwBIFBRUNb8ebCw==", + "dependencies": { + "lodash": "^4.17.4", + "quill": "^1.2.2", + "raw-loader": "^0.5.1" + } + }, + "node_modules/quill-resize-module": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/quill-resize-module/-/quill-resize-module-1.2.4.tgz", + "integrity": "sha512-toBGslzfzxXlokN7d7naL6iq2Da5mDS4zzvyES2YpqwBqO/yrxHR1rylz0D1zLHAZcF+aeDWBNwktrYQMN5vGw==", + "dependencies": { + "extend": "^3.0.2" + } + }, + "node_modules/raw-loader": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", + "integrity": "sha512-sf7oGoLuaYAScB4VGr0tzetsYlS8EJH6qnTCfQ/WVEa89hALQ4RQfCKt5xCyPQKPDUbVUAIP1QsxAwfAjlDp7Q==" + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", diff --git a/package.json b/package.json index 32506ff..ea48d87 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,16 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", + "@xeger/quill-image-actions": "^0.7.2", + "@xeger/quill-image-formats": "^0.7.2", "framer-motion": "^11.0.8", "next": "14.1.3", "quill": "^1.3.7", + "quill-image-drop-module": "^1.0.3", + "quill-image-edit-module": "^0.1.6", + "quill-image-resize-module": "^3.0.0", + "quill-image-resize-module-react": "^3.0.0", + "quill-resize-module": "^1.2.4", "react": "^18", "react-dom": "^18", "react-icons": "^5.0.1", diff --git a/src/app/globals.css b/src/app/globals.css index 2f5ffa4..23d7540 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,118 +1,158 @@ :root { - --max-width: 1100px; - --border-radius: 12px; - --font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", + --max-width: 1100px; + --border-radius: 12px; + --font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace; - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; - --primary-glow: conic-gradient( - from 180deg at 50% 50%, - #16abff33 0deg, - #0885ff33 55deg, - #54d6ff33 120deg, - #0071ff33 160deg, - transparent 360deg - ); - --secondary-glow: radial-gradient( - rgba(255, 255, 255, 1), - rgba(255, 255, 255, 0) - ); + --primary-glow: conic-gradient( + from 180deg at 50% 50%, + #16abff33 0deg, + #0885ff33 55deg, + #54d6ff33 120deg, + #0071ff33 160deg, + transparent 360deg + ); + --secondary-glow: radial-gradient( + rgba(255, 255, 255, 1), + rgba(255, 255, 255, 0) + ); - --tile-start-rgb: 239, 245, 249; - --tile-end-rgb: 228, 232, 233; - --tile-border: conic-gradient( - #00000080, - #00000040, - #00000030, - #00000020, - #00000010, - #00000010, - #00000080 - ); + --tile-start-rgb: 239, 245, 249; + --tile-end-rgb: 228, 232, 233; + --tile-border: conic-gradient( + #00000080, + #00000040, + #00000030, + #00000020, + #00000010, + #00000010, + #00000080 + ); - --callout-rgb: 238, 240, 241; - --callout-border-rgb: 172, 175, 176; - --card-rgb: 180, 185, 188; - --card-border-rgb: 131, 134, 135; + --callout-rgb: 238, 240, 241; + --callout-border-rgb: 172, 175, 176; + --card-rgb: 180, 185, 188; + --card-border-rgb: 131, 134, 135; } @media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; - --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); - --secondary-glow: linear-gradient( - to bottom right, - rgba(1, 65, 255, 0), - rgba(1, 65, 255, 0), - rgba(1, 65, 255, 0.3) - ); + --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); + --secondary-glow: linear-gradient( + to bottom right, + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0.3) + ); - --tile-start-rgb: 2, 13, 46; - --tile-end-rgb: 2, 5, 19; - --tile-border: conic-gradient( - #ffffff80, - #ffffff40, - #ffffff30, - #ffffff20, - #ffffff10, - #ffffff10, - #ffffff80 - ); + --tile-start-rgb: 2, 13, 46; + --tile-end-rgb: 2, 5, 19; + --tile-border: conic-gradient( + #ffffff80, + #ffffff40, + #ffffff30, + #ffffff20, + #ffffff10, + #ffffff10, + #ffffff80 + ); - --callout-rgb: 20, 20, 20; - --callout-border-rgb: 108, 108, 108; - --card-rgb: 100, 100, 100; - --card-border-rgb: 200, 200, 200; - } + --callout-rgb: 20, 20, 20; + --callout-border-rgb: 108, 108, 108; + --card-rgb: 100, 100, 100; + --card-border-rgb: 200, 200, 200; + } } * { - box-sizing: border-box; - padding: 0; - margin: 0; + box-sizing: border-box; + padding: 0; + margin: 0; } html, body { - max-width: 100vw; - overflow-x: hidden; + max-width: 100vw; + overflow-x: hidden; } body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); + color: rgb(var(--foreground-rgb)); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) rgb(var(--background-start-rgb)); } a { - color: inherit; - text-decoration: none; + color: inherit; + text-decoration: none; } .ql-editor::before { - left: 0!important; - right: 0!important; - font-size: 16px; + left: 0 !important; + right: 0 !important; + font-size: 16px; } .ql-editor { - padding: 12px 0!important; - font-size: 16px; + padding: 12px 0 !important; + font-size: 16px; +} + +.quill { + container-type: inline-size; +} + +.quill .ql-editor::-webkit-scrollbar { + width: var(--chakra-sizes-2); +} + +.quill .ql-editor::-webkit-scrollbar-track { + background: var(--chakra-colors-gray-50) +} + +.quill .ql-editor::-webkit-scrollbar-thumb { + background: var(--chakra-colors-gray-400); + border-radius: var(--chakra-sizes-4) +} + +.quill .ql-toolbar { + border-radius: var(--border-radius); +} + +.quill .ql-container { + margin-top: .5rem; + height: calc(100% - 43px - .5rem); + border: none !important; +} + +.quill .ql-container img { + max-width: 1100px; +} + +.quill .ql-container p { + margin-bottom: 1rem; +} + +@container (width < 600px) { + .ql-container { + height: calc(100% - 68px) !important; + } } @media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } + html { + color-scheme: dark; + } } diff --git a/src/chakra-pages/Notes.tsx b/src/chakra-pages/Notes.tsx index 8b89ce1..e9f0907 100644 --- a/src/chakra-pages/Notes.tsx +++ b/src/chakra-pages/Notes.tsx @@ -7,28 +7,50 @@ import { Divider, Flex, Heading, - HStack, + HStack, Icon, IconButton, Input, - Link, + Link, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Text, VStack, Wrap } from "@chakra-ui/react"; import {Logo} from "@/components/shared/Logo"; -import {BsPlus} from "react-icons/bs"; +import {BsCopy, BsGlobe, BsPlus, BsThreeDotsVertical, BsTrash} from "react-icons/bs"; import 'react-quill/dist/quill.snow.css'; import dynamic from "next/dynamic"; -import {useState} from "react"; +import {useEffect, useState} from "react"; +import Quill from "quill"; +import {ImageDrop} from 'quill-image-drop-module'; const QuillEditor = dynamic(() => import('react-quill'), {ssr: false}); +Quill.register('modules/imageDrop', ImageDrop); +let titleTimeout: NodeJS.Timeout | null = null; +let contentTimeout: NodeJS.Timeout | null = null; export default function NotesChakraPage() { + const [title, setTitle] = useState('') const [content, setContent] = useState(''); + useEffect(() => { + if (typeof window !== "undefined" && window.localStorage) { + setTitle(window.localStorage.getItem("qnote:current-note-title") || '') + setContent(window.localStorage.getItem("qnote:current-note") || ''); + } + + return () => { + if (contentTimeout !== null) { + clearTimeout(contentTimeout); + } + if (titleTimeout !== null) { + clearTimeout(titleTimeout); + } + } + }, []); const quillModules = { + imageDrop: true, toolbar: [ [{header: [1, 2, 3, false]}], ['bold', 'italic', 'underline', 'strike', 'blockquote'], @@ -41,7 +63,6 @@ export default function NotesChakraPage() { ], }; - const quillFormats = [ 'header', 'bold', @@ -58,9 +79,30 @@ export default function NotesChakraPage() { 'code-block', ]; + const handleTitleChange = (newTitle: string) => { + setTitle(newTitle); - const handleEditorChange = (newContent) => { + if (titleTimeout !== null) { + clearTimeout(titleTimeout); + } + titleTimeout = setTimeout(() => { + if (typeof window !== 'undefined' && window.localStorage) { + window.localStorage.setItem("qnote:current-note-title", newTitle); + } + }, 750) + } + + const handleEditorChange = (newContent: string) => { setContent(newContent); + + if (contentTimeout !== null) { + clearTimeout(contentTimeout); + } + contentTimeout = setTimeout(() => { + if (typeof window !== 'undefined' && window.localStorage) { + window.localStorage.setItem("qnote:current-note", newContent); + } + }, 750) }; return ( @@ -71,16 +113,17 @@ export default function NotesChakraPage() { display="flex" flexDirection="column" height="100%" - overflowY="hidden" + overflow="hidden" > - + @@ -88,7 +131,7 @@ export default function NotesChakraPage() { @@ -124,12 +167,12 @@ export default function NotesChakraPage() { }}> {Array.from(Array(50).keys()).map(i => ( - - + + Note name ${i + 1} Note text preview... - + ))} @@ -139,18 +182,17 @@ export default function NotesChakraPage() { + handleTitleChange(e.target.value)} mb={3} /> - - - + + } variant="ghost"/> + + }>Publish + }>Duplicate + + } color="red">Delete + + + + + + +