add contact request
This commit is contained in:
Generated
+672
-24
@@ -8,6 +8,15 @@
|
|||||||
"name": "noro-admin",
|
"name": "noro-admin",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"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",
|
"bcryptjs": "^2.4.3",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
@@ -45,7 +54,6 @@
|
|||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
||||||
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -55,7 +63,6 @@
|
|||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
||||||
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -65,7 +72,6 @@
|
|||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
|
||||||
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.29.7"
|
"@babel/types": "^7.29.7"
|
||||||
@@ -91,7 +97,6 @@
|
|||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
||||||
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.29.7",
|
"@babel/helper-string-parser": "^7.29.7",
|
||||||
@@ -492,6 +497,31 @@
|
|||||||
"node": ">=12"
|
"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": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
@@ -517,7 +547,6 @@
|
|||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
@@ -919,6 +948,480 @@
|
|||||||
"win32"
|
"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": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||||
@@ -944,7 +1447,6 @@
|
|||||||
"version": "3.5.39",
|
"version": "3.5.39",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz",
|
||||||
"integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==",
|
"integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.29.7",
|
"@babel/parser": "^7.29.7",
|
||||||
@@ -958,7 +1460,6 @@
|
|||||||
"version": "3.5.39",
|
"version": "3.5.39",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz",
|
||||||
"integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==",
|
"integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-core": "3.5.39",
|
"@vue/compiler-core": "3.5.39",
|
||||||
@@ -969,7 +1470,6 @@
|
|||||||
"version": "3.5.39",
|
"version": "3.5.39",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz",
|
||||||
"integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==",
|
"integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.29.7",
|
"@babel/parser": "^7.29.7",
|
||||||
@@ -987,7 +1487,6 @@
|
|||||||
"version": "3.5.39",
|
"version": "3.5.39",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz",
|
||||||
"integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==",
|
"integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.5.39",
|
"@vue/compiler-dom": "3.5.39",
|
||||||
@@ -1005,7 +1504,6 @@
|
|||||||
"version": "3.5.39",
|
"version": "3.5.39",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz",
|
||||||
"integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==",
|
"integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "3.5.39"
|
"@vue/shared": "3.5.39"
|
||||||
@@ -1015,7 +1513,6 @@
|
|||||||
"version": "3.5.39",
|
"version": "3.5.39",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz",
|
||||||
"integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==",
|
"integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/reactivity": "3.5.39",
|
"@vue/reactivity": "3.5.39",
|
||||||
@@ -1026,7 +1523,6 @@
|
|||||||
"version": "3.5.39",
|
"version": "3.5.39",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz",
|
||||||
"integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==",
|
"integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/reactivity": "3.5.39",
|
"@vue/reactivity": "3.5.39",
|
||||||
@@ -1039,7 +1535,6 @@
|
|||||||
"version": "3.5.39",
|
"version": "3.5.39",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz",
|
||||||
"integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==",
|
"integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-ssr": "3.5.39",
|
"@vue/compiler-ssr": "3.5.39",
|
||||||
@@ -1053,7 +1548,6 @@
|
|||||||
"version": "3.5.39",
|
"version": "3.5.39",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz",
|
||||||
"integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==",
|
"integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
@@ -1599,7 +2093,6 @@
|
|||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/date-fns": {
|
"node_modules/date-fns": {
|
||||||
@@ -1729,7 +2222,6 @@
|
|||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
||||||
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12"
|
"node": ">=0.12"
|
||||||
@@ -1827,7 +2319,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/etag": {
|
"node_modules/etag": {
|
||||||
@@ -2322,6 +2813,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/lodash": {
|
||||||
"version": "4.18.1",
|
"version": "4.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||||
@@ -2375,7 +2872,6 @@
|
|||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
@@ -2536,7 +3032,6 @@
|
|||||||
"version": "3.3.15",
|
"version": "3.3.15",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
|
||||||
"integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
|
"integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -2623,6 +3118,12 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@@ -2649,7 +3150,6 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
@@ -2689,7 +3189,6 @@
|
|||||||
"version": "8.5.16",
|
"version": "8.5.16",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
|
||||||
"integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
|
"integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -2854,6 +3353,145 @@
|
|||||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
@@ -3060,6 +3698,12 @@
|
|||||||
"fsevents": "~2.3.2"
|
"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": {
|
"node_modules/run-parallel": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
@@ -3272,7 +3916,6 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -3574,7 +4217,7 @@
|
|||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@@ -3726,7 +4369,6 @@
|
|||||||
"version": "3.5.39",
|
"version": "3.5.39",
|
||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz",
|
||||||
"integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==",
|
"integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.5.39",
|
"@vue/compiler-dom": "3.5.39",
|
||||||
@@ -3760,6 +4402,12 @@
|
|||||||
"vue": "^3.5.0"
|
"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": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
|||||||
+10
-1
@@ -12,7 +12,16 @@
|
|||||||
"seed": "node server/seed.js"
|
"seed": "node server/seed.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
|
|||||||
+2
-1
@@ -16,7 +16,8 @@ const defaultDb = () => ({
|
|||||||
clients: [],
|
clients: [],
|
||||||
stats: [],
|
stats: [],
|
||||||
blogPosts: [],
|
blogPosts: [],
|
||||||
media: []
|
media: [],
|
||||||
|
contactRequests: []
|
||||||
})
|
})
|
||||||
|
|
||||||
function ensureDataDir() {
|
function ensureDataDir() {
|
||||||
|
|||||||
@@ -242,6 +242,70 @@ app.put('/api/media/:id', authMiddleware, (req, res) => {
|
|||||||
res.json(updated)
|
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) => {
|
app.delete('/api/media/:id', authMiddleware, (req, res) => {
|
||||||
let removed = null
|
let removed = null
|
||||||
updateDb((db) => {
|
updateDb((db) => {
|
||||||
|
|||||||
@@ -85,6 +85,17 @@ export interface MediaItem {
|
|||||||
createdAt?: string
|
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 {
|
export interface SiteSettings {
|
||||||
name?: string
|
name?: string
|
||||||
nameEn?: string
|
nameEn?: string
|
||||||
@@ -296,6 +307,21 @@ export const api = {
|
|||||||
deleteMedia(id: string) {
|
deleteMedia(id: string) {
|
||||||
return request<void>(`/media/${id}`, { method: 'DELETE' })
|
return request<void>(`/media/${id}`, { method: 'DELETE' })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getContactRequests() {
|
||||||
|
return request<ContactRequest[]>('/contact-requests')
|
||||||
|
},
|
||||||
|
|
||||||
|
updateContactRequest(id: string, data: Pick<ContactRequest, 'status'>) {
|
||||||
|
return request<ContactRequest>(`/contact-requests/${id}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteContactRequest(id: string) {
|
||||||
|
return request<void>(`/contact-requests/${id}`, { method: 'DELETE' })
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ApiError }
|
export { ApiError }
|
||||||
|
|||||||
@@ -0,0 +1,423 @@
|
|||||||
|
<template>
|
||||||
|
<div class="blog-editor overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm">
|
||||||
|
<div class="flex flex-wrap items-center gap-1 border-b border-slate-200 bg-slate-50 px-2 py-2">
|
||||||
|
<div class="flex flex-wrap items-center gap-0.5">
|
||||||
|
<button
|
||||||
|
v-for="item in headingOptions"
|
||||||
|
:key="item.level"
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
:class="{ 'is-active': editor?.isActive('heading', { level: item.level }) }"
|
||||||
|
:title="item.label"
|
||||||
|
@click="setHeading(item.level)"
|
||||||
|
>
|
||||||
|
{{ item.short }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="editor-divider" />
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
:class="{ 'is-active': editor?.isActive('bold') }"
|
||||||
|
title="درشت"
|
||||||
|
@click="editor?.chain().focus().toggleBold().run()"
|
||||||
|
>
|
||||||
|
<strong>B</strong>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
:class="{ 'is-active': editor?.isActive('italic') }"
|
||||||
|
title="کج"
|
||||||
|
@click="editor?.chain().focus().toggleItalic().run()"
|
||||||
|
>
|
||||||
|
<em>I</em>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
:class="{ 'is-active': editor?.isActive('underline') }"
|
||||||
|
title="زیرخط"
|
||||||
|
@click="editor?.chain().focus().toggleUnderline().run()"
|
||||||
|
>
|
||||||
|
<span class="underline">U</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
:class="{ 'is-active': editor?.isActive('strike') }"
|
||||||
|
title="خطخورده"
|
||||||
|
@click="editor?.chain().focus().toggleStrike().run()"
|
||||||
|
>
|
||||||
|
<span class="line-through">S</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="editor-divider" />
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
:class="{ 'is-active': editor?.isActive({ textAlign: 'right' }) }"
|
||||||
|
title="راستچین"
|
||||||
|
@click="editor?.chain().focus().setTextAlign('right').run()"
|
||||||
|
>
|
||||||
|
≡
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
:class="{ 'is-active': editor?.isActive({ textAlign: 'center' }) }"
|
||||||
|
title="وسطچین"
|
||||||
|
@click="editor?.chain().focus().setTextAlign('center').run()"
|
||||||
|
>
|
||||||
|
≡
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
:class="{ 'is-active': editor?.isActive({ textAlign: 'left' }) }"
|
||||||
|
title="چپچین"
|
||||||
|
@click="editor?.chain().focus().setTextAlign('left').run()"
|
||||||
|
>
|
||||||
|
≡
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="editor-divider" />
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
:class="{ 'is-active': editor?.isActive('bulletList') }"
|
||||||
|
title="لیست نقطهای"
|
||||||
|
@click="editor?.chain().focus().toggleBulletList().run()"
|
||||||
|
>
|
||||||
|
•≡
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
:class="{ 'is-active': editor?.isActive('orderedList') }"
|
||||||
|
title="لیست شمارهدار"
|
||||||
|
@click="editor?.chain().focus().toggleOrderedList().run()"
|
||||||
|
>
|
||||||
|
1.
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
:class="{ 'is-active': editor?.isActive('blockquote') }"
|
||||||
|
title="نقلقول"
|
||||||
|
@click="editor?.chain().focus().toggleBlockquote().run()"
|
||||||
|
>
|
||||||
|
"
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
title="خط جداکننده"
|
||||||
|
@click="editor?.chain().focus().setHorizontalRule().run()"
|
||||||
|
>
|
||||||
|
―
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="editor-divider" />
|
||||||
|
|
||||||
|
<button type="button" class="editor-btn" title="لینک" @click="setLink">
|
||||||
|
🔗
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="editor?.isActive('link')"
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
title="حذف لینک"
|
||||||
|
@click="editor?.chain().focus().unsetLink().run()"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
:disabled="uploadingImage"
|
||||||
|
title="درج تصویر در محل مکاننما"
|
||||||
|
@click="triggerImageUpload('insert')"
|
||||||
|
>
|
||||||
|
{{ uploadingImage ? '...' : '🖼' }}
|
||||||
|
</button>
|
||||||
|
<template v-if="editor?.isActive('image')">
|
||||||
|
<button type="button" class="editor-btn" title="ویرایش تصویر" @click="editSelectedImage">
|
||||||
|
✎
|
||||||
|
</button>
|
||||||
|
<button type="button" class="editor-btn" title="جایگزینی تصویر" @click="triggerImageUpload('replace')">
|
||||||
|
↻
|
||||||
|
</button>
|
||||||
|
<button type="button" class="editor-btn" title="حذف تصویر" @click="deleteSelectedImage">
|
||||||
|
🗑
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<input
|
||||||
|
ref="imageInput"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
class="hidden"
|
||||||
|
@change="onImageSelected"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span class="editor-divider" />
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
:disabled="!editor?.can().chain().focus().undo().run()"
|
||||||
|
title="بازگشت"
|
||||||
|
@click="editor?.chain().focus().undo().run()"
|
||||||
|
>
|
||||||
|
↶
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="editor-btn"
|
||||||
|
:disabled="!editor?.can().chain().focus().redo().run()"
|
||||||
|
title="ازنو"
|
||||||
|
@click="editor?.chain().focus().redo().run()"
|
||||||
|
>
|
||||||
|
↷
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid lg:grid-cols-[220px_1fr]">
|
||||||
|
<aside class="border-b border-slate-200 bg-slate-50/80 p-3 lg:border-b-0 lg:border-l">
|
||||||
|
<p class="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-500">
|
||||||
|
افزودن بلوک
|
||||||
|
</p>
|
||||||
|
<div class="grid gap-2 sm:grid-cols-2 lg:grid-cols-1">
|
||||||
|
<button
|
||||||
|
v-for="block in blockWidgets"
|
||||||
|
:key="block.id"
|
||||||
|
type="button"
|
||||||
|
class="flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-3 py-2 text-right text-sm text-slate-700 transition hover:border-brand/40 hover:bg-brand/5"
|
||||||
|
@click="block.action()"
|
||||||
|
>
|
||||||
|
<span class="text-base" aria-hidden="true">{{ block.icon }}</span>
|
||||||
|
<span>{{ block.label }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<div class="relative">
|
||||||
|
<EditorContent :editor="editor" class="blog-editor-content" />
|
||||||
|
<p v-if="editor" class="border-t border-slate-100 px-4 py-2 text-xs text-slate-400">
|
||||||
|
{{ wordCount }} کلمه · {{ characterCount }} کاراکتر
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
||||||
|
import { EditorContent, useEditor } from '@tiptap/vue-3'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import Link from '@tiptap/extension-link'
|
||||||
|
import TextAlign from '@tiptap/extension-text-align'
|
||||||
|
import Underline from '@tiptap/extension-underline'
|
||||||
|
import Placeholder from '@tiptap/extension-placeholder'
|
||||||
|
import CharacterCount from '@tiptap/extension-character-count'
|
||||||
|
import { BlogImage } from '@/components/blogImageExtension'
|
||||||
|
import { api } from '@/api/client'
|
||||||
|
import { normalizeEditorContent } from '@/utils/blogContent'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [value: string]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const imageInput = ref<HTMLInputElement | null>(null)
|
||||||
|
const uploadingImage = ref(false)
|
||||||
|
const imageUploadMode = ref<'insert' | 'replace'>('insert')
|
||||||
|
|
||||||
|
const headingOptions = [
|
||||||
|
{ level: 2 as const, label: 'عنوان بخش', short: 'H2' },
|
||||||
|
{ level: 3 as const, label: 'زیرعنوان', short: 'H3' },
|
||||||
|
{ level: 4 as const, label: 'عنوان کوچک', short: 'H4' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const editor = useEditor({
|
||||||
|
content: normalizeEditorContent(props.modelValue),
|
||||||
|
extensions: [
|
||||||
|
StarterKit.configure({
|
||||||
|
heading: { levels: [2, 3, 4] },
|
||||||
|
}),
|
||||||
|
Underline,
|
||||||
|
Link.configure({
|
||||||
|
openOnClick: false,
|
||||||
|
HTMLAttributes: { rel: 'noopener noreferrer' },
|
||||||
|
}),
|
||||||
|
BlogImage.configure({
|
||||||
|
HTMLAttributes: { class: 'blog-editor-image' },
|
||||||
|
inline: false,
|
||||||
|
}),
|
||||||
|
TextAlign.configure({
|
||||||
|
types: ['heading', 'paragraph'],
|
||||||
|
}),
|
||||||
|
Placeholder.configure({
|
||||||
|
placeholder: 'محتوای مقاله را اینجا بنویسید یا از پنل کناری بلوک اضافه کنید...',
|
||||||
|
}),
|
||||||
|
CharacterCount,
|
||||||
|
],
|
||||||
|
editorProps: {
|
||||||
|
attributes: {
|
||||||
|
class: 'blog-editor-prose',
|
||||||
|
dir: 'rtl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onUpdate: ({ editor: current }) => {
|
||||||
|
emit('update:modelValue', current.getHTML())
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const characterCount = computed(() => editor.value?.storage.characterCount.characters() ?? 0)
|
||||||
|
const wordCount = computed(() => editor.value?.storage.characterCount.words() ?? 0)
|
||||||
|
|
||||||
|
const blockWidgets = computed(() => [
|
||||||
|
{
|
||||||
|
id: 'paragraph',
|
||||||
|
icon: '¶',
|
||||||
|
label: 'پاراگراف',
|
||||||
|
action: () => editor.value?.chain().focus().setParagraph().run(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'h2',
|
||||||
|
icon: 'H',
|
||||||
|
label: 'عنوان اصلی',
|
||||||
|
action: () => editor.value?.chain().focus().toggleHeading({ level: 2 }).run(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'h3',
|
||||||
|
icon: 'h',
|
||||||
|
label: 'زیرعنوان',
|
||||||
|
action: () => editor.value?.chain().focus().toggleHeading({ level: 3 }).run(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'list',
|
||||||
|
icon: '•',
|
||||||
|
label: 'لیست',
|
||||||
|
action: () => editor.value?.chain().focus().toggleBulletList().run(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'quote',
|
||||||
|
icon: '❝',
|
||||||
|
label: 'نقلقول',
|
||||||
|
action: () => editor.value?.chain().focus().toggleBlockquote().run(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'image',
|
||||||
|
icon: '🖼',
|
||||||
|
label: 'تصویر',
|
||||||
|
action: () => insertImageAtCursor(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'divider',
|
||||||
|
icon: '―',
|
||||||
|
label: 'جداکننده',
|
||||||
|
action: () => editor.value?.chain().focus().setHorizontalRule().run(),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(value) => {
|
||||||
|
if (!editor.value) return
|
||||||
|
const current = editor.value.getHTML()
|
||||||
|
const normalized = normalizeEditorContent(value)
|
||||||
|
if (normalized !== current) {
|
||||||
|
editor.value.commands.setContent(normalized, { emitUpdate: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function setHeading(level: 2 | 3 | 4) {
|
||||||
|
editor.value?.chain().focus().toggleHeading({ level }).run()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLink() {
|
||||||
|
const previous = editor.value?.getAttributes('link').href as string | undefined
|
||||||
|
const url = window.prompt('آدرس لینک را وارد کنید:', previous || 'https://')
|
||||||
|
if (url === null) return
|
||||||
|
if (!url.trim()) {
|
||||||
|
editor.value?.chain().focus().extendMarkRange('link').unsetLink().run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
editor.value?.chain().focus().extendMarkRange('link').setLink({ href: url.trim() }).run()
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerImageUpload(mode: 'insert' | 'replace' = 'insert') {
|
||||||
|
imageUploadMode.value = mode
|
||||||
|
imageInput.value?.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertImageAtCursor() {
|
||||||
|
if (!editor.value) return
|
||||||
|
editor.value.chain().focus().run()
|
||||||
|
triggerImageUpload('insert')
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSelectedImage() {
|
||||||
|
if (!editor.value?.isActive('image')) return
|
||||||
|
if (!confirm('این تصویر از متن حذف شود؟')) return
|
||||||
|
editor.value.chain().focus().deleteSelection().run()
|
||||||
|
}
|
||||||
|
|
||||||
|
function editSelectedImage() {
|
||||||
|
if (!editor.value?.isActive('image')) return
|
||||||
|
const currentAlt = (editor.value.getAttributes('image').alt as string) || ''
|
||||||
|
const alt = window.prompt('توضیح تصویر (زیرنویس):', currentAlt)
|
||||||
|
if (alt === null) return
|
||||||
|
editor.value.chain().focus().updateAttributes('image', { alt: alt.trim() }).run()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onImageSelected(event: Event) {
|
||||||
|
const input = event.target as HTMLInputElement
|
||||||
|
const file = input.files?.[0]
|
||||||
|
if (!file || !editor.value) return
|
||||||
|
|
||||||
|
uploadingImage.value = true
|
||||||
|
try {
|
||||||
|
const uploaded = await api.uploadMedia(file)
|
||||||
|
const currentAlt =
|
||||||
|
imageUploadMode.value === 'replace'
|
||||||
|
? ((editor.value.getAttributes('image').alt as string) || '')
|
||||||
|
: ''
|
||||||
|
const alt =
|
||||||
|
window.prompt('توضیح تصویر (زیرنویس — اختیاری):', currentAlt || file.name) ?? currentAlt
|
||||||
|
|
||||||
|
if (imageUploadMode.value === 'replace' && editor.value.isActive('image')) {
|
||||||
|
editor.value
|
||||||
|
.chain()
|
||||||
|
.focus()
|
||||||
|
.updateAttributes('image', { src: uploaded.url, alt: alt.trim() })
|
||||||
|
.run()
|
||||||
|
} else {
|
||||||
|
editor.value
|
||||||
|
.chain()
|
||||||
|
.focus()
|
||||||
|
.setImage({ src: uploaded.url, alt: alt.trim() })
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
window.alert(err instanceof Error ? err.message : 'خطا در آپلود تصویر')
|
||||||
|
} finally {
|
||||||
|
uploadingImage.value = false
|
||||||
|
input.value = ''
|
||||||
|
imageUploadMode.value = 'insert'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
editor.value?.destroy()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
<template>
|
||||||
|
<NodeViewWrapper
|
||||||
|
as="figure"
|
||||||
|
class="blog-image-node"
|
||||||
|
:class="{ 'is-selected': selected }"
|
||||||
|
data-drag-handle
|
||||||
|
>
|
||||||
|
<div class="blog-image-node__frame">
|
||||||
|
<img
|
||||||
|
:src="node.attrs.src"
|
||||||
|
:alt="node.attrs.alt || ''"
|
||||||
|
:title="node.attrs.title || undefined"
|
||||||
|
class="blog-editor-image"
|
||||||
|
draggable="false"
|
||||||
|
@click="selectImage"
|
||||||
|
>
|
||||||
|
|
||||||
|
<div v-if="selected" class="blog-image-node__toolbar">
|
||||||
|
<button type="button" class="blog-image-node__btn" @click.stop="toggleEdit">
|
||||||
|
{{ editing ? 'بستن' : 'ویرایش' }}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="blog-image-node__btn" @click.stop="replaceImage">
|
||||||
|
جایگزین
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="blog-image-node__btn blog-image-node__btn--danger"
|
||||||
|
@click.stop="removeImage"
|
||||||
|
>
|
||||||
|
حذف
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<figcaption v-if="node.attrs.alt && !editing" class="blog-image-node__caption">
|
||||||
|
{{ node.attrs.alt }}
|
||||||
|
</figcaption>
|
||||||
|
|
||||||
|
<div v-if="selected && editing" class="blog-image-node__panel" @click.stop>
|
||||||
|
<label class="blog-image-node__label">توضیح تصویر (زیرنویس)</label>
|
||||||
|
<input
|
||||||
|
v-model="altText"
|
||||||
|
type="text"
|
||||||
|
class="blog-image-node__input"
|
||||||
|
placeholder="مثلاً: نمونه طراحی لوگو"
|
||||||
|
@keydown.enter.prevent="saveAlt"
|
||||||
|
>
|
||||||
|
<label class="blog-image-node__label">آدرس تصویر</label>
|
||||||
|
<input
|
||||||
|
v-model="srcText"
|
||||||
|
type="text"
|
||||||
|
class="blog-image-node__input"
|
||||||
|
dir="ltr"
|
||||||
|
placeholder="/uploads/..."
|
||||||
|
@keydown.enter.prevent="saveSrc"
|
||||||
|
>
|
||||||
|
<div class="blog-image-node__actions">
|
||||||
|
<button type="button" class="blog-image-node__btn" @click="saveEdits">
|
||||||
|
اعمال تغییرات
|
||||||
|
</button>
|
||||||
|
<button type="button" class="blog-image-node__btn" @click="replaceImage">
|
||||||
|
آپلود تصویر جدید
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p v-if="localError" class="blog-image-node__error">{{ localError }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
ref="fileInput"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
class="hidden"
|
||||||
|
@change="onFileSelected"
|
||||||
|
>
|
||||||
|
</NodeViewWrapper>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'
|
||||||
|
import { api } from '@/api/client'
|
||||||
|
|
||||||
|
const props = defineProps(nodeViewProps)
|
||||||
|
|
||||||
|
const editing = ref(false)
|
||||||
|
const altText = ref('')
|
||||||
|
const srcText = ref('')
|
||||||
|
const localError = ref('')
|
||||||
|
const uploading = ref(false)
|
||||||
|
const fileInput = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.selected,
|
||||||
|
(isSelected) => {
|
||||||
|
if (!isSelected) editing.value = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.node.attrs,
|
||||||
|
(attrs) => {
|
||||||
|
altText.value = attrs.alt || ''
|
||||||
|
srcText.value = attrs.src || ''
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
function selectImage() {
|
||||||
|
const pos = props.getPos()
|
||||||
|
if (typeof pos !== 'number') return
|
||||||
|
props.editor.chain().focus().setNodeSelection(pos).run()
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleEdit() {
|
||||||
|
editing.value = !editing.value
|
||||||
|
if (editing.value) {
|
||||||
|
altText.value = props.node.attrs.alt || ''
|
||||||
|
srcText.value = props.node.attrs.src || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveEdits() {
|
||||||
|
props.updateAttributes({
|
||||||
|
alt: altText.value.trim(),
|
||||||
|
src: srcText.value.trim() || props.node.attrs.src,
|
||||||
|
})
|
||||||
|
editing.value = false
|
||||||
|
localError.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAlt() {
|
||||||
|
saveEdits()
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveSrc() {
|
||||||
|
saveEdits()
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeImage() {
|
||||||
|
if (!confirm('این تصویر از متن حذف شود؟')) return
|
||||||
|
props.deleteNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceImage() {
|
||||||
|
fileInput.value?.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onFileSelected(event: Event) {
|
||||||
|
const input = event.target as HTMLInputElement
|
||||||
|
const file = input.files?.[0]
|
||||||
|
if (!file) return
|
||||||
|
|
||||||
|
uploading.value = true
|
||||||
|
localError.value = ''
|
||||||
|
try {
|
||||||
|
const uploaded = await api.uploadMedia(file)
|
||||||
|
props.updateAttributes({ src: uploaded.url })
|
||||||
|
srcText.value = uploaded.url
|
||||||
|
editing.value = true
|
||||||
|
} catch (err) {
|
||||||
|
localError.value = err instanceof Error ? err.message : 'خطا در آپلود تصویر'
|
||||||
|
} finally {
|
||||||
|
uploading.value = false
|
||||||
|
input.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -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)
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -67,6 +67,7 @@ const navItems = [
|
|||||||
{ name: 'services', label: 'خدمات', icon: '💼' },
|
{ name: 'services', label: 'خدمات', icon: '💼' },
|
||||||
{ name: 'clients', label: 'مشتریان', icon: '🤝' },
|
{ name: 'clients', label: 'مشتریان', icon: '🤝' },
|
||||||
{ name: 'blog', label: 'وبلاگ', icon: '📝' },
|
{ name: 'blog', label: 'وبلاگ', icon: '📝' },
|
||||||
|
{ name: 'contact-requests', label: 'درخواستها', icon: '📩' },
|
||||||
{ name: 'media', label: 'رسانه', icon: '🖼️' },
|
{ name: 'media', label: 'رسانه', icon: '🖼️' },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@ const titles: Record<string, string> = {
|
|||||||
clients: 'مشتریان',
|
clients: 'مشتریان',
|
||||||
blog: 'وبلاگ',
|
blog: 'وبلاگ',
|
||||||
'blog-edit': 'ویرایش مطلب',
|
'blog-edit': 'ویرایش مطلب',
|
||||||
|
'contact-requests': 'درخواستهای تماس',
|
||||||
media: 'کتابخانه رسانه',
|
media: 'کتابخانه رسانه',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,11 @@ const router = createRouter({
|
|||||||
name: 'media',
|
name: 'media',
|
||||||
component: () => import('@/views/MediaView.vue'),
|
component: () => import('@/views/MediaView.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'contact-requests',
|
||||||
|
name: 'contact-requests',
|
||||||
|
component: () => import('@/views/ContactRequestsView.vue'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
+118
@@ -40,4 +40,122 @@
|
|||||||
.page-title {
|
.page-title {
|
||||||
@apply text-2xl font-bold text-slate-900;
|
@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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, '>')
|
||||||
|
.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 `<h2>${escapeHtml(block.text)}</h2>`
|
||||||
|
case 'h3':
|
||||||
|
return `<h3>${escapeHtml(block.text)}</h3>`
|
||||||
|
case 'h4':
|
||||||
|
return `<h4>${escapeHtml(block.text)}</h4>`
|
||||||
|
case 'p':
|
||||||
|
return `<p>${escapeHtml(block.text).replace(/\n/g, '<br>')}</p>`
|
||||||
|
case 'ul':
|
||||||
|
return `<ul>${block.items.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul>`
|
||||||
|
case 'img':
|
||||||
|
return `<figure><img src="${escapeHtml(block.src)}" alt="${escapeHtml(block.alt)}" />${block.alt ? `<figcaption>${escapeHtml(block.alt)}</figcaption>` : ''}</figure>`
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isHtmlContent(content: string) {
|
||||||
|
const trimmed = content.trim()
|
||||||
|
return /^</.test(trimmed) || /<(?:p|h[1-6]|ul|ol|figure|img|blockquote)\b/i.test(trimmed)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeEditorContent(content: string) {
|
||||||
|
if (!content?.trim()) return ''
|
||||||
|
if (isHtmlContent(content)) return content
|
||||||
|
return legacyBlocksToHtml(parseLegacyContent(content))
|
||||||
|
}
|
||||||
+79
-41
@@ -1,8 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-6 flex items-center justify-between">
|
<div class="mb-6 flex flex-wrap items-center justify-between gap-3">
|
||||||
<h2 class="page-title">{{ isNew ? 'مطلب جدید' : 'ویرایش مطلب' }}</h2>
|
<div>
|
||||||
<RouterLink :to="{ name: 'blog' }" class="btn-secondary">بازگشت</RouterLink>
|
<h2 class="page-title">{{ isNew ? 'مطلب جدید' : 'ویرایش مطلب' }}</h2>
|
||||||
|
<p class="mt-1 text-sm text-slate-500">ویرایشگر بلوکی — شبیه المنتور</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<RouterLink :to="{ name: 'blog' }" class="btn-secondary">بازگشت</RouterLink>
|
||||||
|
<button type="button" class="btn-primary" :disabled="saving" @click="save">
|
||||||
|
{{ saving ? 'در حال ذخیره...' : 'ذخیره مطلب' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="message" class="mb-4 rounded-lg bg-green-50 px-4 py-2 text-sm text-green-700">{{ message }}</p>
|
<p v-if="message" class="mb-4 rounded-lg bg-green-50 px-4 py-2 text-sm text-green-700">{{ message }}</p>
|
||||||
@@ -10,55 +18,84 @@
|
|||||||
|
|
||||||
<div v-if="loading" class="text-slate-500">در حال بارگذاری...</div>
|
<div v-if="loading" class="text-slate-500">در حال بارگذاری...</div>
|
||||||
|
|
||||||
<form v-else class="space-y-6" @submit.prevent="save">
|
<form v-else class="grid items-start gap-6 xl:grid-cols-[minmax(0,1fr)_320px]" @submit.prevent="save">
|
||||||
<section class="card space-y-4">
|
<div class="space-y-6">
|
||||||
<div class="grid gap-4 sm:grid-cols-2">
|
<section class="card space-y-4">
|
||||||
<div class="sm:col-span-2">
|
<div>
|
||||||
<label class="label">عنوان</label>
|
<label class="label">عنوان مقاله</label>
|
||||||
<input v-model="form.title" class="input" required @input="maybeSlug" />
|
<input
|
||||||
|
v-model="form.title"
|
||||||
|
class="input text-lg font-semibold"
|
||||||
|
required
|
||||||
|
placeholder="عنوان جذاب برای مقاله..."
|
||||||
|
@input="maybeSlug"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="label">اسلاگ</label>
|
<label class="label">خلاصه (نمایش در لیست وبلاگ)</label>
|
||||||
<input v-model="form.slug" class="input" dir="ltr" required />
|
<textarea
|
||||||
|
v-model="form.excerpt"
|
||||||
|
class="input min-h-[90px]"
|
||||||
|
rows="3"
|
||||||
|
placeholder="۲ تا ۳ جمله خلاصه مقاله..."
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 pt-6">
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<label class="label mb-2">محتوای مقاله</label>
|
||||||
|
<BlogEditor v-model="form.content" />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<aside class="space-y-4 xl:sticky xl:top-6">
|
||||||
|
<section class="card space-y-4">
|
||||||
|
<h3 class="font-semibold text-slate-800">انتشار</h3>
|
||||||
|
<div>
|
||||||
|
<label class="label">اسلاگ (URL)</label>
|
||||||
|
<input
|
||||||
|
v-model="form.slug"
|
||||||
|
class="input"
|
||||||
|
dir="ltr"
|
||||||
|
required
|
||||||
|
@input="slugTouched = true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<label class="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
id="published"
|
id="published"
|
||||||
v-model="form.published"
|
v-model="form.published"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="h-4 w-4 rounded border-slate-300 text-brand focus:ring-brand"
|
class="h-4 w-4 rounded border-slate-300 text-brand focus:ring-brand"
|
||||||
/>
|
/>
|
||||||
<label for="published" class="text-sm text-slate-700">منتشر شده</label>
|
<span class="text-sm text-slate-700">منتشر شده</span>
|
||||||
</div>
|
</label>
|
||||||
<div class="sm:col-span-2">
|
<button type="submit" class="btn-primary w-full" :disabled="saving">
|
||||||
<label class="label">خلاصه</label>
|
{{ saving ? 'در حال ذخیره...' : 'ذخیره مطلب' }}
|
||||||
<textarea v-model="form.excerpt" class="input min-h-[80px]" rows="3" />
|
</button>
|
||||||
</div>
|
</section>
|
||||||
<div class="sm:col-span-2">
|
|
||||||
<label class="label">محتوا</label>
|
|
||||||
<textarea v-model="form.content" class="input min-h-[200px] font-mono text-sm" rows="12" />
|
|
||||||
</div>
|
|
||||||
<div class="sm:col-span-2">
|
|
||||||
<ImageField v-model="form.coverUrl" label="تصویر کاور مطلب" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="card space-y-4">
|
<section class="card">
|
||||||
<h3 class="font-semibold text-slate-800">سئو</h3>
|
<ImageField v-model="form.coverUrl" label="تصویر کاور" />
|
||||||
<div>
|
</section>
|
||||||
<label class="label">عنوان سئو (seoTitle)</label>
|
|
||||||
<input v-model="form.seoTitle" class="input" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="label">توضیحات سئو (seoDescription)</label>
|
|
||||||
<textarea v-model="form.seoDescription" class="input min-h-[80px]" rows="3" />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<button type="submit" class="btn-primary" :disabled="saving">
|
<section class="card space-y-4">
|
||||||
{{ saving ? 'در حال ذخیره...' : 'ذخیره' }}
|
<h3 class="font-semibold text-slate-800">سئو</h3>
|
||||||
</button>
|
<div>
|
||||||
|
<label class="label">عنوان سئو</label>
|
||||||
|
<input v-model="form.seoTitle" class="input" placeholder="در صورت خالی بودن، عنوان مقاله استفاده میشود" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="label">توضیحات سئو</label>
|
||||||
|
<textarea
|
||||||
|
v-model="form.seoDescription"
|
||||||
|
class="input min-h-[90px]"
|
||||||
|
rows="3"
|
||||||
|
placeholder="توضیح کوتاه برای موتورهای جستجو..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</aside>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -67,6 +104,7 @@
|
|||||||
import { computed, onMounted, reactive, ref } from 'vue'
|
import { computed, onMounted, reactive, ref } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { api, type BlogPost } from '@/api/client'
|
import { api, type BlogPost } from '@/api/client'
|
||||||
|
import BlogEditor from '@/components/BlogEditor.vue'
|
||||||
import ImageField from '@/components/ImageField.vue'
|
import ImageField from '@/components/ImageField.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="mb-6 flex items-center justify-between">
|
||||||
|
<h2 class="page-title">درخواستهای تماس</h2>
|
||||||
|
<button type="button" class="btn-secondary" @click="load">بروزرسانی</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-if="error" class="mb-4 rounded-lg bg-red-50 px-4 py-2 text-sm text-red-600">{{ error }}</p>
|
||||||
|
|
||||||
|
<div v-if="loading" class="text-slate-500">در حال بارگذاری...</div>
|
||||||
|
|
||||||
|
<div v-else-if="requests.length === 0" class="card text-center text-slate-500">
|
||||||
|
هنوز درخواستی ثبت نشده است.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="space-y-4">
|
||||||
|
<div
|
||||||
|
v-for="item in requests"
|
||||||
|
:key="item.id"
|
||||||
|
class="card"
|
||||||
|
:class="item.status === 'new' ? 'border-r-4 border-brand' : ''"
|
||||||
|
>
|
||||||
|
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<h3 class="font-semibold text-slate-900">{{ item.name }}</h3>
|
||||||
|
<span
|
||||||
|
class="rounded-full px-2 py-0.5 text-xs"
|
||||||
|
:class="statusClass(item.status)"
|
||||||
|
>
|
||||||
|
{{ statusLabel(item.status) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-1 text-sm text-slate-500">{{ formatDate(item.createdAt) }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
v-if="item.status === 'new'"
|
||||||
|
type="button"
|
||||||
|
class="btn-secondary text-sm"
|
||||||
|
@click="setStatus(item, 'read')"
|
||||||
|
>
|
||||||
|
علامتگذاری خواندهشده
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="item.status !== 'archived'"
|
||||||
|
type="button"
|
||||||
|
class="btn-secondary text-sm"
|
||||||
|
@click="setStatus(item, 'archived')"
|
||||||
|
>
|
||||||
|
بایگانی
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="text-sm text-red-500 hover:underline"
|
||||||
|
@click="remove(item.id)"
|
||||||
|
>
|
||||||
|
حذف
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
|
<div>
|
||||||
|
<p class="text-xs text-slate-500">ایمیل</p>
|
||||||
|
<a :href="`mailto:${item.email}`" class="text-sm text-brand hover:underline" dir="ltr">
|
||||||
|
{{ item.email }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.phone">
|
||||||
|
<p class="text-xs text-slate-500">تلفن</p>
|
||||||
|
<a :href="`tel:${item.phone}`" class="text-sm text-slate-800" dir="ltr">
|
||||||
|
{{ item.phone }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-xs text-slate-500">نوع پروژه</p>
|
||||||
|
<p class="text-sm text-slate-800">{{ categoryLabel(item.category) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 rounded-lg bg-slate-50 p-4">
|
||||||
|
<p class="text-xs text-slate-500">توضیحات پروژه</p>
|
||||||
|
<p class="mt-2 whitespace-pre-wrap text-sm leading-relaxed text-slate-700">
|
||||||
|
{{ item.message }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { api, type ContactRequest } from '@/api/client'
|
||||||
|
|
||||||
|
const loading = ref(true)
|
||||||
|
const error = ref('')
|
||||||
|
const requests = ref<ContactRequest[]>([])
|
||||||
|
const categories = ref<Record<string, string>>({})
|
||||||
|
|
||||||
|
function formatDate(value: string) {
|
||||||
|
return new Date(value).toLocaleString('fa-IR', {
|
||||||
|
dateStyle: 'medium',
|
||||||
|
timeStyle: 'short',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusLabel(status: ContactRequest['status']) {
|
||||||
|
if (status === 'new') return 'جدید'
|
||||||
|
if (status === 'read') return 'خواندهشده'
|
||||||
|
return 'بایگانی'
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusClass(status: ContactRequest['status']) {
|
||||||
|
if (status === 'new') return 'bg-brand/10 text-brand-700'
|
||||||
|
if (status === 'read') return 'bg-blue-100 text-blue-700'
|
||||||
|
return 'bg-slate-100 text-slate-600'
|
||||||
|
}
|
||||||
|
|
||||||
|
function categoryLabel(slug: string) {
|
||||||
|
return categories.value[slug] || slug
|
||||||
|
}
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
loading.value = true
|
||||||
|
error.value = ''
|
||||||
|
try {
|
||||||
|
const [items, cats] = await Promise.all([
|
||||||
|
api.getContactRequests(),
|
||||||
|
api.getPortfolioCategories(),
|
||||||
|
])
|
||||||
|
requests.value = items
|
||||||
|
categories.value = Object.fromEntries(cats.map((c) => [c.slug, c.label]))
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e instanceof Error ? e.message : 'خطا در بارگذاری'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setStatus(item: ContactRequest, status: ContactRequest['status']) {
|
||||||
|
try {
|
||||||
|
const updated = await api.updateContactRequest(item.id, { status })
|
||||||
|
item.status = updated.status
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e instanceof Error ? e.message : 'خطا در بروزرسانی'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function remove(id: string) {
|
||||||
|
if (!confirm('آیا از حذف این درخواست مطمئن هستید؟')) return
|
||||||
|
try {
|
||||||
|
await api.deleteContactRequest(id)
|
||||||
|
requests.value = requests.value.filter((r) => r.id !== id)
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e instanceof Error ? e.message : 'خطا در حذف'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(load)
|
||||||
|
</script>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<div v-if="loading" class="text-slate-500">در حال بارگذاری...</div>
|
<div v-if="loading" class="text-slate-500">در حال بارگذاری...</div>
|
||||||
|
|
||||||
<div v-else class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
<div v-else class="grid gap-4 sm:grid-cols-2 lg:grid-cols-5">
|
||||||
<div v-for="stat in stats" :key="stat.label" class="card">
|
<div v-for="stat in stats" :key="stat.label" class="card">
|
||||||
<p class="text-3xl font-bold text-brand">{{ stat.value }}</p>
|
<p class="text-3xl font-bold text-brand">{{ stat.value }}</p>
|
||||||
<p class="mt-1 text-sm text-slate-600">{{ stat.label }}</p>
|
<p class="mt-1 text-sm text-slate-600">{{ stat.label }}</p>
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
<RouterLink :to="{ name: 'site' }" class="btn-secondary">تنظیمات سایت</RouterLink>
|
<RouterLink :to="{ name: 'site' }" class="btn-secondary">تنظیمات سایت</RouterLink>
|
||||||
<RouterLink :to="{ name: 'portfolio-edit' }" class="btn-secondary">نمونهکار جدید</RouterLink>
|
<RouterLink :to="{ name: 'portfolio-edit' }" class="btn-secondary">نمونهکار جدید</RouterLink>
|
||||||
<RouterLink :to="{ name: 'blog-edit' }" class="btn-secondary">مطلب جدید</RouterLink>
|
<RouterLink :to="{ name: 'blog-edit' }" class="btn-secondary">مطلب جدید</RouterLink>
|
||||||
|
<RouterLink :to="{ name: 'contact-requests' }" class="btn-secondary">درخواستهای تماس</RouterLink>
|
||||||
<RouterLink :to="{ name: 'media' }" class="btn-secondary">آپلود رسانه</RouterLink>
|
<RouterLink :to="{ name: 'media' }" class="btn-secondary">آپلود رسانه</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,24 +44,28 @@ const stats = ref([
|
|||||||
{ value: '—', label: 'خدمات' },
|
{ value: '—', label: 'خدمات' },
|
||||||
{ value: '—', label: 'مشتری' },
|
{ value: '—', label: 'مشتری' },
|
||||||
{ value: '—', label: 'مطلب وبلاگ' },
|
{ value: '—', label: 'مطلب وبلاگ' },
|
||||||
|
{ value: '—', label: 'درخواست جدید' },
|
||||||
])
|
])
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const [site, projects, services, clients, posts] = await Promise.all([
|
const [site, projects, services, clients, posts, contactRequests] = await Promise.all([
|
||||||
api.getSite(),
|
api.getSite(),
|
||||||
api.getProjects(),
|
api.getProjects(),
|
||||||
api.getServices(),
|
api.getServices(),
|
||||||
api.getClients(),
|
api.getClients(),
|
||||||
api.getBlogPosts(),
|
api.getBlogPosts(),
|
||||||
|
api.getContactRequests(),
|
||||||
])
|
])
|
||||||
siteName.value = site.name || 'استودیو نورو'
|
siteName.value = site.name || 'استودیو نورو'
|
||||||
siteTagline.value = site.tagline || ''
|
siteTagline.value = site.tagline || ''
|
||||||
|
const newRequests = contactRequests.filter((r) => r.status === 'new').length
|
||||||
stats.value = [
|
stats.value = [
|
||||||
{ value: String(projects.length), label: 'نمونهکار' },
|
{ value: String(projects.length), label: 'نمونهکار' },
|
||||||
{ value: String(services.length), label: 'خدمات' },
|
{ value: String(services.length), label: 'خدمات' },
|
||||||
{ value: String(clients.length), label: 'مشتری' },
|
{ value: String(clients.length), label: 'مشتری' },
|
||||||
{ value: String(posts.length), label: 'مطلب وبلاگ' },
|
{ value: String(posts.length), label: 'مطلب وبلاگ' },
|
||||||
|
{ value: String(newRequests), label: 'درخواست جدید' },
|
||||||
]
|
]
|
||||||
} catch {
|
} catch {
|
||||||
// stats stay as dashes
|
// stats stay as dashes
|
||||||
|
|||||||
Reference in New Issue
Block a user