Significant tiptap updates
This commit is contained in:
@@ -8,10 +8,22 @@
|
|||||||
"@kobalte/core": "^0.13.11",
|
"@kobalte/core": "^0.13.11",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@tiptap/core": "^3.18.0",
|
"@tiptap/core": "^3.18.0",
|
||||||
|
"@tiptap/extension-bubble-menu": "^3.20.0",
|
||||||
|
"@tiptap/extension-dropcursor": "^3.20.0",
|
||||||
|
"@tiptap/extension-floating-menu": "^3.20.0",
|
||||||
|
"@tiptap/extension-highlight": "^3.20.0",
|
||||||
"@tiptap/extension-image": "^3.20.0",
|
"@tiptap/extension-image": "^3.20.0",
|
||||||
|
"@tiptap/extension-link": "^3.20.0",
|
||||||
|
"@tiptap/extension-mention": "^3.20.0",
|
||||||
"@tiptap/extension-placeholder": "^3.18.0",
|
"@tiptap/extension-placeholder": "^3.18.0",
|
||||||
|
"@tiptap/extension-table": "^3.20.0",
|
||||||
|
"@tiptap/extension-table-cell": "^3.20.0",
|
||||||
|
"@tiptap/extension-table-header": "^3.20.0",
|
||||||
|
"@tiptap/extension-table-row": "^3.20.0",
|
||||||
"@tiptap/extension-task-item": "^3.18.0",
|
"@tiptap/extension-task-item": "^3.18.0",
|
||||||
"@tiptap/extension-task-list": "^3.18.0",
|
"@tiptap/extension-task-list": "^3.18.0",
|
||||||
|
"@tiptap/extension-text-align": "^3.20.0",
|
||||||
|
"@tiptap/extension-typography": "^3.20.0",
|
||||||
"@tiptap/extension-underline": "^3.20.0",
|
"@tiptap/extension-underline": "^3.20.0",
|
||||||
"@tiptap/starter-kit": "^3.18.0",
|
"@tiptap/starter-kit": "^3.18.0",
|
||||||
"@tiptap/suggestion": "^3.18.0",
|
"@tiptap/suggestion": "^3.18.0",
|
||||||
@@ -451,6 +463,8 @@
|
|||||||
|
|
||||||
"@tiptap/extension-bold": ["@tiptap/extension-bold@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-xUgOvHCdGXh9Lfxd7DtgsSr0T/egIwBllWHIBWDjQEQQ0b+ICn+0+i703btHMB4hjdduZtgVDrhK8jAW3U6swA=="],
|
"@tiptap/extension-bold": ["@tiptap/extension-bold@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-xUgOvHCdGXh9Lfxd7DtgsSr0T/egIwBllWHIBWDjQEQQ0b+ICn+0+i703btHMB4hjdduZtgVDrhK8jAW3U6swA=="],
|
||||||
|
|
||||||
|
"@tiptap/extension-bubble-menu": ["@tiptap/extension-bubble-menu@3.20.0", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "@tiptap/core": "^3.20.0", "@tiptap/pm": "^3.20.0" } }, "sha512-MDosUfs8Tj+nwg8RC+wTMWGkLJORXmbR6YZgbiX4hrc7G90Gopdd6kj6ht5/T8t7dLLaX7N0+DEHdUEPGED7dw=="],
|
||||||
|
|
||||||
"@tiptap/extension-bullet-list": ["@tiptap/extension-bullet-list@3.18.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.18.0" } }, "sha512-8sEpY0nxAGGFDYlF+WVFPKX00X2dAAjmoi0+2eWvK990PdQqwXrQsRs7pkUbpE2mDtATV8+GlDXk9KDkK/ZXhA=="],
|
"@tiptap/extension-bullet-list": ["@tiptap/extension-bullet-list@3.18.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.18.0" } }, "sha512-8sEpY0nxAGGFDYlF+WVFPKX00X2dAAjmoi0+2eWvK990PdQqwXrQsRs7pkUbpE2mDtATV8+GlDXk9KDkK/ZXhA=="],
|
||||||
|
|
||||||
"@tiptap/extension-code": ["@tiptap/extension-code@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-0SU53O0NRmdtRM2Hgzm372dVoHjs2F40o/dtB7ls4kocf4W89FyWeC2R6ZsFQqcXisNh9RTzLtYfbNyizGuZIw=="],
|
"@tiptap/extension-code": ["@tiptap/extension-code@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-0SU53O0NRmdtRM2Hgzm372dVoHjs2F40o/dtB7ls4kocf4W89FyWeC2R6ZsFQqcXisNh9RTzLtYfbNyizGuZIw=="],
|
||||||
@@ -459,7 +473,9 @@
|
|||||||
|
|
||||||
"@tiptap/extension-document": ["@tiptap/extension-document@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-e0hOGrjTMpCns8IC5p+c5CEiE1BBmFBFL+RpIxU/fjT2SaZ7q2xsFguBu94lQDT0cD6fdZokFRpGwEMxZNVGCg=="],
|
"@tiptap/extension-document": ["@tiptap/extension-document@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-e0hOGrjTMpCns8IC5p+c5CEiE1BBmFBFL+RpIxU/fjT2SaZ7q2xsFguBu94lQDT0cD6fdZokFRpGwEMxZNVGCg=="],
|
||||||
|
|
||||||
"@tiptap/extension-dropcursor": ["@tiptap/extension-dropcursor@3.18.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.18.0" } }, "sha512-pIW/K9fGth221dkfA5SInHcqfnCr0aG9LGkRiEh4gwM4cf6ceUBrvcD+QlemSZ4q9oktNGJmXT+sEXVOQ8QoeQ=="],
|
"@tiptap/extension-dropcursor": ["@tiptap/extension-dropcursor@3.20.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.20.0" } }, "sha512-d+cxplRlktVgZPwatnc34IArlppM0IFKS1J5wLk+ba1jidizsbMVh45tP/BTK2flhyfRqcNoB5R0TArhUpbkNQ=="],
|
||||||
|
|
||||||
|
"@tiptap/extension-floating-menu": ["@tiptap/extension-floating-menu@3.20.0", "", { "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.20.0", "@tiptap/pm": "^3.20.0" } }, "sha512-rYs4Bv5pVjqZ/2vvR6oe7ammZapkAwN51As/WDbemvYDjfOGRqK58qGauUjYZiDzPOEIzI2mxGwsZ4eJhPW4Ig=="],
|
||||||
|
|
||||||
"@tiptap/extension-gapcursor": ["@tiptap/extension-gapcursor@3.18.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.18.0" } }, "sha512-covioXPPHX3SnlTwC/1rcHUHAc7/JFd4vN0kZQmZmvGHlxqq2dPmtrPh8D7TuDuhG0k/3Z6i8dJFP0phfRAhuA=="],
|
"@tiptap/extension-gapcursor": ["@tiptap/extension-gapcursor@3.18.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.18.0" } }, "sha512-covioXPPHX3SnlTwC/1rcHUHAc7/JFd4vN0kZQmZmvGHlxqq2dPmtrPh8D7TuDuhG0k/3Z6i8dJFP0phfRAhuA=="],
|
||||||
|
|
||||||
@@ -467,13 +483,15 @@
|
|||||||
|
|
||||||
"@tiptap/extension-heading": ["@tiptap/extension-heading@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-MTamVnYsFWVndLSq5PRQ7ZmbF6AExsFS9uIvGtUAwuhzvR4of/WHh6wpvWYjA+BLXTWRrfuGHaZTl7UXBN13fg=="],
|
"@tiptap/extension-heading": ["@tiptap/extension-heading@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-MTamVnYsFWVndLSq5PRQ7ZmbF6AExsFS9uIvGtUAwuhzvR4of/WHh6wpvWYjA+BLXTWRrfuGHaZTl7UXBN13fg=="],
|
||||||
|
|
||||||
|
"@tiptap/extension-highlight": ["@tiptap/extension-highlight@3.20.0", "", { "peerDependencies": { "@tiptap/core": "^3.20.0" } }, "sha512-ANL1wFz0s1ScNR4uBfO0s6Sz+qqGp2u6ynrCVk6TCT3d10CQ+gD1gSDTrVRC3CtlMKtHHH4fYrFAq284+J0gKA=="],
|
||||||
|
|
||||||
"@tiptap/extension-horizontal-rule": ["@tiptap/extension-horizontal-rule@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0", "@tiptap/pm": "^3.18.0" } }, "sha512-fEq7DwwQZ496RHNbMQypBVNqoWnhDEERbzWMBqlmfCfc/0FvJrHtsQkk3k4lgqMYqmBwym3Wp0SrRYiyKCPGTw=="],
|
"@tiptap/extension-horizontal-rule": ["@tiptap/extension-horizontal-rule@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0", "@tiptap/pm": "^3.18.0" } }, "sha512-fEq7DwwQZ496RHNbMQypBVNqoWnhDEERbzWMBqlmfCfc/0FvJrHtsQkk3k4lgqMYqmBwym3Wp0SrRYiyKCPGTw=="],
|
||||||
|
|
||||||
"@tiptap/extension-image": ["@tiptap/extension-image@3.20.0", "", { "peerDependencies": { "@tiptap/core": "^3.20.0" } }, "sha512-0t7HYncV0kYEQS79NFczxdlZoZ8zu8X4VavDqt+mbSAUKRq3gCvgtZ5Zyd778sNmtmbz3arxkEYMIVou2swD0g=="],
|
"@tiptap/extension-image": ["@tiptap/extension-image@3.20.0", "", { "peerDependencies": { "@tiptap/core": "^3.20.0" } }, "sha512-0t7HYncV0kYEQS79NFczxdlZoZ8zu8X4VavDqt+mbSAUKRq3gCvgtZ5Zyd778sNmtmbz3arxkEYMIVou2swD0g=="],
|
||||||
|
|
||||||
"@tiptap/extension-italic": ["@tiptap/extension-italic@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-1C4nB08psiRo0BPxAbpYq8peUOKnjQWtBCLPbE6B9ToTK3vmUk0AZTqLO11FvokuM1GF5l2Lg3sKrKFuC2hcjQ=="],
|
"@tiptap/extension-italic": ["@tiptap/extension-italic@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-1C4nB08psiRo0BPxAbpYq8peUOKnjQWtBCLPbE6B9ToTK3vmUk0AZTqLO11FvokuM1GF5l2Lg3sKrKFuC2hcjQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-link": ["@tiptap/extension-link@3.18.0", "", { "dependencies": { "linkifyjs": "^4.3.2" }, "peerDependencies": { "@tiptap/core": "^3.18.0", "@tiptap/pm": "^3.18.0" } }, "sha512-1J28C4+fKAMQi7q/UsTjAmgmKTnzjExXY98hEBneiVzFDxqF69n7+Vb7nVTNAIhmmJkZMA0DEcMhSiQC/1/u4A=="],
|
"@tiptap/extension-link": ["@tiptap/extension-link@3.20.0", "", { "dependencies": { "linkifyjs": "^4.3.2" }, "peerDependencies": { "@tiptap/core": "^3.20.0", "@tiptap/pm": "^3.20.0" } }, "sha512-qI/5A+R0ZWBxo/8HxSn1uOyr7odr3xHBZ/gzOR1GUJaZqjlJxkWFX0RtXMbLKEGEvT25o345cF7b0wFznEh8qA=="],
|
||||||
|
|
||||||
"@tiptap/extension-list": ["@tiptap/extension-list@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0", "@tiptap/pm": "^3.18.0" } }, "sha512-9lQBo45HNqIFcLEHAk+CY3W51eMMxIJjWbthm2CwEWr4PB3+922YELlvq8JcLH1nVFkBVpmBFmQe/GxgnCkzwQ=="],
|
"@tiptap/extension-list": ["@tiptap/extension-list@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0", "@tiptap/pm": "^3.18.0" } }, "sha512-9lQBo45HNqIFcLEHAk+CY3W51eMMxIJjWbthm2CwEWr4PB3+922YELlvq8JcLH1nVFkBVpmBFmQe/GxgnCkzwQ=="],
|
||||||
|
|
||||||
@@ -481,6 +499,8 @@
|
|||||||
|
|
||||||
"@tiptap/extension-list-keymap": ["@tiptap/extension-list-keymap@3.18.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.18.0" } }, "sha512-ZzO5r/cW7G0zpL/eM69WPnMpzb0YsSjtI60CYGA0iQDRJnK9INvxu0RU0ewM2faqqwASmtjuNJac+Fjk6scdXg=="],
|
"@tiptap/extension-list-keymap": ["@tiptap/extension-list-keymap@3.18.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.18.0" } }, "sha512-ZzO5r/cW7G0zpL/eM69WPnMpzb0YsSjtI60CYGA0iQDRJnK9INvxu0RU0ewM2faqqwASmtjuNJac+Fjk6scdXg=="],
|
||||||
|
|
||||||
|
"@tiptap/extension-mention": ["@tiptap/extension-mention@3.20.0", "", { "peerDependencies": { "@tiptap/core": "^3.20.0", "@tiptap/pm": "^3.20.0", "@tiptap/suggestion": "^3.20.0" } }, "sha512-wUjsq7Za0JJdJzrGNG+g8nrCpek/85GQ0Rm9bka3PynIVRwus+xQqW6IyWVPBdl1BSkrbgMAUqtrfoh1ymznbg=="],
|
||||||
|
|
||||||
"@tiptap/extension-ordered-list": ["@tiptap/extension-ordered-list@3.18.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.18.0" } }, "sha512-5bUAfklYLS5o6qvLLfreGyGvD1JKXqOQF0YntLyPuCGrXv7+XjPWQL2BmEf59fOn2UPT2syXLQ1WN5MHTArRzg=="],
|
"@tiptap/extension-ordered-list": ["@tiptap/extension-ordered-list@3.18.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.18.0" } }, "sha512-5bUAfklYLS5o6qvLLfreGyGvD1JKXqOQF0YntLyPuCGrXv7+XjPWQL2BmEf59fOn2UPT2syXLQ1WN5MHTArRzg=="],
|
||||||
|
|
||||||
"@tiptap/extension-paragraph": ["@tiptap/extension-paragraph@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-uvFhdwiur4NhhUdBmDsajxjGAIlg5qga55fYag2DzOXxIQE2M7/aVMRkRpuJzb88GY4EHSh8rY34HgMK2FJt2Q=="],
|
"@tiptap/extension-paragraph": ["@tiptap/extension-paragraph@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-uvFhdwiur4NhhUdBmDsajxjGAIlg5qga55fYag2DzOXxIQE2M7/aVMRkRpuJzb88GY4EHSh8rY34HgMK2FJt2Q=="],
|
||||||
@@ -489,12 +509,24 @@
|
|||||||
|
|
||||||
"@tiptap/extension-strike": ["@tiptap/extension-strike@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-kl/fa68LZg8NWUqTkRTfgyCx+IGqozBmzJxQDc1zxurrIU+VFptDV9UuZim587sbM2KGjCi/PNPjPGk1Uu0PVg=="],
|
"@tiptap/extension-strike": ["@tiptap/extension-strike@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-kl/fa68LZg8NWUqTkRTfgyCx+IGqozBmzJxQDc1zxurrIU+VFptDV9UuZim587sbM2KGjCi/PNPjPGk1Uu0PVg=="],
|
||||||
|
|
||||||
|
"@tiptap/extension-table": ["@tiptap/extension-table@3.20.0", "", { "peerDependencies": { "@tiptap/core": "^3.20.0", "@tiptap/pm": "^3.20.0" } }, "sha512-vaaMtQ2KnSSr8WVwgWf7NYNzPwrHx/6T0ekA5CxV8qNUEpXIaLXa5+tE7tJHWEdNR2KY3gUJ46D3lfOkxyFrBQ=="],
|
||||||
|
|
||||||
|
"@tiptap/extension-table-cell": ["@tiptap/extension-table-cell@3.20.0", "", { "peerDependencies": { "@tiptap/extension-table": "^3.20.0" } }, "sha512-9Dg4zda3UWwtpBwSG7b9BeQy5oT27a/yEIBeARuxe19bloMLZgqpPRtnSrOK0OAITtVnjA+NZdKPcVLRMS2E8A=="],
|
||||||
|
|
||||||
|
"@tiptap/extension-table-header": ["@tiptap/extension-table-header@3.20.0", "", { "peerDependencies": { "@tiptap/extension-table": "^3.20.0" } }, "sha512-2tVHHlihpeHO/gh2uU46gAX3NTGdKR+yDmfLlO2l0QAvx2TXNfNzX2pOM4MmyostW5Ko9TCWV4x0D9h3IQDhPw=="],
|
||||||
|
|
||||||
|
"@tiptap/extension-table-row": ["@tiptap/extension-table-row@3.20.0", "", { "peerDependencies": { "@tiptap/extension-table": "^3.20.0" } }, "sha512-clkfQahkYW/U48QBh1rPZv3AWWSC9AqGKp2DLTH/SGIorM/NwI0jpPtBETMlvowyQu0ivlH9B896smEph+Do2A=="],
|
||||||
|
|
||||||
"@tiptap/extension-task-item": ["@tiptap/extension-task-item@3.18.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.18.0" } }, "sha512-XWrprk9YN2sc6coDypLxFGnFN51ZW+nKwz+9CHefINK6ndmUOeG1QNDGkQBnU5IB1fZBSEpyJga/shmJ7RfOAg=="],
|
"@tiptap/extension-task-item": ["@tiptap/extension-task-item@3.18.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.18.0" } }, "sha512-XWrprk9YN2sc6coDypLxFGnFN51ZW+nKwz+9CHefINK6ndmUOeG1QNDGkQBnU5IB1fZBSEpyJga/shmJ7RfOAg=="],
|
||||||
|
|
||||||
"@tiptap/extension-task-list": ["@tiptap/extension-task-list@3.18.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.18.0" } }, "sha512-/znJ9/B4dBa4PizWtKFbVyuR7ROnlQA2HngL9Z5IvZQUlru1djpVfU4nuUImOkRWg4H7CRV5i2xL21JkcS57UA=="],
|
"@tiptap/extension-task-list": ["@tiptap/extension-task-list@3.18.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.18.0" } }, "sha512-/znJ9/B4dBa4PizWtKFbVyuR7ROnlQA2HngL9Z5IvZQUlru1djpVfU4nuUImOkRWg4H7CRV5i2xL21JkcS57UA=="],
|
||||||
|
|
||||||
"@tiptap/extension-text": ["@tiptap/extension-text@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-9TvctdnBCwK/zyTi9kS7nGFNl5OvGM8xE0u38ZmQw5t79JOqJHgOroyqMjw8LHK/1PWrozfNCmsZbpq4IZuKXw=="],
|
"@tiptap/extension-text": ["@tiptap/extension-text@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-9TvctdnBCwK/zyTi9kS7nGFNl5OvGM8xE0u38ZmQw5t79JOqJHgOroyqMjw8LHK/1PWrozfNCmsZbpq4IZuKXw=="],
|
||||||
|
|
||||||
|
"@tiptap/extension-text-align": ["@tiptap/extension-text-align@3.20.0", "", { "peerDependencies": { "@tiptap/core": "^3.20.0" } }, "sha512-4s0r+bovtH6yeGDUD+Ui8j5WOV5koB5P6AuzOMqoLwaFGRSkKf64ly6DXjjmjIgnYCLZN/XO6llaQKVVdvad2g=="],
|
||||||
|
|
||||||
|
"@tiptap/extension-typography": ["@tiptap/extension-typography@3.20.0", "", { "peerDependencies": { "@tiptap/core": "^3.20.0" } }, "sha512-XpstIJ/hCK6+N87DdzjaYie1iH1gunbDK0UCsqsLemd8rMR/WAlF04M/yeJ6F2QtNzNh83bkoTxyNAHLDdUm2A=="],
|
||||||
|
|
||||||
"@tiptap/extension-underline": ["@tiptap/extension-underline@3.20.0", "", { "peerDependencies": { "@tiptap/core": "^3.20.0" } }, "sha512-LzNXuy2jwR/y+ymoUqC72TiGzbOCjioIjsDu0MNYpHuHqTWPK5aV9Mh0nbZcYFy/7fPlV1q0W139EbJeYBZEAQ=="],
|
"@tiptap/extension-underline": ["@tiptap/extension-underline@3.20.0", "", { "peerDependencies": { "@tiptap/core": "^3.20.0" } }, "sha512-LzNXuy2jwR/y+ymoUqC72TiGzbOCjioIjsDu0MNYpHuHqTWPK5aV9Mh0nbZcYFy/7fPlV1q0W139EbJeYBZEAQ=="],
|
||||||
|
|
||||||
"@tiptap/extensions": ["@tiptap/extensions@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0", "@tiptap/pm": "^3.18.0" } }, "sha512-uSRIE9HGshBN6NRFR3LX2lZqBLvX92SgU5A9AvUbJD4MqU63E+HdruJnRjsVlX3kPrmbIDowxrzXlUcg3K0USQ=="],
|
"@tiptap/extensions": ["@tiptap/extensions@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0", "@tiptap/pm": "^3.18.0" } }, "sha512-uSRIE9HGshBN6NRFR3LX2lZqBLvX92SgU5A9AvUbJD4MqU63E+HdruJnRjsVlX3kPrmbIDowxrzXlUcg3K0USQ=="],
|
||||||
@@ -1393,6 +1425,10 @@
|
|||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"@tiptap/starter-kit/@tiptap/extension-dropcursor": ["@tiptap/extension-dropcursor@3.18.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.18.0" } }, "sha512-pIW/K9fGth221dkfA5SInHcqfnCr0aG9LGkRiEh4gwM4cf6ceUBrvcD+QlemSZ4q9oktNGJmXT+sEXVOQ8QoeQ=="],
|
||||||
|
|
||||||
|
"@tiptap/starter-kit/@tiptap/extension-link": ["@tiptap/extension-link@3.18.0", "", { "dependencies": { "linkifyjs": "^4.3.2" }, "peerDependencies": { "@tiptap/core": "^3.18.0", "@tiptap/pm": "^3.18.0" } }, "sha512-1J28C4+fKAMQi7q/UsTjAmgmKTnzjExXY98hEBneiVzFDxqF69n7+Vb7nVTNAIhmmJkZMA0DEcMhSiQC/1/u4A=="],
|
||||||
|
|
||||||
"@tiptap/starter-kit/@tiptap/extension-underline": ["@tiptap/extension-underline@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-009IeXURNJ/sm1pBqbj+2YQgjQaBtNlJR3dbl6xu49C+qExqCmI7klhKQuwsVVGLR7ahsYlp7d9RlftnhCXIcQ=="],
|
"@tiptap/starter-kit/@tiptap/extension-underline": ["@tiptap/extension-underline@3.18.0", "", { "peerDependencies": { "@tiptap/core": "^3.18.0" } }, "sha512-009IeXURNJ/sm1pBqbj+2YQgjQaBtNlJR3dbl6xu49C+qExqCmI7klhKQuwsVVGLR7ahsYlp7d9RlftnhCXIcQ=="],
|
||||||
|
|
||||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
|
|||||||
@@ -18,10 +18,22 @@
|
|||||||
"@kobalte/core": "^0.13.11",
|
"@kobalte/core": "^0.13.11",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@tiptap/core": "^3.18.0",
|
"@tiptap/core": "^3.18.0",
|
||||||
|
"@tiptap/extension-bubble-menu": "^3.20.0",
|
||||||
|
"@tiptap/extension-dropcursor": "^3.20.0",
|
||||||
|
"@tiptap/extension-floating-menu": "^3.20.0",
|
||||||
|
"@tiptap/extension-highlight": "^3.20.0",
|
||||||
"@tiptap/extension-image": "^3.20.0",
|
"@tiptap/extension-image": "^3.20.0",
|
||||||
|
"@tiptap/extension-link": "^3.20.0",
|
||||||
|
"@tiptap/extension-mention": "^3.20.0",
|
||||||
"@tiptap/extension-placeholder": "^3.18.0",
|
"@tiptap/extension-placeholder": "^3.18.0",
|
||||||
|
"@tiptap/extension-table": "^3.20.0",
|
||||||
|
"@tiptap/extension-table-cell": "^3.20.0",
|
||||||
|
"@tiptap/extension-table-header": "^3.20.0",
|
||||||
|
"@tiptap/extension-table-row": "^3.20.0",
|
||||||
"@tiptap/extension-task-item": "^3.18.0",
|
"@tiptap/extension-task-item": "^3.18.0",
|
||||||
"@tiptap/extension-task-list": "^3.18.0",
|
"@tiptap/extension-task-list": "^3.18.0",
|
||||||
|
"@tiptap/extension-text-align": "^3.20.0",
|
||||||
|
"@tiptap/extension-typography": "^3.20.0",
|
||||||
"@tiptap/extension-underline": "^3.20.0",
|
"@tiptap/extension-underline": "^3.20.0",
|
||||||
"@tiptap/starter-kit": "^3.18.0",
|
"@tiptap/starter-kit": "^3.18.0",
|
||||||
"@tiptap/suggestion": "^3.18.0",
|
"@tiptap/suggestion": "^3.18.0",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type Component, type JSX, createSignal, createEffect, Show, lazy, Suspense, onMount } from "solid-js";
|
import { type Component, type JSX, createSignal, createEffect, Show, lazy, Suspense, onMount, onCleanup } from "solid-js";
|
||||||
import { Sidebar, BottomNav, ContextSwitcher } from "./Navigation";
|
import { Sidebar, BottomNav, ContextSwitcher } from "./Navigation";
|
||||||
import { store, setStore, activeTaskId, setActiveTaskId } from "@/store";
|
import { store, setStore, activeTaskId, setActiveTaskId } from "@/store";
|
||||||
import { PanelLeftOpen, PanelLeftClose } from "lucide-solid";
|
import { PanelLeftOpen, PanelLeftClose } from "lucide-solid";
|
||||||
@@ -30,6 +30,16 @@ export const Layout: Component<LayoutProps> = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
const handleOpenNote = (e: Event) => {
|
||||||
|
const detail = (e as CustomEvent).detail;
|
||||||
|
if (detail && detail.noteId) {
|
||||||
|
setStore('isNotepadMode', true);
|
||||||
|
setSelectedNoteId(detail.noteId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('open-note', handleOpenNote);
|
||||||
|
onCleanup(() => window.removeEventListener('open-note', handleOpenNote));
|
||||||
|
|
||||||
// Preload heavy components in background when idle
|
// Preload heavy components in background when idle
|
||||||
const idleCallback = (window as any).requestIdleCallback || ((cb: any) => setTimeout(cb, 2000));
|
const idleCallback = (window as any).requestIdleCallback || ((cb: any) => setTimeout(cb, 2000));
|
||||||
idleCallback(() => {
|
idleCallback(() => {
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { type Component, createSignal, For, createEffect } from "solid-js";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
export interface MentionItem {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
sublabel?: string;
|
||||||
|
type: 'user' | 'note';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MentionListProps {
|
||||||
|
items: MentionItem[];
|
||||||
|
command: (item: MentionItem) => void;
|
||||||
|
ref: (handlers: { onKeyDown: (e: KeyboardEvent) => boolean }) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MentionList: Component<MentionListProps> = (props) => {
|
||||||
|
const [selectedIndex, setSelectedIndex] = createSignal(0);
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
props.items;
|
||||||
|
setSelectedIndex(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectItem = (index: number) => {
|
||||||
|
const item = props.items[index];
|
||||||
|
if (item) {
|
||||||
|
props.command(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
props.ref({
|
||||||
|
onKeyDown: (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "ArrowUp") {
|
||||||
|
setSelectedIndex((prev) => (prev + props.items.length - 1) % props.items.length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (e.key === "ArrowDown") {
|
||||||
|
setSelectedIndex((prev) => (prev + 1) % props.items.length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
selectItem(selectedIndex());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="z-[200] w-64 bg-popover border border-border rounded-lg shadow-xl overflow-hidden p-1 bg-background pointer-events-auto">
|
||||||
|
<div class="max-h-[300px] overflow-y-auto scrollbar-thin scrollbar-thumb-muted-foreground/20 overscroll-contain">
|
||||||
|
<For each={props.items}>
|
||||||
|
{(item, index) => (
|
||||||
|
<button
|
||||||
|
class={cn(
|
||||||
|
"flex flex-col w-full px-2 py-1.5 text-left rounded-md transition-colors outline-none",
|
||||||
|
index() === selectedIndex() ? "bg-accent text-accent-foreground" : "hover:bg-muted"
|
||||||
|
)}
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
selectItem(index());
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setSelectedIndex(index())}
|
||||||
|
>
|
||||||
|
<span class="text-sm font-medium leading-none">{item.label}</span>
|
||||||
|
{item.sublabel && (
|
||||||
|
<span class="text-[0.6875rem] text-muted-foreground truncate mt-1">{item.sublabel}</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
{props.items.length === 0 && (
|
||||||
|
<div class="p-2 text-xs text-muted-foreground text-center">No results found</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -4,7 +4,10 @@ import {
|
|||||||
List, ListTodo, Type,
|
List, ListTodo, Type,
|
||||||
Code, Quote,
|
Code, Quote,
|
||||||
Image as ImageIcon, Minus,
|
Image as ImageIcon, Minus,
|
||||||
Bold, Italic, Underline
|
Bold, Italic, Underline,
|
||||||
|
Grid3X3, Highlighter,
|
||||||
|
AlignLeft, AlignCenter, AlignRight,
|
||||||
|
Trash2
|
||||||
} from "lucide-solid";
|
} from "lucide-solid";
|
||||||
import { cn, convertImageToWebpFile } from "@/lib/utils";
|
import { cn, convertImageToWebpFile } from "@/lib/utils";
|
||||||
import { activeTaskId, uploadTaskAttachment } from "@/store";
|
import { activeTaskId, uploadTaskAttachment } from "@/store";
|
||||||
@@ -170,6 +173,56 @@ export const getSuggestionItems = ({ query }: { query: string }): CommandItem[]
|
|||||||
editor.chain().focus().deleteRange(range).setHorizontalRule().run();
|
editor.chain().focus().deleteRange(range).setHorizontalRule().run();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Table",
|
||||||
|
description: "Insert a 3x3 table.",
|
||||||
|
aliases: ["grid", "table"],
|
||||||
|
icon: Grid3X3,
|
||||||
|
command: ({ editor, range }: { editor: any, range: any }) => {
|
||||||
|
editor.chain().focus().deleteRange(range).insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Highlight",
|
||||||
|
description: "Highlight selected text.",
|
||||||
|
aliases: ["mark"],
|
||||||
|
icon: Highlighter,
|
||||||
|
command: ({ editor, range }: { editor: any, range: any }) => {
|
||||||
|
editor.chain().focus().deleteRange(range).toggleHighlight().run();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Align Left",
|
||||||
|
description: "Align text to the left.",
|
||||||
|
icon: AlignLeft,
|
||||||
|
command: ({ editor, range }: { editor: any, range: any }) => {
|
||||||
|
editor.chain().focus().deleteRange(range).setTextAlign("left").run();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Align Center",
|
||||||
|
description: "Align text to the center.",
|
||||||
|
icon: AlignCenter,
|
||||||
|
command: ({ editor, range }: { editor: any, range: any }) => {
|
||||||
|
editor.chain().focus().deleteRange(range).setTextAlign("center").run();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Align Right",
|
||||||
|
description: "Align text to the right.",
|
||||||
|
icon: AlignRight,
|
||||||
|
command: ({ editor, range }: { editor: any, range: any }) => {
|
||||||
|
editor.chain().focus().deleteRange(range).setTextAlign("right").run();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Delete Table",
|
||||||
|
description: "Delete the entire table.",
|
||||||
|
icon: Trash2,
|
||||||
|
command: ({ editor, range }: { editor: any, range: any }) => {
|
||||||
|
editor.chain().focus().deleteRange(range).deleteTable().run();
|
||||||
|
},
|
||||||
|
},
|
||||||
].filter(item => {
|
].filter(item => {
|
||||||
const lowerQuery = query.toLowerCase();
|
const lowerQuery = query.toLowerCase();
|
||||||
return item.title.toLowerCase().startsWith(lowerQuery) ||
|
return item.title.toLowerCase().startsWith(lowerQuery) ||
|
||||||
|
|||||||
@@ -48,6 +48,21 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [trackedMentions, setTrackedMentions] = createSignal<Set<string>>(new Set());
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (props.isOpen && props.task.content) {
|
||||||
|
const html = props.task.content;
|
||||||
|
const mentionRegex = /data-type="mention"[^>]*data-id="([^"]+)"/g;
|
||||||
|
let match;
|
||||||
|
const initial = new Set<string>();
|
||||||
|
while ((match = mentionRegex.exec(html)) !== null) {
|
||||||
|
initial.add(match[1]);
|
||||||
|
}
|
||||||
|
setTrackedMentions(initial);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let debounceTimer: number | undefined;
|
let debounceTimer: number | undefined;
|
||||||
let pendingContent: string | null = null;
|
let pendingContent: string | null = null;
|
||||||
|
|
||||||
@@ -57,6 +72,39 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
|
|||||||
debounceTimer = setTimeout(() => {
|
debounceTimer = setTimeout(() => {
|
||||||
if (pendingContent !== null) {
|
if (pendingContent !== null) {
|
||||||
updateTask(props.task.id, { content: pendingContent });
|
updateTask(props.task.id, { content: pendingContent });
|
||||||
|
|
||||||
|
// Sync mentions deletion
|
||||||
|
const mentionRegex = /data-type="mention"[^>]*data-id="([^"]+)"/g;
|
||||||
|
let match;
|
||||||
|
const currentMentions = new Set<string>();
|
||||||
|
while ((match = mentionRegex.exec(pendingContent)) !== null) {
|
||||||
|
currentMentions.add(match[1].toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTags = props.task.tags || [];
|
||||||
|
let tagsChanged = false;
|
||||||
|
const newTags = currentTags.filter(t => {
|
||||||
|
const tagLower = t.toLowerCase();
|
||||||
|
const isTracked = [...trackedMentions()].some(tm => tm.toLowerCase() === tagLower);
|
||||||
|
if (isTracked && !currentMentions.has(tagLower)) {
|
||||||
|
tagsChanged = true;
|
||||||
|
// Also remove from tracked
|
||||||
|
setTrackedMentions(prev => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
for (let p of next) {
|
||||||
|
if (p.toLowerCase() === tagLower) next.delete(p);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
return false; // remove from tags
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tagsChanged) {
|
||||||
|
updateTags(newTags);
|
||||||
|
}
|
||||||
|
|
||||||
pendingContent = null;
|
pendingContent = null;
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
@@ -353,6 +401,19 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
|
|||||||
content={props.task.content}
|
content={props.task.content}
|
||||||
onUpdate={updateTaskContent}
|
onUpdate={updateTaskContent}
|
||||||
onEditorReady={setEditorInstance}
|
onEditorReady={setEditorInstance}
|
||||||
|
onNoteOpen={(noteId) => {
|
||||||
|
props.onClose();
|
||||||
|
window.dispatchEvent(new CustomEvent('open-note', { detail: { noteId } }));
|
||||||
|
}}
|
||||||
|
onUserMention={(name) => {
|
||||||
|
const tagName = '@' + name;
|
||||||
|
setTrackedMentions(prev => new Set([...prev, tagName]));
|
||||||
|
const currentTags = props.task.tags || [];
|
||||||
|
if (!currentTags.some(t => t.toLowerCase() === tagName.toLowerCase())) {
|
||||||
|
updateTags([...currentTags, tagName]);
|
||||||
|
toast.success(`Added tag ${tagName} to share task.`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
|
|||||||
@@ -6,28 +6,47 @@ import TaskList from "@tiptap/extension-task-list";
|
|||||||
import { CustomTaskItem } from "./CustomTaskItem";
|
import { CustomTaskItem } from "./CustomTaskItem";
|
||||||
import Image from "@tiptap/extension-image";
|
import Image from "@tiptap/extension-image";
|
||||||
import Underline from "@tiptap/extension-underline";
|
import Underline from "@tiptap/extension-underline";
|
||||||
import { X } from "lucide-solid";
|
import { X, Trash2, ArrowUpToLine, ArrowDownToLine, ArrowLeftToLine, ArrowRightToLine } from "lucide-solid";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { store } from "@/store";
|
||||||
|
|
||||||
import { SlashCommands } from "@/lib/slash-command";
|
import { SlashCommands } from "@/lib/slash-command";
|
||||||
import { suggestion } from "@/lib/slash-renderer";
|
import { suggestion } from "@/lib/slash-renderer";
|
||||||
import { MobileIndent } from "@/lib/mobile-indent";
|
import { MobileIndent } from "@/lib/mobile-indent";
|
||||||
|
|
||||||
|
// New Extensions
|
||||||
|
import { Link } from "@tiptap/extension-link";
|
||||||
|
import { Highlight } from "@tiptap/extension-highlight";
|
||||||
|
import { TextAlign } from "@tiptap/extension-text-align";
|
||||||
|
import { Table } from "@tiptap/extension-table";
|
||||||
|
import { TableRow } from "@tiptap/extension-table-row";
|
||||||
|
import { TableCell } from "@tiptap/extension-table-cell";
|
||||||
|
import { TableHeader } from "@tiptap/extension-table-header";
|
||||||
|
import { Typography } from "@tiptap/extension-typography";
|
||||||
|
import { Dropcursor } from "@tiptap/extension-dropcursor";
|
||||||
|
import { BubbleMenu } from "@tiptap/extension-bubble-menu";
|
||||||
|
import { Mention } from "@tiptap/extension-mention";
|
||||||
|
import { userSuggestion, noteSuggestion } from "@/lib/mention-suggestion";
|
||||||
|
|
||||||
interface TaskEditorProps {
|
interface TaskEditorProps {
|
||||||
content?: string;
|
content?: string;
|
||||||
onUpdate: (html: string) => void;
|
onUpdate: (html: string) => void;
|
||||||
onEditorReady?: (editor: any) => void;
|
onEditorReady?: (editor: any) => void;
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
|
onUserMention?: (userName: string) => void;
|
||||||
|
onNoteOpen?: (noteId: string) => void;
|
||||||
class?: string;
|
class?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TaskEditor: Component<TaskEditorProps> = (props) => {
|
export const TaskEditor: Component<TaskEditorProps> = (props) => {
|
||||||
let editorRef: HTMLDivElement | undefined;
|
let editorRef: HTMLDivElement | undefined;
|
||||||
|
let tableMenuRef: HTMLDivElement | undefined;
|
||||||
const [fullscreenImage, setFullscreenImage] = createSignal<string | null>(null);
|
const [fullscreenImage, setFullscreenImage] = createSignal<string | null>(null);
|
||||||
const [zoomScale, setZoomScale] = createSignal(1);
|
const [zoomScale, setZoomScale] = createSignal(1);
|
||||||
const [zoomOffset, setZoomOffset] = createSignal({ x: 0, y: 0 });
|
const [zoomOffset, setZoomOffset] = createSignal({ x: 0, y: 0 });
|
||||||
const [isDragging, setIsDragging] = createSignal(false);
|
const [isDragging, setIsDragging] = createSignal(false);
|
||||||
const [dragStart, setDragStart] = createSignal({ x: 0, y: 0 });
|
const [dragStart, setDragStart] = createSignal({ x: 0, y: 0 });
|
||||||
|
const [previewNote, setPreviewNote] = createSignal<{ noteId: string, x: number, y: number } | null>(null);
|
||||||
|
|
||||||
const resetZoom = () => {
|
const resetZoom = () => {
|
||||||
setZoomScale(1);
|
setZoomScale(1);
|
||||||
@@ -73,6 +92,81 @@ export const TaskEditor: Component<TaskEditorProps> = (props) => {
|
|||||||
}),
|
}),
|
||||||
Underline.configure({}),
|
Underline.configure({}),
|
||||||
MobileIndent.configure({}),
|
MobileIndent.configure({}),
|
||||||
|
Link.configure({
|
||||||
|
autolink: true,
|
||||||
|
openOnClick: false,
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: "text-primary underline underline-offset-4 cursor-pointer hover:text-primary/80 transition-colors",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Highlight.configure({
|
||||||
|
multicolor: true,
|
||||||
|
}),
|
||||||
|
TextAlign.configure({
|
||||||
|
types: ["heading", "paragraph"],
|
||||||
|
}),
|
||||||
|
Table.configure({
|
||||||
|
resizable: true,
|
||||||
|
allowTableNodeSelection: true,
|
||||||
|
}),
|
||||||
|
TableRow,
|
||||||
|
TableHeader,
|
||||||
|
TableCell,
|
||||||
|
Typography.configure({
|
||||||
|
openDoubleQuote: false,
|
||||||
|
closeDoubleQuote: false,
|
||||||
|
openSingleQuote: false,
|
||||||
|
closeSingleQuote: false,
|
||||||
|
}),
|
||||||
|
Mention.configure({
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: "mention bg-primary/10 text-primary font-medium py-0.5 px-1 rounded-md border border-primary/20",
|
||||||
|
},
|
||||||
|
suggestion: {
|
||||||
|
...userSuggestion,
|
||||||
|
command: ({ editor, range, props: mentionProps }: any) => {
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.focus()
|
||||||
|
.insertContentAt(range, [
|
||||||
|
{
|
||||||
|
type: "mention",
|
||||||
|
attrs: mentionProps,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: " ",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.run();
|
||||||
|
|
||||||
|
if (mentionProps.label) {
|
||||||
|
props.onUserMention?.(mentionProps.label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Mention.extend({
|
||||||
|
name: "noteMention",
|
||||||
|
}).configure({
|
||||||
|
suggestion: {
|
||||||
|
...noteSuggestion,
|
||||||
|
char: "#",
|
||||||
|
},
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: "note-mention bg-muted text-muted-foreground font-medium py-0.5 px-1 rounded-md border border-border italic cursor-pointer hover:bg-muted/80",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
BubbleMenu.configure({
|
||||||
|
element: tableMenuRef!,
|
||||||
|
shouldShow: ({ editor }) => editor.isActive("table"),
|
||||||
|
// @ts-ignore - Solid-tiptap types might be outdated
|
||||||
|
tippyOptions: { placement: "bottom", duration: 100 },
|
||||||
|
}),
|
||||||
|
Dropcursor.configure({
|
||||||
|
color: "var(--primary)",
|
||||||
|
width: 2,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
// Use untrack so the editor doesn't re-initialize when props.content changes
|
// Use untrack so the editor doesn't re-initialize when props.content changes
|
||||||
content: untrack(() => props.content) || "",
|
content: untrack(() => props.content) || "",
|
||||||
@@ -88,6 +182,14 @@ export const TaskEditor: Component<TaskEditorProps> = (props) => {
|
|||||||
"prose-p:my-1 prose-headings:my-2 prose-ul:my-1 prose-ol:my-1",
|
"prose-p:my-1 prose-headings:my-2 prose-ul:my-1 prose-ol:my-1",
|
||||||
"prose-pre:bg-muted prose-pre:rounded-lg prose-pre:p-4",
|
"prose-pre:bg-muted prose-pre:rounded-lg prose-pre:p-4",
|
||||||
"prose-img:rounded-lg prose-img:border prose-img:border-border prose-img:cursor-zoom-in",
|
"prose-img:rounded-lg prose-img:border prose-img:border-border prose-img:cursor-zoom-in",
|
||||||
|
// New Extension styles
|
||||||
|
"prose-a:text-primary prose-a:underline prose-a:underline-offset-4",
|
||||||
|
"prose-blockquote:border-l-4 prose-blockquote:border-primary prose-blockquote:pl-4 prose-blockquote:italic",
|
||||||
|
"[&_table]:border-collapse [&_table]:w-full [&_table]:my-4",
|
||||||
|
"[&_th]:border [&_th]:border-border [&_th]:bg-muted/50 [&_th]:p-2 [&_th]:text-left [&_th]:font-bold",
|
||||||
|
"[&_td]:border [&_td]:border-border [&_td]:p-2 [&_td]:align-top",
|
||||||
|
"[&_mark]:bg-primary/20 dark:[&_mark]:bg-primary/40 [&_mark]:text-foreground dark:[&_mark]:text-primary-foreground [&_mark]:px-0.5 [&_mark]:rounded-sm",
|
||||||
|
"[&_.mention]:mx-0.5 [&_.note-mention]:mx-0.5",
|
||||||
// Custom Task List Styling
|
// Custom Task List Styling
|
||||||
"[&_ul[data-type='taskList']]:list-none [&_ul[data-type='taskList']]:p-0",
|
"[&_ul[data-type='taskList']]:list-none [&_ul[data-type='taskList']]:p-0",
|
||||||
"[&_li[data-type='taskItem']]:flex [&_li[data-type='taskItem']]:items-start",
|
"[&_li[data-type='taskItem']]:flex [&_li[data-type='taskItem']]:items-start",
|
||||||
@@ -118,6 +220,32 @@ export const TaskEditor: Component<TaskEditorProps> = (props) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle note mention clicks
|
||||||
|
createEffect(() => {
|
||||||
|
const e = editor();
|
||||||
|
if (!e) return;
|
||||||
|
|
||||||
|
const handleClick = (event: MouseEvent) => {
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
if (target.classList.contains('note-mention')) {
|
||||||
|
const noteId = target.getAttribute('data-id');
|
||||||
|
if (noteId) {
|
||||||
|
const rect = target.getBoundingClientRect();
|
||||||
|
setPreviewNote({ noteId, x: rect.left, y: rect.bottom + window.scrollY });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Close preview if clicking elsewhere
|
||||||
|
setPreviewNote(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const element = e.view.dom;
|
||||||
|
if (element) {
|
||||||
|
element.addEventListener('click', handleClick);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle full screen images as before
|
||||||
return (
|
return (
|
||||||
<div class="relative w-full h-full flex flex-col">
|
<div class="relative w-full h-full flex flex-col">
|
||||||
<div
|
<div
|
||||||
@@ -146,6 +274,65 @@ export const TaskEditor: Component<TaskEditorProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Table Bubble Menu */}
|
||||||
|
<div
|
||||||
|
ref={tableMenuRef}
|
||||||
|
class="z-[9999] flex p-0.5 bg-background border border-border shadow-md rounded-md gap-0.5 invisible data-[state=visible]:visible"
|
||||||
|
>
|
||||||
|
<button type="button" class="p-1 px-1.5 hover:bg-muted rounded text-xs flex flex-col items-center justify-center opacity-80 hover:opacity-100 transition-opacity whitespace-nowrap text-muted-foreground hover:text-foreground" onClick={() => editor()?.chain().focus().addRowBefore().run()} title="Add Row Above"><ArrowUpToLine size={14} /> +Row</button>
|
||||||
|
<button type="button" class="p-1 px-1.5 hover:bg-muted rounded text-xs flex flex-col items-center justify-center opacity-80 hover:opacity-100 transition-opacity whitespace-nowrap text-muted-foreground hover:text-foreground" onClick={() => editor()?.chain().focus().addRowAfter().run()} title="Add Row Below"><ArrowDownToLine size={14} /> +Row</button>
|
||||||
|
<button type="button" class="p-1 px-1.5 hover:bg-muted rounded text-xs flex flex-col items-center justify-center opacity-60 hover:opacity-100 transition-opacity whitespace-nowrap text-muted-foreground hover:text-foreground" onClick={() => editor()?.chain().focus().deleteRow().run()} title="Delete Row"><Trash2 size={14} /> -Row</button>
|
||||||
|
<div class="w-px bg-border my-1 mx-0.5 shrink-0" />
|
||||||
|
<button type="button" class="p-1 px-1.5 hover:bg-muted rounded text-xs flex flex-col items-center justify-center opacity-80 hover:opacity-100 transition-opacity whitespace-nowrap text-muted-foreground hover:text-foreground" onClick={() => editor()?.chain().focus().addColumnBefore().run()} title="Add Col Left"><ArrowLeftToLine size={14} /> +Col</button>
|
||||||
|
<button type="button" class="p-1 px-1.5 hover:bg-muted rounded text-xs flex flex-col items-center justify-center opacity-80 hover:opacity-100 transition-opacity whitespace-nowrap text-muted-foreground hover:text-foreground" onClick={() => editor()?.chain().focus().addColumnAfter().run()} title="Add Col Right"><ArrowRightToLine size={14} /> +Col</button>
|
||||||
|
<button type="button" class="p-1 px-1.5 hover:bg-muted rounded text-xs flex flex-col items-center justify-center opacity-60 hover:opacity-100 transition-opacity whitespace-nowrap text-muted-foreground hover:text-foreground" onClick={() => editor()?.chain().focus().deleteColumn().run()} title="Delete Col"><Trash2 size={14} /> -Col</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Note Preview Popover */}
|
||||||
|
<Show when={previewNote() && store.notes.find(n => n.id === previewNote()!.noteId)}>
|
||||||
|
{(note) => {
|
||||||
|
const pos = previewNote()!;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class="fixed z-[250] max-w-[280px] w-full bg-popover border border-border shadow-xl rounded-lg p-3 animate-in fade-in zoom-in-95 duration-200 pointer-events-auto"
|
||||||
|
style={{ left: `${Math.min(pos.x, window.innerWidth - 300)}px`, top: `${pos.y + 4}px` }}
|
||||||
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<h4 class="font-bold text-sm mb-1 truncate text-foreground">{note().title}</h4>
|
||||||
|
<p class="text-xs text-muted-foreground line-clamp-3 mb-3 break-words whitespace-pre-wrap">
|
||||||
|
{note().content?.replace(/<[^>]*>?/gm, '') || "Empty note"}
|
||||||
|
</p>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="text-xs px-2.5 py-1.5 rounded bg-muted hover:bg-muted/80 text-foreground font-medium transition-colors"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setPreviewNote(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="text-xs px-2.5 py-1.5 rounded bg-primary hover:bg-primary/90 text-primary-foreground font-medium transition-colors shadow-sm"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (props.onNoteOpen) {
|
||||||
|
props.onNoteOpen(note().id);
|
||||||
|
}
|
||||||
|
setPreviewNote(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Open Note
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Show>
|
||||||
|
|
||||||
<Show when={fullscreenImage()}>
|
<Show when={fullscreenImage()}>
|
||||||
<div
|
<div
|
||||||
class="fixed inset-0 z-[9999] p-4 bg-background/90 backdrop-blur-sm flex items-center justify-center animate-in fade-in duration-200 overflow-hidden"
|
class="fixed inset-0 z-[9999] p-4 bg-background/90 backdrop-blur-sm flex items-center justify-center animate-in fade-in duration-200 overflow-hidden"
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import { store } from "@/store";
|
||||||
|
import { render } from "solid-js/web";
|
||||||
|
import tippy, { type Instance as TippyInstance } from "tippy.js";
|
||||||
|
import { MentionList } from "@/components/MentionList";
|
||||||
|
|
||||||
|
export const userSuggestion = {
|
||||||
|
items: ({ query }: { query: string }) => {
|
||||||
|
return store.tagDefinitions
|
||||||
|
.filter(tag => tag.isUser && tag.name.toLowerCase().substring(1).startsWith(query.toLowerCase()))
|
||||||
|
.map(tag => ({
|
||||||
|
id: tag.name, // Use the tag name as ID for sharing logic
|
||||||
|
label: tag.name.substring(1),
|
||||||
|
type: 'user' as const
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
render: () => {
|
||||||
|
let componentDispose: (() => void) | undefined;
|
||||||
|
let popup: TippyInstance | undefined;
|
||||||
|
let container: HTMLDivElement | undefined;
|
||||||
|
let componentHandlers: { onKeyDown: (e: KeyboardEvent) => boolean } | undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
onStart: (props: any) => {
|
||||||
|
container = document.createElement("div");
|
||||||
|
|
||||||
|
componentDispose = render(
|
||||||
|
() => <MentionList
|
||||||
|
items={props.items}
|
||||||
|
command={props.command}
|
||||||
|
ref={(h) => componentHandlers = h}
|
||||||
|
/>,
|
||||||
|
container
|
||||||
|
);
|
||||||
|
|
||||||
|
popup = tippy("body", {
|
||||||
|
getReferenceClientRect: props.clientRect,
|
||||||
|
appendTo: () => props.editor.options.element || document.body,
|
||||||
|
content: container,
|
||||||
|
showOnCreate: true,
|
||||||
|
interactive: true,
|
||||||
|
trigger: "manual",
|
||||||
|
placement: "bottom-start",
|
||||||
|
})[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
onUpdate(props: any) {
|
||||||
|
if (componentDispose) componentDispose();
|
||||||
|
|
||||||
|
componentDispose = render(
|
||||||
|
() => <MentionList
|
||||||
|
items={props.items}
|
||||||
|
command={props.command}
|
||||||
|
ref={(h) => componentHandlers = h}
|
||||||
|
/>,
|
||||||
|
container!
|
||||||
|
);
|
||||||
|
|
||||||
|
if (props.clientRect) {
|
||||||
|
popup?.setProps({
|
||||||
|
getReferenceClientRect: props.clientRect,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onKeyDown(props: any) {
|
||||||
|
if (props.event.key === "Escape") {
|
||||||
|
popup?.hide();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return componentHandlers?.onKeyDown(props.event) ?? false;
|
||||||
|
},
|
||||||
|
|
||||||
|
onExit() {
|
||||||
|
if (popup && !popup.state.isDestroyed) {
|
||||||
|
popup.destroy();
|
||||||
|
}
|
||||||
|
popup = undefined;
|
||||||
|
if (componentDispose) {
|
||||||
|
componentDispose();
|
||||||
|
componentDispose = undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const noteSuggestion = {
|
||||||
|
char: '#',
|
||||||
|
items: ({ query }: { query: string }) => {
|
||||||
|
return store.notes
|
||||||
|
.filter(note => note.title.toLowerCase().includes(query.toLowerCase()))
|
||||||
|
.map(note => ({
|
||||||
|
id: note.id,
|
||||||
|
label: note.title,
|
||||||
|
sublabel: note.content?.substring(0, 50).replace(/<[^>]*>?/gm, ''),
|
||||||
|
type: 'note' as const
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
render: userSuggestion.render, // Share the same renderer
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user