diff --git a/package-lock.json b/package-lock.json index 837429f..3d5d7f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,15 @@ "name": "noro-admin", "version": "0.1.0", "dependencies": { + "@tiptap/extension-character-count": "^3.27.3", + "@tiptap/extension-image": "^3.27.3", + "@tiptap/extension-link": "^3.27.3", + "@tiptap/extension-placeholder": "^3.27.3", + "@tiptap/extension-text-align": "^3.27.3", + "@tiptap/extension-underline": "^3.27.3", + "@tiptap/pm": "^3.27.3", + "@tiptap/starter-kit": "^3.27.3", + "@tiptap/vue-3": "^3.27.3", "bcryptjs": "^2.4.3", "cors": "^2.8.5", "dotenv": "^16.4.5", @@ -45,7 +54,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -55,7 +63,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -65,7 +72,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.7" @@ -91,7 +97,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -492,6 +497,31 @@ "node": ">=12" } }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -517,7 +547,6 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -919,6 +948,480 @@ "win32" ] }, + "node_modules/@tiptap/core": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.27.3.tgz", + "integrity": "sha512-TJj5929M96C1KlH796wS8MywfHDh49RhmakOyzyMMc9pFmRj9UXi1gj0TCXgsZtjEOG7B+m/DRvNOvnuvR9kmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "3.27.3" + } + }, + "node_modules/@tiptap/extension-blockquote": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.27.3.tgz", + "integrity": "sha512-VGoVMcqcGkkoduzEqQ70ZK5OOHBDn5iYeHJSgkvNoSHzYHl0CaKoWxoDKJjSWC5DAM4mBU+tXTlsiUp3evRMfA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.27.3.tgz", + "integrity": "sha512-pchbycFppBqBmvk+OxrQLIPZLNd3rNbcm7zOa+OTjluphpAcsJ6AmHBmiRhJUYWitV6/SA+0nujcaWxBp5hNcQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.27.3.tgz", + "integrity": "sha512-PKSM9g8BXzO0Lxm2gOin7FAFvxO6T0QvlXTXVke/GJn/sR1tEz7w5ybsvweytUPmSliUc601pfIUOHKEhIVC2Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" + } + }, + "node_modules/@tiptap/extension-bullet-list": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.27.3.tgz", + "integrity": "sha512-owQW6mcgnYgQU1z5BqmslcjZFBUm1SGM/Ax++4R6XelIt/7ol6uSTjzjAzum+ASW7UlBbuJHuSgRjl8cA+RGkg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.27.3" + } + }, + "node_modules/@tiptap/extension-character-count": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-3.27.3.tgz", + "integrity": "sha512-dtgj4ume0d+4Zkd8osj9mGLNhZpB5BxketX0JkIdi7M5dzF6aAdEzrIl1wYbccUplVrzNsqxVR4Hs7bOjiJ4tA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "3.27.3" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.27.3.tgz", + "integrity": "sha512-T7JF10Y3k7fyVujXXLVubdUh7fctgBUoiuxbvjotpltg1U/mPbOcT0UW/uif9j3H8TFvAwNApVw3x8bwwFUakA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.27.3.tgz", + "integrity": "sha512-3CVnzkpGoqqI5KaXI9l9PMwLkx34SYY63tpeo0I0QjDk4LU+JTAQDTPJFSwB4zTsHZ7ZFMU0jjasFOZ1kZRVgw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.27.3.tgz", + "integrity": "sha512-PR25MRv56Nm4ETVLwA8nAh/0RrVdvD4D2nxnAC8kUkxz1WOYDaqTfPqqEeSWDoUUTUp1I1xzDkD4x0gEnAlmZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extension-dropcursor": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.27.3.tgz", + "integrity": "sha512-XsL0Ziual+ynXyx5JgLb0KIOShlMN3MW7+GQvf9PGRb1vOB7IOTRFDxMPrxDWcFIZ6WWMWSrbkCLOKHStt1BWg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "3.27.3" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.27.3.tgz", + "integrity": "sha512-qL+g1Z6MqZvYG4mHTPwub9II2fWZY+2I5VFfV8fzqy1HchBOyY6Df0mRKDNb0bmjBdFabIXnswz3iqLx3mDTbw==", + "license": "MIT", + "optional": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.0.0", + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" + } + }, + "node_modules/@tiptap/extension-gapcursor": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.27.3.tgz", + "integrity": "sha512-MVBqzwYrDOttkn2GOrvvKTDCymfSS2VGSKpahcKKhge5mweSDiqko2X1vBDnMF8aVW3WxIFfDMmO4VMmh8HDZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "3.27.3" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.27.3.tgz", + "integrity": "sha512-cySObJITR2CRrWII87mI2LJ12wEETTnxt2p1vvC6ZtUmKnB50fOu+ux8ZEt6KYNocIOtrEZthNFzTOx7R7UtrQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.27.3.tgz", + "integrity": "sha512-QHXnsNic6iId8pnsFZ8z4PkX5L+HCHa/D7rAi3nNWtPlSIAOxo4nKrALcB5/tHmY+XL8kEXKH3nsNLNEDLCYPg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.27.3.tgz", + "integrity": "sha512-sNCuo0+Q001B5XTVWY+ULpWXQqtBNRmAqXxAexZl44bx3rDqHG8OzjwT4EM0LIj2+BYVbdqwaJ1vfQbZVEEbig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" + } + }, + "node_modules/@tiptap/extension-image": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-3.27.3.tgz", + "integrity": "sha512-3OVLRH54Xkh9yrI5oGIempuqZkhLG1plyJBRnapx0N8j9IjEImOLF78kPYoAZzaBuU78M3Zk/E7HQZgmnaNRBg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.27.3.tgz", + "integrity": "sha512-7VpVi2vn8kGFAc/HLrt96J/E6+LXOGKn8xLhJS863t150yLIG3EPQLA1h+G5O6eo+/w+9YokvJAAJoeDihj1Jg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extension-link": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.27.3.tgz", + "integrity": "sha512-iXmu1tJ/3vP60c9d+zfO3+X64vod2EyBrM1qeufecAtWA6t0bT9tYeka0Zq6qDRGsZK5zViO9F2lsY7eHXiYuA==", + "license": "MIT", + "dependencies": { + "linkifyjs": "^4.3.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" + } + }, + "node_modules/@tiptap/extension-list": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.27.3.tgz", + "integrity": "sha512-52rcaSzYjtVGUrNkdB8k1Bw/rId2lBirrWMvmnBLsieoeFd8zo8ow5zkwV057BYSgWXfnMAV7GOpDyq2IoSD9A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" + } + }, + "node_modules/@tiptap/extension-list-item": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.27.3.tgz", + "integrity": "sha512-UddhVaQ7+V9IfQVVE8AJLNzSmyaaNpZPQsoPQxFNlJyOFxD6RwSTT8L4rb/TwbUctxSFWpyd1J7exzf33Ony/g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.27.3" + } + }, + "node_modules/@tiptap/extension-list-keymap": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.27.3.tgz", + "integrity": "sha512-GZPVg4QCFm41yTMPEPeTlH2odNeQwRuWHY/pt3pI5LsfipQ4kDBv6h9we928vJQUnU2Gg9Y3cL5iRbmXIWWTUA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.27.3" + } + }, + "node_modules/@tiptap/extension-ordered-list": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.27.3.tgz", + "integrity": "sha512-oHYoE65WfRSM5tCcXi8MtQmzPxz83E2qw0pa2yHBKwr9xCCQUu8vJHJE+EJGduWdaNImv43+6/6kVMeYYbXRKg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.27.3" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.27.3.tgz", + "integrity": "sha512-G5XPCN7lm0nULYPelJC2eUbKbt1q97j67e/HSWxwMYEXhceec5eNAtVfpDCRyXNO9osyBi2kXqLv/IQtxbch9A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extension-placeholder": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.27.3.tgz", + "integrity": "sha512-Y43dGaN3rA0oU4CWmXkZ2idjZjrxPX9oy0zZL92EgXOguhAUToBs0dIxRG5ZanYX9A8kKKWPlfeRFlBMtRhKvg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "3.27.3" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.27.3.tgz", + "integrity": "sha512-dp6Rs3I4zLVJPvAw2Q9yrNuYKcX5LTONH3/oyULvGsrqq5DKz7pqscfwhwnDSzrgm/3aCz6B9r1BVHlYI/DXlA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.27.3.tgz", + "integrity": "sha512-wSatZ/bop0pleeC18nF90zvWWEoRe/ewZncvm8LjcMrXwq41kTEs3j7nZflMYAh7X8fZvu00UIMeEyCNJl+5Yg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extension-text-align": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.27.3.tgz", + "integrity": "sha512-yyhlwRz0Eh6xQCn+GtTDWUGqxGajT02Qf8zBN/3eZQtl3EZ2xrI8lvfLmcV9SAW0srgMd+5hX0CVV+zKeXuxeg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extension-underline": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.27.3.tgz", + "integrity": "sha512-YH7ka/Rp7ZwlzTuQCcs0OZ4CMSx/P/MzbseQKxKebEIZreWaKMZAaED97Fnu0dA66VlreD4/1C7xHJy84ovamg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extensions": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.27.3.tgz", + "integrity": "sha512-IA2QKUVJgzUPEhhkUfr6P5u5GFVfhiApiEaaHXBz07saL2c4HNoVs2RC18QlZDCtLNKrPR1mUScr72u7uYs+YQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" + } + }, + "node_modules/@tiptap/pm": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.27.3.tgz", + "integrity": "sha512-ppiG57RxM3HSHHgcHT0hP6Ib4P56Sd5itxdV4w0hIGHKPGyLLKiAkYp+htIHa9T7IvjMdaqxIlsfyV3ZKsS1sw==", + "license": "MIT", + "dependencies": { + "prosemirror-changeset": "^2.4.1", + "prosemirror-commands": "^1.7.1", + "prosemirror-dropcursor": "^1.8.2", + "prosemirror-gapcursor": "^1.4.1", + "prosemirror-history": "^1.5.0", + "prosemirror-inputrules": "^1.5.1", + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.9", + "prosemirror-schema-list": "^1.5.1", + "prosemirror-state": "^1.4.4", + "prosemirror-tables": "^1.8.5", + "prosemirror-transform": "^1.12.0", + "prosemirror-view": "^1.41.9" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.27.3.tgz", + "integrity": "sha512-xX3baFqiC30skntdhxUUvyJo755ON9c1pE83+2Wiq2g+Qnffg9knUuLCZStHoZZ318yB0aBsKAMvHWLtYDo8AA==", + "license": "MIT", + "dependencies": { + "@tiptap/core": "^3.27.3", + "@tiptap/extension-blockquote": "^3.27.3", + "@tiptap/extension-bold": "^3.27.3", + "@tiptap/extension-bullet-list": "^3.27.3", + "@tiptap/extension-code": "^3.27.3", + "@tiptap/extension-code-block": "^3.27.3", + "@tiptap/extension-document": "^3.27.3", + "@tiptap/extension-dropcursor": "^3.27.3", + "@tiptap/extension-gapcursor": "^3.27.3", + "@tiptap/extension-hard-break": "^3.27.3", + "@tiptap/extension-heading": "^3.27.3", + "@tiptap/extension-horizontal-rule": "^3.27.3", + "@tiptap/extension-italic": "^3.27.3", + "@tiptap/extension-link": "^3.27.3", + "@tiptap/extension-list": "^3.27.3", + "@tiptap/extension-list-item": "^3.27.3", + "@tiptap/extension-list-keymap": "^3.27.3", + "@tiptap/extension-ordered-list": "^3.27.3", + "@tiptap/extension-paragraph": "^3.27.3", + "@tiptap/extension-strike": "^3.27.3", + "@tiptap/extension-text": "^3.27.3", + "@tiptap/extension-underline": "^3.27.3", + "@tiptap/extensions": "^3.27.3", + "@tiptap/pm": "^3.27.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/vue-3": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/vue-3/-/vue-3-3.27.3.tgz", + "integrity": "sha512-/VfDPXTM6R9w913oziODzHoZQMdtB0jFvJLF8LxYbO1FwDopPuX/kCWaeDyvr2CYrutbCe3PBj5FzPExOIJ3zQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "optionalDependencies": { + "@tiptap/extension-bubble-menu": "^3.27.3", + "@tiptap/extension-floating-menu": "^3.27.3" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.0.0", + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3", + "vue": "^3.0.0" + } + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -944,7 +1447,6 @@ "version": "3.5.39", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz", "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.7", @@ -958,7 +1460,6 @@ "version": "3.5.39", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-core": "3.5.39", @@ -969,7 +1470,6 @@ "version": "3.5.39", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.7", @@ -987,7 +1487,6 @@ "version": "3.5.39", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-dom": "3.5.39", @@ -1005,7 +1504,6 @@ "version": "3.5.39", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz", "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==", - "dev": true, "license": "MIT", "dependencies": { "@vue/shared": "3.5.39" @@ -1015,7 +1513,6 @@ "version": "3.5.39", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz", "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==", - "dev": true, "license": "MIT", "dependencies": { "@vue/reactivity": "3.5.39", @@ -1026,7 +1523,6 @@ "version": "3.5.39", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz", "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==", - "dev": true, "license": "MIT", "dependencies": { "@vue/reactivity": "3.5.39", @@ -1039,7 +1535,6 @@ "version": "3.5.39", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz", "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-ssr": "3.5.39", @@ -1053,7 +1548,6 @@ "version": "3.5.39", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz", "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==", - "dev": true, "license": "MIT" }, "node_modules/accepts": { @@ -1599,7 +2093,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/date-fns": { @@ -1729,7 +2222,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -1827,7 +2319,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, "license": "MIT" }, "node_modules/etag": { @@ -2322,6 +2813,12 @@ "dev": true, "license": "MIT" }, + "node_modules/linkifyjs": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.3.tgz", + "integrity": "sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==", + "license": "MIT" + }, "node_modules/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", @@ -2375,7 +2872,6 @@ "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -2536,7 +3032,6 @@ "version": "3.3.15", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", - "dev": true, "funding": [ { "type": "github", @@ -2623,6 +3118,12 @@ "node": ">= 0.8" } }, + "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/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2649,7 +3150,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -2689,7 +3189,6 @@ "version": "8.5.16", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2854,6 +3353,145 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/prosemirror-changeset": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.1.tgz", + "integrity": "sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==", + "license": "MIT", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.3.tgz", + "integrity": "sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz", + "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==", + "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.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", + "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.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.11", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.11.tgz", + "integrity": "sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==", + "license": "MIT", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "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.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", + "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.4", + "prosemirror-state": "^1.4.4", + "prosemirror-transform": "^1.10.5", + "prosemirror-view": "^1.41.4" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz", + "integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.42.1.tgz", + "integrity": "sha512-rRqzZnRgkyh69XoOMrfFJHwauHscLBmHbq772kwbic1ymQAM8gXjzEbJse5j1ep2UO2HRIAQL0bY3kZ/RoqjVw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.25.8", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -3060,6 +3698,12 @@ "fsevents": "~2.3.2" } }, + "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", @@ -3272,7 +3916,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -3574,7 +4217,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -3726,7 +4369,6 @@ "version": "3.5.39", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz", "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", - "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-dom": "3.5.39", @@ -3760,6 +4402,12 @@ "vue": "^3.5.0" } }, + "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/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index c92b8c0..162f7ef 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,16 @@ "seed": "node server/seed.js" }, "dependencies": { - "bcryptjs": "^2.4.3", + "@tiptap/extension-character-count": "^3.27.3", + "@tiptap/extension-image": "^3.27.3", + "@tiptap/extension-link": "^3.27.3", + "@tiptap/extension-placeholder": "^3.27.3", + "@tiptap/extension-text-align": "^3.27.3", + "@tiptap/extension-underline": "^3.27.3", + "@tiptap/pm": "^3.27.3", + "@tiptap/starter-kit": "^3.27.3", + "@tiptap/vue-3": "^3.27.3", + "bcryptjs": "^2.4.3", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.0", diff --git a/server/db.js b/server/db.js index 026d623..28d1377 100644 --- a/server/db.js +++ b/server/db.js @@ -16,7 +16,8 @@ const defaultDb = () => ({ clients: [], stats: [], blogPosts: [], - media: [] + media: [], + contactRequests: [] }) function ensureDataDir() { diff --git a/server/index.js b/server/index.js index d7117ba..a1fd417 100644 --- a/server/index.js +++ b/server/index.js @@ -242,6 +242,70 @@ app.put('/api/media/:id', authMiddleware, (req, res) => { res.json(updated) }) +app.post('/api/contact-requests', (req, res) => { + const { name, email, phone, category, message } = req.body || {} + + if (!name?.trim() || !email?.trim() || !category?.trim() || !message?.trim()) { + return res.status(400).json({ error: 'نام، ایمیل، نوع پروژه و توضیحات الزامی است' }) + } + + const request = { + id: randomUUID(), + name: name.trim(), + email: email.trim(), + phone: phone?.trim() || '', + category: category.trim(), + message: message.trim(), + status: 'new', + createdAt: new Date().toISOString(), + } + + updateDb((db) => { + if (!Array.isArray(db.contactRequests)) db.contactRequests = [] + db.contactRequests.unshift(request) + }) + + res.status(201).json({ ok: true, id: request.id }) +}) + +app.get('/api/contact-requests', authMiddleware, (_req, res) => { + const requests = readDb().contactRequests || [] + res.json(requests) +}) + +app.patch('/api/contact-requests/:id', authMiddleware, (req, res) => { + const { status } = req.body || {} + if (!['new', 'read', 'archived'].includes(status)) { + return res.status(400).json({ error: 'وضعیت نامعتبر است' }) + } + + let updated = null + updateDb((db) => { + if (!Array.isArray(db.contactRequests)) db.contactRequests = [] + const i = db.contactRequests.findIndex((r) => r.id === req.params.id) + if (i === -1) return + db.contactRequests[i] = { ...db.contactRequests[i], status } + updated = db.contactRequests[i] + }) + + if (!updated) return res.status(404).json({ error: 'درخواست یافت نشد' }) + res.json(updated) +}) + +app.delete('/api/contact-requests/:id', authMiddleware, (req, res) => { + let found = false + updateDb((db) => { + if (!Array.isArray(db.contactRequests)) db.contactRequests = [] + const i = db.contactRequests.findIndex((r) => r.id === req.params.id) + if (i !== -1) { + db.contactRequests.splice(i, 1) + found = true + } + }) + if (!found) return res.status(404).json({ error: 'درخواست یافت نشد' }) + res.status(204).end() +}) + app.delete('/api/media/:id', authMiddleware, (req, res) => { let removed = null updateDb((db) => { diff --git a/src/api/client.ts b/src/api/client.ts index aad1425..125fcef 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -85,6 +85,17 @@ export interface MediaItem { createdAt?: string } +export interface ContactRequest { + id: string + name: string + email: string + phone?: string + category: string + message: string + status: 'new' | 'read' | 'archived' + createdAt: string +} + export interface SiteSettings { name?: string nameEn?: string @@ -296,6 +307,21 @@ export const api = { deleteMedia(id: string) { return request(`/media/${id}`, { method: 'DELETE' }) }, + + getContactRequests() { + return request('/contact-requests') + }, + + updateContactRequest(id: string, data: Pick) { + return request(`/contact-requests/${id}`, { + method: 'PATCH', + body: JSON.stringify(data), + }) + }, + + deleteContactRequest(id: string) { + return request(`/contact-requests/${id}`, { method: 'DELETE' }) + }, } export { ApiError } diff --git a/src/components/BlogEditor.vue b/src/components/BlogEditor.vue new file mode 100644 index 0000000..fb18a32 --- /dev/null +++ b/src/components/BlogEditor.vue @@ -0,0 +1,423 @@ + + + diff --git a/src/components/BlogImageNode.vue b/src/components/BlogImageNode.vue new file mode 100644 index 0000000..f24545d --- /dev/null +++ b/src/components/BlogImageNode.vue @@ -0,0 +1,167 @@ + + + diff --git a/src/components/blogImageExtension.ts b/src/components/blogImageExtension.ts new file mode 100644 index 0000000..5451ed6 --- /dev/null +++ b/src/components/blogImageExtension.ts @@ -0,0 +1,9 @@ +import Image from '@tiptap/extension-image' +import { VueNodeViewRenderer } from '@tiptap/vue-3' +import BlogImageNode from './BlogImageNode.vue' + +export const BlogImage = Image.extend({ + addNodeView() { + return VueNodeViewRenderer(BlogImageNode) + }, +}) diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue index aff3495..09a8cfc 100644 --- a/src/layouts/AdminLayout.vue +++ b/src/layouts/AdminLayout.vue @@ -67,6 +67,7 @@ const navItems = [ { name: 'services', label: 'خدمات', icon: '💼' }, { name: 'clients', label: 'مشتریان', icon: '🤝' }, { name: 'blog', label: 'وبلاگ', icon: '📝' }, + { name: 'contact-requests', label: 'درخواست‌ها', icon: '📩' }, { name: 'media', label: 'رسانه', icon: '🖼️' }, ] @@ -80,6 +81,7 @@ const titles: Record = { clients: 'مشتریان', blog: 'وبلاگ', 'blog-edit': 'ویرایش مطلب', + 'contact-requests': 'درخواست‌های تماس', media: 'کتابخانه رسانه', } diff --git a/src/router/index.ts b/src/router/index.ts index 7aed7df..403635f 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -65,6 +65,11 @@ const router = createRouter({ name: 'media', component: () => import('@/views/MediaView.vue'), }, + { + path: 'contact-requests', + name: 'contact-requests', + component: () => import('@/views/ContactRequestsView.vue'), + }, ], }, ], diff --git a/src/style.css b/src/style.css index a76c5d1..3deef6a 100644 --- a/src/style.css +++ b/src/style.css @@ -40,4 +40,122 @@ .page-title { @apply text-2xl font-bold text-slate-900; } + + .editor-btn { + @apply inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm text-slate-600 transition hover:bg-white hover:text-slate-900 disabled:cursor-not-allowed disabled:opacity-40; + } + + .editor-btn.is-active { + @apply bg-brand/10 text-brand-700; + } + + .editor-divider { + @apply mx-1 hidden h-6 w-px bg-slate-200 sm:inline-block; + } + + .blog-editor-content .ProseMirror { + @apply min-h-[420px] px-5 py-4 text-base leading-8 text-slate-700 outline-none; + } + + .blog-editor-content .ProseMirror p.is-editor-empty:first-child::before { + @apply pointer-events-none float-right h-0 text-slate-400; + content: attr(data-placeholder); + } + + .blog-editor-prose h2 { + @apply mb-3 mt-8 text-2xl font-bold text-slate-900; + } + + .blog-editor-prose h3 { + @apply mb-2 mt-6 text-xl font-bold text-slate-900; + } + + .blog-editor-prose h4 { + @apply mb-2 mt-4 text-lg font-semibold text-slate-900; + } + + .blog-editor-prose p { + @apply my-3; + } + + .blog-editor-prose ul { + @apply my-4 list-disc space-y-2 pr-6 marker:text-brand; + } + + .blog-editor-prose ol { + @apply my-4 list-decimal space-y-2 pr-6 marker:text-brand; + } + + .blog-editor-prose blockquote { + @apply my-6 border-r-4 border-brand/40 bg-slate-50 px-4 py-3 italic text-slate-600; + } + + .blog-editor-prose a { + @apply text-brand underline decoration-brand/30 underline-offset-2; + } + + .blog-editor-prose hr { + @apply my-8 border-slate-200; + } + + .blog-editor-prose img, + .blog-editor-image { + @apply my-6 w-full rounded-xl border border-slate-200 object-cover; + } + + .blog-editor-prose figure { + @apply my-6 overflow-hidden rounded-xl border border-slate-200 bg-slate-50; + } + + .blog-editor-prose figcaption { + @apply px-4 py-2 text-center text-sm text-slate-500; + } + + .blog-image-node { + @apply my-6; + } + + .blog-image-node__frame { + @apply relative overflow-hidden rounded-xl border-2 border-transparent transition; + } + + .blog-image-node.is-selected .blog-image-node__frame { + @apply border-brand/50 shadow-md shadow-brand/10; + } + + .blog-image-node__toolbar { + @apply absolute left-3 top-3 flex flex-wrap gap-1; + } + + .blog-image-node__btn { + @apply rounded-md bg-white/95 px-2.5 py-1 text-xs font-medium text-slate-700 shadow-sm transition hover:bg-white; + } + + .blog-image-node__btn--danger { + @apply text-red-600 hover:bg-red-50; + } + + .blog-image-node__caption { + @apply mt-2 text-center text-sm text-slate-500; + } + + .blog-image-node__panel { + @apply mt-3 space-y-2 rounded-lg border border-slate-200 bg-slate-50 p-3; + } + + .blog-image-node__label { + @apply block text-xs font-medium text-slate-600; + } + + .blog-image-node__input { + @apply w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm text-slate-800 focus:border-brand focus:outline-none focus:ring-2 focus:ring-brand/20; + } + + .blog-image-node__actions { + @apply flex flex-wrap gap-2 pt-1; + } + + .blog-image-node__error { + @apply text-xs text-red-600; + } } diff --git a/src/utils/blogContent.ts b/src/utils/blogContent.ts new file mode 100644 index 0000000..7745dde --- /dev/null +++ b/src/utils/blogContent.ts @@ -0,0 +1,116 @@ +type LegacyBlock = + | { type: 'h2'; text: string } + | { type: 'h3'; text: string } + | { type: 'h4'; text: string } + | { type: 'p'; text: string } + | { type: 'ul'; items: string[] } + | { type: 'img'; src: string; alt: string } + +const imagePattern = /^!\[(.*?)\]\((.*?)\)$/ +const headingPattern = /^(#{2,4})\s+(.*)$/ + +function escapeHtml(text: string) { + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') +} + +function parseLegacyContent(content: string): LegacyBlock[] { + const blocks: LegacyBlock[] = [] + const lines = content.replace(/\r\n/g, '\n').split('\n') + let paragraph: string[] = [] + let listItems: string[] = [] + + const flushParagraph = () => { + const text = paragraph.join('\n').trim() + if (text) blocks.push({ type: 'p', text }) + paragraph = [] + } + + const flushList = () => { + if (listItems.length) blocks.push({ type: 'ul', items: [...listItems] }) + listItems = [] + } + + for (const rawLine of lines) { + const trimmed = rawLine.trimEnd().trim() + if (!trimmed) { + flushParagraph() + flushList() + continue + } + + const imageMatch = trimmed.match(imagePattern) + if (imageMatch) { + flushParagraph() + flushList() + blocks.push({ + type: 'img', + alt: imageMatch[1] || '', + src: imageMatch[2] || '', + }) + continue + } + + const headingMatch = trimmed.match(headingPattern) + if (headingMatch) { + flushParagraph() + flushList() + const level = headingMatch[1].length + const text = headingMatch[2].trim() + if (level === 2) blocks.push({ type: 'h2', text }) + else if (level === 3) blocks.push({ type: 'h3', text }) + else blocks.push({ type: 'h4', text }) + continue + } + + if (trimmed.startsWith('* ')) { + flushParagraph() + listItems.push(trimmed.slice(2).trim()) + continue + } + + flushList() + paragraph.push(trimmed) + } + + flushParagraph() + flushList() + return blocks +} + +function legacyBlocksToHtml(blocks: LegacyBlock[]) { + return blocks + .map((block) => { + switch (block.type) { + case 'h2': + return `

${escapeHtml(block.text)}

` + case 'h3': + return `

${escapeHtml(block.text)}

` + case 'h4': + return `

${escapeHtml(block.text)}

` + case 'p': + return `

${escapeHtml(block.text).replace(/\n/g, '
')}

` + case 'ul': + return `
    ${block.items.map((item) => `
  • ${escapeHtml(item)}
  • `).join('')}
` + case 'img': + return `
${escapeHtml(block.alt)}${block.alt ? `
${escapeHtml(block.alt)}
` : ''}
` + default: + return '' + } + }) + .join('') +} + +export function isHtmlContent(content: string) { + const trimmed = content.trim() + return /^
-
-

{{ isNew ? 'مطلب جدید' : 'ویرایش مطلب' }}

- بازگشت +
+
+

{{ isNew ? 'مطلب جدید' : 'ویرایش مطلب' }}

+

ویرایشگر بلوکی — شبیه المنتور

+
+
+ بازگشت + +

{{ message }}

@@ -10,55 +18,84 @@
در حال بارگذاری...
-
-
-
-
- - + +
+
+
+ +
- - + +