diff --git a/package.json b/package.json index 58ba215..2c77225 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ ], "repository": "https://github.com/themashcodee/slack-blocks-to-jsx.git", "license": "MIT", - "version": "0.2.2", + "version": "0.3.0", "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", @@ -29,19 +29,21 @@ "release": "pnpm run build && changeset publish" }, "dependencies": { - "html-react-parser": "^4.2.2", + "@yozora/ast": "^2.3.2", + "@yozora/character": "^2.3.2", + "@yozora/core-tokenizer": "^2.3.2", + "@yozora/parser": "^2.3.2", "node-emoji": "^2.1.0", - "slack-markdown": "^0.3.0", "zustand": "^4.5.2" }, "peerDependencies": { - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "react": "^17 || ^18", + "react-dom": "^17 || ^18" }, "devDependencies": { "@changesets/cli": "^2.26.2", - "@types/react": "17.0.0", - "@types/react-dom": "17.0.0", + "@types/react": "^17", + "@types/react-dom": "^17", "autoprefixer": "^10.4.17", "cssnano": "^6.0.3", "postcss": "^8.4.33", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7bfb9aa..d4dff6a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,35 +2,39 @@ lockfileVersion: 5.4 specifiers: '@changesets/cli': ^2.26.2 - '@types/react': 17.0.0 - '@types/react-dom': 17.0.0 + '@types/react': ^17 + '@types/react-dom': ^17 + '@yozora/ast': ^2.3.2 + '@yozora/character': ^2.3.2 + '@yozora/core-tokenizer': ^2.3.2 + '@yozora/parser': ^2.3.2 autoprefixer: ^10.4.17 cssnano: ^6.0.3 - html-react-parser: ^4.2.2 node-emoji: ^2.1.0 postcss: ^8.4.33 postcss-cli: ^11.0.0 postcss-nesting: ^12.0.2 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - slack-markdown: ^0.3.0 + react: ^17 || ^18 + react-dom: ^17 || ^18 tailwindcss: ^3.4.1 tsup: ^7.2.0 typescript: ^5.2.2 zustand: ^4.5.2 dependencies: - html-react-parser: 4.2.2_react@18.3.1 + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + '@yozora/parser': 2.3.2 node-emoji: 2.1.0 react: 18.3.1 react-dom: 18.3.1_react@18.3.1 - slack-markdown: 0.3.0 - zustand: 4.5.2_lkaryubz2bolmitu7rok3kbwrm + zustand: 4.5.2_ugm3hist2nig7ypm6usfhfueka devDependencies: '@changesets/cli': 2.26.2 - '@types/react': 17.0.0 - '@types/react-dom': 17.0.0 + '@types/react': 17.0.80 + '@types/react-dom': 17.0.25 autoprefixer: 10.4.17_postcss@8.4.33 cssnano: 6.0.3_postcss@8.4.33 postcss: 8.4.33 @@ -571,25 +575,400 @@ packages: resolution: {integrity: sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==} dev: true - /@types/prop-types/15.7.7: - resolution: {integrity: sha512-FbtmBWCcSa2J4zL781Zf1p5YUBXQomPEcep9QZCfRfQgTxz3pJWiDFLebohZ9fFntX5ibzOkSsrJ0TEew8cAog==} + /@types/prop-types/15.7.12: + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - /@types/react-dom/17.0.0: - resolution: {integrity: sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g==} + /@types/react-dom/17.0.25: + resolution: {integrity: sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==} dependencies: - '@types/react': 17.0.0 + '@types/react': 17.0.80 dev: true - /@types/react/17.0.0: - resolution: {integrity: sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==} + /@types/react/17.0.80: + resolution: {integrity: sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==} dependencies: - '@types/prop-types': 15.7.7 - csstype: 3.1.2 + '@types/prop-types': 15.7.12 + '@types/scheduler': 0.16.8 + csstype: 3.1.3 + + /@types/scheduler/0.16.8: + resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} /@types/semver/7.5.3: resolution: {integrity: sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==} dev: true + /@yozora/ast-util/2.3.2: + resolution: {integrity: sha512-mW8r/gbgIfACarWfgLzBMU6mSJLmLYdPgAvHttGVm7Ucd+zWwZLmJ3JeNbUvcvAOpTQkE7s2WNVZTbeuO5z2nA==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + dev: false + + /@yozora/ast/2.3.2: + resolution: {integrity: sha512-YDA8TdWjufVYEqLwuONDLNwW5epBWlsNT2IQtats/Y8i4QOYYE56bAGaTVsGcq4TMF8qmkVUt9bsKuPYmHRJ3Q==} + engines: {node: '>= 16.0.0'} + dev: false + + /@yozora/character/2.3.2: + resolution: {integrity: sha512-jdzmFnMLKbFkCT/vVhQAk/kBY6Ot0ks84ws/En7pDRxiubXfrSGgkdID0SahfQCQbfqffo4tUit0O/Q3vQZPlw==} + engines: {node: '>= 16.0.0'} + dev: false + + /@yozora/core-parser/2.3.2: + resolution: {integrity: sha512-WVAjGa0UwKZctwqRJirUiznAZ9UoiPgSuKBhxqg10H6dV50+x0ScFAr9cQwC3mdT9UlHxG+0JaMnr0atyfsToA==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/ast-util': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + '@yozora/invariant': 2.3.2 + dev: false + + /@yozora/core-tokenizer/2.3.2: + resolution: {integrity: sha512-g7Xw0puubmet0UVMmX4mi+Q5/Pb4xkzlnqZF23sWOB2ug7MkTGR3xH0BBBNtQQs2gV8NdGuoyCMmG+bhsZIejQ==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + dev: false + + /@yozora/invariant/2.3.2: + resolution: {integrity: sha512-gcqjGyWUi5JGO2/Kka35G6Kn2ecDhPYhSgLKP+aHmqL/tUnSxbK+AITJu7eDa9Ro1RVetRfXIG2XFZbjg2xH5w==} + engines: {node: '>= 16.0.0'} + dev: false + + /@yozora/parser/2.3.2: + resolution: {integrity: sha512-DLnZ7OIj6Eau5+drE6mc9vprol7QWeouQmX8QXSOx6j8hxFfSFAfcBqN35frqdG9D7cZODL3GqBAbQId1p3P3A==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/core-parser': 2.3.2 + '@yozora/tokenizer-admonition': 2.3.2 + '@yozora/tokenizer-autolink': 2.3.2 + '@yozora/tokenizer-autolink-extension': 2.3.2 + '@yozora/tokenizer-blockquote': 2.3.2 + '@yozora/tokenizer-break': 2.3.2 + '@yozora/tokenizer-definition': 2.3.2 + '@yozora/tokenizer-delete': 2.3.2 + '@yozora/tokenizer-ecma-import': 2.3.2 + '@yozora/tokenizer-emphasis': 2.3.2 + '@yozora/tokenizer-fenced-code': 2.3.2 + '@yozora/tokenizer-footnote': 2.3.2 + '@yozora/tokenizer-footnote-definition': 2.3.2 + '@yozora/tokenizer-footnote-reference': 2.3.2 + '@yozora/tokenizer-heading': 2.3.2 + '@yozora/tokenizer-html-block': 2.3.2 + '@yozora/tokenizer-html-inline': 2.3.2 + '@yozora/tokenizer-image': 2.3.2 + '@yozora/tokenizer-image-reference': 2.3.2 + '@yozora/tokenizer-indented-code': 2.3.2 + '@yozora/tokenizer-inline-code': 2.3.2 + '@yozora/tokenizer-inline-math': 2.3.2 + '@yozora/tokenizer-link': 2.3.2 + '@yozora/tokenizer-link-reference': 2.3.2 + '@yozora/tokenizer-list': 2.3.2 + '@yozora/tokenizer-math': 2.3.2 + '@yozora/tokenizer-paragraph': 2.3.2 + '@yozora/tokenizer-setext-heading': 2.3.2 + '@yozora/tokenizer-table': 2.3.2 + '@yozora/tokenizer-text': 2.3.2 + '@yozora/tokenizer-thematic-break': 2.3.2 + dev: false + + /@yozora/tokenizer-admonition/2.3.2: + resolution: {integrity: sha512-ZUV6RVJy0n6tI32AbkiccI2mN3nH4EWykLdiCUyNFsCYNT+RJKRbZ0/0U9q7nuenJfbewurh7SxSnZrGZFwPZQ==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + '@yozora/tokenizer-fenced-block': 2.3.2 + dev: false + + /@yozora/tokenizer-autolink-extension/2.3.2: + resolution: {integrity: sha512-s4Q/wkO2nIMTkjcWH5SG+B8U0lLy0PhR8nzV00yBgrWTaTRcxpN16ecvS2Abrc+ezDnFmFB9pb1ZJ1jzturDyQ==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + '@yozora/tokenizer-autolink': 2.3.2 + dev: false + + /@yozora/tokenizer-autolink/2.3.2: + resolution: {integrity: sha512-WQnMrKTN20KSp0OW375ofrkOw1CbIJi+eP1d5kgxx+aFhbHS0qS9zykQ8roz7Ovi6v3fBWDLxLS4J8s96VlPxA==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-blockquote/2.3.2: + resolution: {integrity: sha512-j9vCvVWJ+JVW16PhBRh5DXHO1X3Y2sro6zht1nAiWn722sM9tb6Ha7FTcLl0aTs5uu+Hguo3e1XiihVZF9Ud4Q==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-break/2.3.2: + resolution: {integrity: sha512-vvkgNGsrGC3ggdXjmcH13h28z/rGtC/siEeuNtrZt+1I6VeD54nbN19KSSYYI1gERDU3zPADqBcrPcNRvXe3/g==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-definition/2.3.2: + resolution: {integrity: sha512-GxdSi6ykgDiSIhH95dv0K+kf2RLhYAtKb6df7n33Zy7aDQPEiUrLH+MWAME0WKKUmY7+sWlDLhtOWcbecFcZHw==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-delete/2.3.2: + resolution: {integrity: sha512-lUgv6Vr0D0/+LdxHsK8nWta/WiHTGXQB2fhXyE2g8uGoOvp6FX5oNss1UcSXpr/wMvWgT/39YtcDvEeIJnO6wQ==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-ecma-import/2.3.2: + resolution: {integrity: sha512-4TClGwDatfCR5BVAkHQy1YOWeR258KoCCOXFxEwngpIirfNr7u/U6oOFGwctLuQafvGAkRwvdjllwoxxVmLUQA==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-emphasis/2.3.2: + resolution: {integrity: sha512-RWfxj/rmglwKklpgTdz17MdoywsJs6RgHQn3gwJ21A2FL1VajbSEbMcV42BJPYFmzlpPU0ZNeeOIUu7QF3Z/hw==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-fenced-block/2.3.2: + resolution: {integrity: sha512-YELd4nBRmpBw5X+oGt2z24w9V2yhm4ltXpukFkiBRPlPHin5K3G8KXlLkF+5KLGhr0duTmJEztgZ5Bxo7Qa9+w==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-fenced-code/2.3.2: + resolution: {integrity: sha512-17zokbGr7pCdIZSQdx2wChfsPa91bzBXTDvWUgrGXSCn+XTCfnUgFqlNidnVtc32mwRvsSxdtf6NGHHb4E7xtw==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + '@yozora/tokenizer-fenced-block': 2.3.2 + dev: false + + /@yozora/tokenizer-footnote-definition/2.3.2: + resolution: {integrity: sha512-0qgxZSIJflbtKMJun+mrwMWT4RX8SPXRx/6G45XxrGzJDBV9UN6us7Q5hLetKKeLojdJBJ1WEpnRaEbOdF0Wqw==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-footnote-reference/2.3.2: + resolution: {integrity: sha512-6Zkqmo4pv4tGYMDgNwo92cR0UwCxaLdstyjLV9RP7HMBp43sSxmbDeGxnDPInaIdYpj6N95thTmkdkl3kc39OQ==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + '@yozora/tokenizer-footnote-definition': 2.3.2 + dev: false + + /@yozora/tokenizer-footnote/2.3.2: + resolution: {integrity: sha512-sA7t/9blPkO0jcggqPeirKGrLUNGu03uu7uePow/Bg04RgL14lHNjyDWDJJpbYgczINdNxb/xpePjCOpLzESjA==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + '@yozora/tokenizer-link': 2.3.2 + dev: false + + /@yozora/tokenizer-heading/2.3.2: + resolution: {integrity: sha512-Vf0EFKYTKyZ2kdpMSjhbfbwjJrjCITVvCmWnfAhp3rUgICe30CUQuvsOFco2B9JHnpUdqc0wWHdUs4fvt6lTxQ==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-html-block/2.3.2: + resolution: {integrity: sha512-klLFjO0kRWwNZHLcmLJle8StIqAysstvgnYg3evYmd7fnVv8gnfROCseoOUJ0Jh1Fr0Uz3iQz4Xx0D11Q8uiWQ==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-html-inline/2.3.2: + resolution: {integrity: sha512-SbQlRQQ7cM6TUqiwdMJSZdsw5p59T8rpgbvzlrqcfzI1Jgo9zlw0pFWYjhDLowIdX68LldYfQLbqYKmlJLaxKA==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + '@yozora/tokenizer-html-block': 2.3.2 + dev: false + + /@yozora/tokenizer-image-reference/2.3.2: + resolution: {integrity: sha512-Suw9muo/eDpj+deKaoKSECDCzuf+Xr/10s0lm/GY/LYbJnOcnQtxD4nasF2W6fQcQJcbJcs7JAd3n3eI/0ZRFg==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + '@yozora/tokenizer-image': 2.3.2 + '@yozora/tokenizer-link': 2.3.2 + '@yozora/tokenizer-link-reference': 2.3.2 + dev: false + + /@yozora/tokenizer-image/2.3.2: + resolution: {integrity: sha512-2v4bkHeOoZYOuFCsmnoUY/4oHfoPsIWtaFuTSj3iuqYSgNJszm2ghgNESz1q8oQZPC6XL00ilGaEsaCeJuhd9w==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + '@yozora/tokenizer-link': 2.3.2 + dev: false + + /@yozora/tokenizer-indented-code/2.3.2: + resolution: {integrity: sha512-iT8JBMPbghy/ITrU+Ms8stzfDFmVIGv3Kb04yVUiUrtIO1fQ0yMNWsCXwMQ5C4d+yW1DHKtMJf7RHOD5ppjWKA==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-inline-code/2.3.2: + resolution: {integrity: sha512-Pf5c7qVkOuRwGwLh1u6U+DIEH2J0SQooCrG2JnAS7RJCscKTqrPU+UNeALDqapThsF7SWFYVEQbBeTuLkarZlA==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-inline-math/2.3.2: + resolution: {integrity: sha512-rogTL8dQeDEmVZwGbzCtYn9dPb6dr52eb7ZxXHZouhRJxKUOV+CQ+90hCj8o3M5lOX0Qx1JLtrMo+uVnnYFaBg==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-link-reference/2.3.2: + resolution: {integrity: sha512-sgHGYSapxodJRLwEJoHDVbqhLXWT5lLKSsMj1rVPUEmIfE/vl06OUOuMD+62csU7Kkj9IbbGmBu6zMPc4uuApw==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + '@yozora/tokenizer-link': 2.3.2 + dev: false + + /@yozora/tokenizer-link/2.3.2: + resolution: {integrity: sha512-LlqZeB0O9/oUjHWUYW3wefDPXYu2C9NXQ8M1fWgCrc8whSN7vdh+/hU1AWKVF6mIiRXAnKM7o26WQk+0Fgc3uA==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-list/2.3.2: + resolution: {integrity: sha512-6KaOC84c7BCwJBIWGiGZUa2nT0xCPv0E7vfZyaxiReCeW+IMptxt0ZrrX+iWIFvwKig8x0X0nbJHf/gaxLYV+w==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-math/2.3.2: + resolution: {integrity: sha512-sjPYFxiKqlkxpL92nYqbruwGynixgjk2lbOmTxEfTw4Qw+rrTUKFrAN3SyhNHkwWMZ7lKWUAknoyipEBSsWRpg==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + '@yozora/tokenizer-fenced-block': 2.3.2 + dev: false + + /@yozora/tokenizer-paragraph/2.3.2: + resolution: {integrity: sha512-3l6iH71WeOwzVLZ+hujU9b2YoeMEfX8jD4GzVBo8dgwlg1xNrdLqdtfroFMuibB0Cs+bRQGbsYxXlzyyMi3vWg==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-setext-heading/2.3.2: + resolution: {integrity: sha512-knI5oOoUQH7LoIHlqm3YjndAxNMx9okY82fGCZunpI2tSbZyLKqJmGomdmodzgEjPqv483ZtKt5T6keupRf23A==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-table/2.3.2: + resolution: {integrity: sha512-pRcxpGEjMETi5KT33AnjcHN6oNkdXN1/2x63a9MZqUsweLpu4cRNB/ixwrfxCr9WJH6nEzmZGFKW0HOqwvbm7A==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-text/2.3.2: + resolution: {integrity: sha512-s3zJ5bdebrdgZDtdoVAkUZ0LYOfA4nIbq3gyUMOZq1ygNDxjTzMcgGr6mzMw8OzYkrWQT2XyN17YWR+zsgUw4w==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + + /@yozora/tokenizer-thematic-break/2.3.2: + resolution: {integrity: sha512-taYZ9iMJ7f3c0QEEkskf+84T0Ri1m+rxnJquZxIYRDHybjgpmquqyYPF4jp9ZozFShw5mxzN03Qqs1ryMiXm/A==} + engines: {node: '>= 16.0.0'} + dependencies: + '@yozora/ast': 2.3.2 + '@yozora/character': 2.3.2 + '@yozora/core-tokenizer': 2.3.2 + dev: false + /ansi-colors/4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -1037,8 +1416,8 @@ packages: css-tree: 2.2.1 dev: true - /csstype/3.1.2: - resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + /csstype/3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} /csv-generate/3.4.3: resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} @@ -1142,15 +1521,18 @@ packages: domelementtype: 2.3.0 domhandler: 5.0.3 entities: 4.5.0 + dev: true /domelementtype/2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: true /domhandler/5.0.3: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} dependencies: domelementtype: 2.3.0 + dev: true /domutils/3.1.0: resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} @@ -1158,6 +1540,7 @@ packages: dom-serializer: 2.0.0 domelementtype: 2.3.0 domhandler: 5.0.3 + dev: true /electron-to-chromium/1.4.646: resolution: {integrity: sha512-vThkQ0JuF45qT/20KbRgM56lV7IuGt7SjhawQ719PDHzhP84KAO1WJoaxgCoAffKHK47FmVKP1Fqizx7CwK1SA==} @@ -1182,6 +1565,7 @@ packages: /entities/4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + dev: true /error-ex/1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -1293,10 +1677,6 @@ packages: engines: {node: '>=6'} dev: true - /escape-html/1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - dev: false - /escape-string-regexp/1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -1616,34 +1996,6 @@ packages: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true - /html-dom-parser/4.0.0: - resolution: {integrity: sha512-TUa3wIwi80f5NF8CVWzkopBVqVAtlawUzJoLwVLHns0XSJGynss4jiY0mTWpiDOsuyw+afP+ujjMgRh9CoZcXw==} - dependencies: - domhandler: 5.0.3 - htmlparser2: 9.0.0 - dev: false - - /html-react-parser/4.2.2_react@18.3.1: - resolution: {integrity: sha512-lh0wEGISnFZEAmvQqK4xc0duFMUh/m9YYyAhFursWxdtNv+hCZge0kj1y4wep6qPB5Zm33L+2/P6TcGWAJJbjA==} - peerDependencies: - react: 0.14 || 15 || 16 || 17 || 18 - dependencies: - domhandler: 5.0.3 - html-dom-parser: 4.0.0 - react: 18.3.1 - react-property: 2.0.0 - style-to-js: 1.1.4 - dev: false - - /htmlparser2/9.0.0: - resolution: {integrity: sha512-uxbSI98wmFT/G4P2zXx4OVx04qWUmyFPrD2/CNepa2Zo3GPNaCaaxElDgwUrwYWkK1nr9fft0Ya8dws8coDLLQ==} - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - domutils: 3.1.0 - entities: 4.5.0 - dev: false - /human-id/1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} dev: true @@ -1681,10 +2033,6 @@ packages: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true - /inline-style-parser/0.1.1: - resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} - dev: false - /internal-slot/1.0.5: resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} engines: {node: '>= 0.4'} @@ -1965,10 +2313,6 @@ packages: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} dev: true - /lodash/4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: false - /loose-envify/1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -2090,12 +2434,6 @@ packages: hasBin: true dev: true - /node-emoji/1.11.0: - resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} - dependencies: - lodash: 4.17.21 - dev: false - /node-emoji/2.1.0: resolution: {integrity: sha512-tcsBm9C6FmPN5Wo7OjFi9lgMyJjvkAeirmjR/ax8Ttfqy4N8PoFic26uqFTIgayHPNI5FH4ltUvfh9kHzwcK9A==} dependencies: @@ -2771,10 +3109,6 @@ packages: scheduler: 0.23.2 dev: false - /react-property/2.0.0: - resolution: {integrity: sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==} - dev: false - /react/18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -2977,12 +3311,6 @@ packages: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true - /simple-markdown/0.7.3: - resolution: {integrity: sha512-uGXIc13NGpqfPeFJIt/7SHHxd6HekEJYtsdoCM06mEBPL9fQH/pSD7LRM6PZ7CKchpSvxKL4tvwMamqAaNDAyg==} - dependencies: - '@types/react': 17.0.0 - dev: false - /skin-tone/2.0.0: resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} engines: {node: '>=8'} @@ -2990,14 +3318,6 @@ packages: unicode-emoji-modifier-base: 1.0.0 dev: false - /slack-markdown/0.3.0: - resolution: {integrity: sha512-LNOwBXZ1xzOYNo9ZU0gPXZN0rqlAPOiVQMTjz+rMq1ZTGg655UNHDRos+NqSU20BC+SV7VeA/3lLOs5DG+UEvA==} - dependencies: - escape-html: 1.0.3 - node-emoji: 1.11.0 - simple-markdown: 0.7.3 - dev: false - /slash/3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -3130,18 +3450,6 @@ packages: min-indent: 1.0.1 dev: true - /style-to-js/1.1.4: - resolution: {integrity: sha512-zEeU3vy9xL/hdLBFmzqjhm+2vJ1Y35V0ctDeB2sddsvN1856OdMZUCOOfKUn3nOjjEKr6uLhOnY4CrX6gLDRrA==} - dependencies: - style-to-object: 0.4.2 - dev: false - - /style-to-object/0.4.2: - resolution: {integrity: sha512-1JGpfPB3lo42ZX8cuPrheZbfQ6kqPPnPHlKMyeRYtfKD+0jG+QsXgXN57O/dvJlzlB2elI6dGmrPnl5VPQFPaA==} - dependencies: - inline-style-parser: 0.1.1 - dev: false - /stylehacks/6.0.2_postcss@8.4.33: resolution: {integrity: sha512-00zvJGnCu64EpMjX8b5iCZ3us2Ptyw8+toEkb92VdmkEaRaSGBNKAoK6aWZckhXxmQP8zWiTaFaiMGIU8Ve8sg==} engines: {node: ^14 || ^16 || >=18.0} @@ -3619,7 +3927,7 @@ packages: engines: {node: '>=10'} dev: true - /zustand/4.5.2_lkaryubz2bolmitu7rok3kbwrm: + /zustand/4.5.2_ugm3hist2nig7ypm6usfhfueka: resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==} engines: {node: '>=12.7.0'} peerDependencies: @@ -3634,7 +3942,7 @@ packages: react: optional: true dependencies: - '@types/react': 17.0.0 + '@types/react': 17.0.80 react: 18.3.1 use-sync-external-store: 1.2.0_react@18.3.1 dev: false diff --git a/src/components/blocks/rich_text/rich_text_section_broadcast.tsx b/src/components/blocks/rich_text/rich_text_section_broadcast.tsx index b74b659..848eeb1 100644 --- a/src/components/blocks/rich_text/rich_text_section_broadcast.tsx +++ b/src/components/blocks/rich_text/rich_text_section_broadcast.tsx @@ -7,10 +7,13 @@ export const RichTextSectionBroadcast = (props: Props) => { const { range, style } = props; const { hooks } = useGlobalData(); + if (range === "channel" && hooks.atChannel) return <>{hooks.atChannel()}; + if (range === "everyone" && hooks.atEveryone) return <>{hooks.atEveryone()}; + if (range === "here" && hooks.atHere) return <>{hooks.atHere()}; + return ( -
{ ${style?.bold ? "font-medium" : ""} `} > - {range === "channel" && (hooks.atChannel ? hooks.atChannel() : `@${range}`)} - {range === "everyone" && (hooks.atEveryone ? hooks.atEveryone() : `@${range}`)} - {range === "here" && (hooks.atHere ? hooks.atHere() : `@${range}`)} -
+ @{range} + ); }; diff --git a/src/components/blocks/rich_text/rich_text_section_channel.tsx b/src/components/blocks/rich_text/rich_text_section_channel.tsx index 5bf4cc6..3928034 100644 --- a/src/components/blocks/rich_text/rich_text_section_channel.tsx +++ b/src/components/blocks/rich_text/rich_text_section_channel.tsx @@ -7,14 +7,15 @@ export const RichTextSectionChannel = (props: Props) => { const { channel_id, style } = props; const { channels, hooks } = useGlobalData(); - const channel = channels.find((u) => u.id === channel_id); + const channel = channels.find((u) => u.id === channel_id || u.name === channel_id); const label = channel?.name || channel_id; + if (hooks.channel) return <>{hooks.channel(channel || { id: channel_id, name: label })}; + return ( -
{ ${style?.bold ? "font-medium" : ""} `} > - {hooks.user - ? hooks.user( - channel || { - id: channel_id, - name: label, - }, - ) - : `#${label}`} -
+ #{label} + ); }; diff --git a/src/components/blocks/rich_text/rich_text_section_user.tsx b/src/components/blocks/rich_text/rich_text_section_user.tsx index f989ff5..607e6ae 100644 --- a/src/components/blocks/rich_text/rich_text_section_user.tsx +++ b/src/components/blocks/rich_text/rich_text_section_user.tsx @@ -7,14 +7,15 @@ export const RichTextSectionUser = (props: Props) => { const { user_id, style } = props; const { users, hooks } = useGlobalData(); - const user = users.find((u) => u.id === user_id); + const user = users.find((u) => u.id === user_id || u.name === user_id); const label = user?.name || user_id; + if (hooks.user) return <>{hooks.user(user || { id: user_id, name: label })}; + return ( -
{ ${style?.bold ? "font-medium" : ""} `} > - {hooks.user - ? hooks.user( - user || { - id: user_id, - name: label, - }, - ) - : `@${label}`} -
+ @{label} + ); }; diff --git a/src/components/blocks/rich_text/rich_text_section_user_group.tsx b/src/components/blocks/rich_text/rich_text_section_user_group.tsx index 59308be..3425579 100644 --- a/src/components/blocks/rich_text/rich_text_section_user_group.tsx +++ b/src/components/blocks/rich_text/rich_text_section_user_group.tsx @@ -7,14 +7,15 @@ export const RichTextSectionUserGroup = (props: Props) => { const { usergroup_id, style } = props; const { user_groups, hooks } = useGlobalData(); - const group = user_groups.find((u) => u.id === usergroup_id); + const group = user_groups.find((u) => u.id === usergroup_id || u.name === usergroup_id); const label = group?.name || usergroup_id; + if (hooks.usergroup) return <>{hooks.usergroup(group || { id: usergroup_id, name: label })}; + return ( -
{ ${style?.bold ? "font-medium" : ""} `} > - {hooks.usergroup ? hooks.usergroup(usergroup_id) : `@${label}`} -
+ @{label} + ); }; diff --git a/src/components/composition_objects/text_object.tsx b/src/components/composition_objects/text_object.tsx index c80fd99..b8d806e 100644 --- a/src/components/composition_objects/text_object.tsx +++ b/src/components/composition_objects/text_object.tsx @@ -1,6 +1,6 @@ import { useGlobalData } from "../../store"; import type { TextObject as TextObjectType } from "../../types"; -import { parseEmojis, slack_text_to_jsx } from "../../utils"; +import { markdown_parser, parseEmojis } from "../../utils"; type TextObjectProps = { data: TextObjectType; @@ -23,13 +23,13 @@ export const TextObject = (props: TextObjectProps) => { if (type === "plain_text") return (
- {slack_text_to_jsx(emoji_parsed, { markdown: false, users, channels, hooks })} + {markdown_parser(emoji_parsed, { markdown: false, users, channels, hooks })}
); return (
- {slack_text_to_jsx(emoji_parsed, { markdown: true, users, channels, hooks })} + {markdown_parser(emoji_parsed, { markdown: true, users, channels, hooks })}
); }; diff --git a/src/message.tsx b/src/message.tsx index 605c6d4..49e4f38 100644 --- a/src/message.tsx +++ b/src/message.tsx @@ -48,11 +48,12 @@ export const Message = (props: Props) => { withoutWrapper = false, } = props; - const { setChannels, setUsers, setHooks } = useGlobalData(); + const { setChannels, setUsers, setHooks, setUserGroups } = useGlobalData(); useEffect(() => { if (data?.users) setUsers(data.users); if (data?.channels) setChannels(data.channels); + if (data?.user_groups) setUserGroups(data.user_groups); if (hooks) setHooks(hooks); }, [data, hooks]); diff --git a/src/store/useGlobalData.ts b/src/store/useGlobalData.ts index f035437..1c5f216 100644 --- a/src/store/useGlobalData.ts +++ b/src/store/useGlobalData.ts @@ -11,13 +11,18 @@ type Channel = { name: string; }; +type UserGroup = { + id: string; + name: string; +}; + type Hooks = { user?: (data: User) => ReactNode; channel?: (data: Channel) => ReactNode; + usergroup?: (data: UserGroup) => ReactNode; atChannel?: () => ReactNode; atEveryone?: () => ReactNode; atHere?: () => ReactNode; - usergroup?: (id: string) => ReactNode; emoji?: (emoji_text: string) => ReactNode; date?: (data: { timestamp: string; @@ -30,10 +35,11 @@ type Hooks = { type Data = { users: User[]; channels: Channel[]; - user_groups: Channel[]; + user_groups: UserGroup[]; hooks: Hooks; setUsers: (users: User[]) => void; setChannels: (channels: Channel[]) => void; + setUserGroups: (channels: UserGroup[]) => void; setHooks: (hooks: Hooks) => void; }; @@ -46,11 +52,13 @@ const useBearStore = create((set) => ({ hooks: {}, setUsers: (users) => set({ users }), setChannels: (channels) => set({ channels }), + setUserGroups: (user_groups) => set({ user_groups }), setHooks: (hooks) => set({ hooks }), })); export const useGlobalData = () => { - const { users, channels, user_groups, hooks, setChannels, setUsers, setHooks } = useBearStore(); + const { users, channels, user_groups, hooks, setChannels, setUsers, setHooks, setUserGroups } = + useBearStore(); return { users, @@ -60,5 +68,6 @@ export const useGlobalData = () => { setChannels, setUsers, setHooks, + setUserGroups, }; }; diff --git a/src/style.css b/src/style.css index 277b467..d586de0 100644 --- a/src/style.css +++ b/src/style.css @@ -269,6 +269,10 @@ /* #endregion */ /* #region CUSTOM STYLES */ + & .slack_code_inline { + @apply inline-block px-1 text-xs leading-[1.5] whitespace-pre-wrap break-words rounded-[3px] border border-black-primary/[0.13] bg-black-primary/[0.04] text-red-primary; + } + & .slack_code { @apply block p-2 text-xs leading-[1.5] whitespace-pre-wrap break-words rounded-[3px] border border-black-primary/[0.13] bg-black-primary/[0.04] w-full my-1; } @@ -322,21 +326,18 @@ color: rgb(18, 100, 163); background: rgb(29, 155, 209, 0.1); user-select: none; - cursor: pointer; } & .slack_channel { color: rgb(18, 100, 163); background: rgb(29, 155, 209, 0.1); user-select: none; - cursor: pointer; } & .slack_user_group { color: rgb(18, 100, 163); background: rgb(29, 155, 209, 0.1); user-select: none; - cursor: pointer; } & .slack_broadcast { @@ -350,7 +351,11 @@ } & .slack_blocks_to_jsx__line_break_not_first { display: block; - height: 8px; + height: 3px; + } + + & .slack_link { + color: rgb(18, 100, 163); } /* #endregion */ diff --git a/src/utils/index.ts b/src/utils/index.ts index c3ce864..00854dd 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,4 @@ export * from "./date"; export * from "./emojis"; -export * from "./slack_text_to_jsx"; +export * from "./markdown_parser"; export * from "./is_accessory_stacked"; diff --git a/src/utils/markdown_parser/elements/blockquote.tsx b/src/utils/markdown_parser/elements/blockquote.tsx new file mode 100644 index 0000000..392c229 --- /dev/null +++ b/src/utils/markdown_parser/elements/blockquote.tsx @@ -0,0 +1,18 @@ +import { BlockQuoteElement } from "../types"; +import { Paragraph } from "./paragraph"; + +type Props = { + element: BlockQuoteElement; +}; + +export const Blockquote = (props: Props) => { + const { element } = props; + + return ( + <> + {element.children.map((para) => { + return ; + })} + + ); +}; diff --git a/src/utils/markdown_parser/elements/code.tsx b/src/utils/markdown_parser/elements/code.tsx new file mode 100644 index 0000000..8876bf5 --- /dev/null +++ b/src/utils/markdown_parser/elements/code.tsx @@ -0,0 +1,11 @@ +import { CodeElement } from "../types"; + +type Props = { + element: CodeElement; +}; + +export const Code = (props: Props) => { + const { element } = props; + + return {element.value}; +}; diff --git a/src/utils/markdown_parser/elements/index.ts b/src/utils/markdown_parser/elements/index.ts new file mode 100644 index 0000000..95184d9 --- /dev/null +++ b/src/utils/markdown_parser/elements/index.ts @@ -0,0 +1,3 @@ +export * from "./paragraph"; +export * from "./blockquote"; +export * from "./code"; diff --git a/src/utils/markdown_parser/elements/paragraph.tsx b/src/utils/markdown_parser/elements/paragraph.tsx new file mode 100644 index 0000000..02161f2 --- /dev/null +++ b/src/utils/markdown_parser/elements/paragraph.tsx @@ -0,0 +1,44 @@ +import { + Delete, + Emphasis, + InlineCode, + Link, + SlackBroadcast, + SlackChannelMention, + SlackUserGroupMention, + SlackUserMention, + Strong, + Text, +} from "../sub_elements"; +import { ParagraphElement } from "../types"; + +type Props = { + element: ParagraphElement; +}; + +export const Paragraph = (props: Props) => { + const { element } = props; + + return ( + <> + {element.children.map((subelement, i) => { + if (subelement.type === "text") return ; + if (subelement.type === "emphasis") return ; + if (subelement.type === "inlineCode") return ; + if (subelement.type === "delete") return ; + if (subelement.type === "strong") return ; + if (subelement.type === "link") return ; + if (subelement.type === "slack_user_mention") + return ; + if (subelement.type === "slack_channel_mention") + return ; + if (subelement.type === "slack_user_group_mention") + return ; + if (subelement.type === "slack_broadcast") + return ; + + return null; + })} + + ); +}; diff --git a/src/utils/markdown_parser/index.ts b/src/utils/markdown_parser/index.ts new file mode 100644 index 0000000..5c58fd8 --- /dev/null +++ b/src/utils/markdown_parser/index.ts @@ -0,0 +1 @@ +export * from "./parser"; diff --git a/src/utils/markdown_parser/parser.tsx b/src/utils/markdown_parser/parser.tsx new file mode 100644 index 0000000..71b2d67 --- /dev/null +++ b/src/utils/markdown_parser/parser.tsx @@ -0,0 +1,68 @@ +import YozoraParser from "@yozora/parser"; +import { MarkdownElement } from "./types"; +import { Blockquote, Paragraph, Code } from "./elements"; +import { GlobalStore } from "../../store"; +import { + SlackUserMentionTokenizer, + SlackChannelMentionTokenizer, + SlackUserGroupMentionTokenizer, + SlackBroadcastTokenizer, +} from "./tokenizers"; +import { ReactNode } from "react"; + +const parser = new YozoraParser() + .unmountTokenizer("@yozora/tokenizer-list") + .useTokenizer(new SlackUserMentionTokenizer()) + .useTokenizer(new SlackChannelMentionTokenizer()) + .useTokenizer(new SlackUserGroupMentionTokenizer()) + .useTokenizer(new SlackBroadcastTokenizer()); + +type Options = { + markdown: boolean; + users: GlobalStore["users"]; + channels: GlobalStore["channels"]; + hooks: GlobalStore["hooks"]; +}; + +// #region HELPER CODE +// TODO: HANDLE DATE PARSING ...(hooks.date && { date: hooks.date }), +// #endregion + +export const markdown_parser = (markdown: string, options: Options): ReactNode => { + if (!markdown) return null; + + let text_string = markdown; + + // TRANSFORM ``` TO MAKE IT A CODE BLOCK INSTEAD OF INLINE CODE BLOCK + text_string = text_string.replace(/```/g, `\n\`\`\`\n`); + // REPLACE SINGLE asterisk WITH DOUBLE asterisk + text_string = text_string.replace(/(? to [label](link) + text_string = text_string.replace(/<([^|>]+)\|([^>]+)>/g, "[$2]($1)"); + // REPLACE \n\n WITH '[[DOUBLE_LINE_BREAK]]' to prevent @yozora/parser to eat it + text_string = text_string.replace(/\n\n/g, "[[DOUBLE_LINE_BREAK]]"); + // REPLACE with @here + text_string = text_string.replace(//g, "@here"); + // REPLACE with @everyone + text_string = text_string.replace(//g, "@everyone"); + // REPLACE with @channel + text_string = text_string.replace(//g, "@channel"); + + const parsed_data = parser.parse(text_string); + + const elements = parsed_data.children as unknown as MarkdownElement[]; + + return ( +
+ {elements.map((element, i) => { + if (element.type === "paragraph") return ; + if (element.type === "blockquote") return
; + if (element.type === "code") return ; + + return null; + })} +
+ ); +}; diff --git a/src/utils/markdown_parser/sub_elements/delete.tsx b/src/utils/markdown_parser/sub_elements/delete.tsx new file mode 100644 index 0000000..4a79cbf --- /dev/null +++ b/src/utils/markdown_parser/sub_elements/delete.tsx @@ -0,0 +1,18 @@ +import { DeleteSubElement } from "../types"; +import { Text } from "./text"; + +type Props = { + element: DeleteSubElement; +}; + +export const Delete = (props: Props) => { + const { element } = props; + + return ( + + {element.children.map((child, i) => { + return ; + })} + + ); +}; diff --git a/src/utils/markdown_parser/sub_elements/emphasis.tsx b/src/utils/markdown_parser/sub_elements/emphasis.tsx new file mode 100644 index 0000000..18d11c3 --- /dev/null +++ b/src/utils/markdown_parser/sub_elements/emphasis.tsx @@ -0,0 +1,32 @@ +import { EmphasisSubElement } from "../types"; +import { Delete } from "./delete"; +import { SlackBroadcast } from "./slack_broadcast"; +import { SlackChannelMention } from "./slack_channel_mention"; +import { SlackUserGroupMention } from "./slack_user_group_mention"; +import { SlackUserMention } from "./slack_user_mention"; +import { Text } from "./text"; + +type Props = { + element: EmphasisSubElement; +}; + +export const Emphasis = (props: Props) => { + const { element } = props; + + return ( + + {element.children.map((child, i) => { + if (child.type === "delete") return ; + if (child.type === "slack_user_mention") + return ; + if (child.type === "slack_channel_mention") + return ; + if (child.type === "slack_user_group_mention") + return ; + if (child.type === "slack_broadcast") return ; + + return ; + })} + + ); +}; diff --git a/src/utils/markdown_parser/sub_elements/index.ts b/src/utils/markdown_parser/sub_elements/index.ts new file mode 100644 index 0000000..2f9006f --- /dev/null +++ b/src/utils/markdown_parser/sub_elements/index.ts @@ -0,0 +1,10 @@ +export * from "./emphasis"; +export * from "./text"; +export * from "./inline_code"; +export * from "./strong"; +export * from "./delete"; +export * from "./link"; +export * from "./slack_user_mention"; +export * from "./slack_channel_mention"; +export * from "./slack_user_group_mention"; +export * from "./slack_broadcast"; diff --git a/src/utils/markdown_parser/sub_elements/inline_code.tsx b/src/utils/markdown_parser/sub_elements/inline_code.tsx new file mode 100644 index 0000000..d74da6c --- /dev/null +++ b/src/utils/markdown_parser/sub_elements/inline_code.tsx @@ -0,0 +1,11 @@ +import { InlineCodeSubElement } from "../types"; + +type Props = { + element: InlineCodeSubElement; +}; + +export const InlineCode = (props: Props) => { + const { element } = props; + + return {element.value}; +}; diff --git a/src/utils/markdown_parser/sub_elements/link.tsx b/src/utils/markdown_parser/sub_elements/link.tsx new file mode 100644 index 0000000..cd2d9f9 --- /dev/null +++ b/src/utils/markdown_parser/sub_elements/link.tsx @@ -0,0 +1,24 @@ +import { LinkSubElement } from "../types"; +import { Delete } from "./delete"; +import { Emphasis } from "./emphasis"; +import { Strong } from "./strong"; +import { Text } from "./text"; + +type Props = { + element: LinkSubElement; +}; + +export const Link = (props: Props) => { + const { element } = props; + + return ( + + {element.children.map((child, i) => { + if (child.type === "delete") return ; + if (child.type === "emphasis") return ; + if (child.type === "strong") return ; + return ; + })} + + ); +}; diff --git a/src/utils/markdown_parser/sub_elements/slack_broadcast.tsx b/src/utils/markdown_parser/sub_elements/slack_broadcast.tsx new file mode 100644 index 0000000..6d96880 --- /dev/null +++ b/src/utils/markdown_parser/sub_elements/slack_broadcast.tsx @@ -0,0 +1,17 @@ +import { useGlobalData } from "../../../store"; +import { SlackBroadcastSubElement } from "../types"; + +type Props = { + element: SlackBroadcastSubElement; +}; + +export const SlackBroadcast = (props: Props) => { + const { element } = props; + const { hooks } = useGlobalData(); + + if (element.value === "here" && hooks.atHere) return <>{hooks.atHere()}; + if (element.value === "everyone" && hooks.atEveryone) return <>{hooks.atEveryone()}; + if (element.value === "channel" && hooks.atChannel) return <>{hooks.atChannel()}; + + return @{element.value}; +}; diff --git a/src/utils/markdown_parser/sub_elements/slack_channel_mention.tsx b/src/utils/markdown_parser/sub_elements/slack_channel_mention.tsx new file mode 100644 index 0000000..caff8db --- /dev/null +++ b/src/utils/markdown_parser/sub_elements/slack_channel_mention.tsx @@ -0,0 +1,23 @@ +import { useGlobalData } from "../../../store"; +import { SlackChannelMentionSubElement } from "../types"; + +type Props = { + element: SlackChannelMentionSubElement; +}; + +export const SlackChannelMention = (props: Props) => { + const { element } = props; + const { hooks, channels } = useGlobalData(); + + const channel_id = element.value; + const channel = channels.find((u) => u.id === channel_id || u.name === channel_id); + const label = channel?.name || channel_id; + + if (hooks.channel) return <>{hooks.channel(channel || { id: channel_id, name: label })}; + + return ( + + #{label} + + ); +}; diff --git a/src/utils/markdown_parser/sub_elements/slack_user_group_mention.tsx b/src/utils/markdown_parser/sub_elements/slack_user_group_mention.tsx new file mode 100644 index 0000000..4c8a61a --- /dev/null +++ b/src/utils/markdown_parser/sub_elements/slack_user_group_mention.tsx @@ -0,0 +1,23 @@ +import { useGlobalData } from "../../../store"; +import { SlackUserGroupMentionSubElement } from "../types"; + +type Props = { + element: SlackUserGroupMentionSubElement; +}; + +export const SlackUserGroupMention = (props: Props) => { + const { element } = props; + const { hooks, user_groups } = useGlobalData(); + + const group_id = element.value; + const group = user_groups.find((u) => u.id === group_id || u.name === group_id); + const label = group?.name || group_id; + + if (hooks.usergroup) return <>{hooks.usergroup(group || { id: group_id, name: label })}; + + return ( + + @{label} + + ); +}; diff --git a/src/utils/markdown_parser/sub_elements/slack_user_mention.tsx b/src/utils/markdown_parser/sub_elements/slack_user_mention.tsx new file mode 100644 index 0000000..c02705f --- /dev/null +++ b/src/utils/markdown_parser/sub_elements/slack_user_mention.tsx @@ -0,0 +1,22 @@ +import { useGlobalData } from "../../../store"; +import { SlackUserMentionSubElement } from "../types"; + +type Props = { + element: SlackUserMentionSubElement; +}; + +export const SlackUserMention = (props: Props) => { + const { element } = props; + const { hooks, users } = useGlobalData(); + + const user_id = element.value; + const user = users.find((u) => u.id === user_id || u.name === user_id); + if (hooks.user) return <>{hooks.user(user || { id: user_id, name: user_id })}; + const label = user?.name || user_id; + + return ( + + @{label} + + ); +}; diff --git a/src/utils/markdown_parser/sub_elements/strong.tsx b/src/utils/markdown_parser/sub_elements/strong.tsx new file mode 100644 index 0000000..5ea6c41 --- /dev/null +++ b/src/utils/markdown_parser/sub_elements/strong.tsx @@ -0,0 +1,32 @@ +import { StrongSubElement } from "../types"; +import { Delete } from "./delete"; +import { SlackBroadcast } from "./slack_broadcast"; +import { SlackChannelMention } from "./slack_channel_mention"; +import { SlackUserGroupMention } from "./slack_user_group_mention"; +import { SlackUserMention } from "./slack_user_mention"; +import { Text } from "./text"; + +type Props = { + element: StrongSubElement; +}; + +export const Strong = (props: Props) => { + const { element } = props; + + return ( + + {element.children.map((child, i) => { + if (child.type === "delete") return ; + if (child.type === "slack_user_mention") + return ; + if (child.type === "slack_channel_mention") + return ; + if (child.type === "slack_user_group_mention") + return ; + if (child.type === "slack_broadcast") return ; + + return ; + })} + + ); +}; diff --git a/src/utils/markdown_parser/sub_elements/text.tsx b/src/utils/markdown_parser/sub_elements/text.tsx new file mode 100644 index 0000000..1441613 --- /dev/null +++ b/src/utils/markdown_parser/sub_elements/text.tsx @@ -0,0 +1,22 @@ +import { TextSubElement } from "../types"; + +type Props = { + element: TextSubElement; +}; + +export const Text = (props: Props) => { + const { element } = props; + + return ( + + {element.value.split("[[DOUBLE_LINE_BREAK]]").map((line, index) => { + return ( + + {index > 0 && } + {line} + + ); + })} + + ); +}; diff --git a/src/utils/markdown_parser/tokenizers/index.ts b/src/utils/markdown_parser/tokenizers/index.ts new file mode 100644 index 0000000..e17dc8a --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/index.ts @@ -0,0 +1,4 @@ +export * from "./slack_user_mention"; +export * from "./slack_channel_mention"; +export * from "./slack_user_group_mention"; +export * from "./slack_broadcast"; diff --git a/src/utils/markdown_parser/tokenizers/slack_broadcast/index.ts b/src/utils/markdown_parser/tokenizers/slack_broadcast/index.ts new file mode 100644 index 0000000..816278d --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_broadcast/index.ts @@ -0,0 +1 @@ +export * from "./tokenizer"; diff --git a/src/utils/markdown_parser/tokenizers/slack_broadcast/match.ts b/src/utils/markdown_parser/tokenizers/slack_broadcast/match.ts new file mode 100644 index 0000000..1585a85 --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_broadcast/match.ts @@ -0,0 +1,88 @@ +import type { INodePoint } from "@yozora/character"; +import { AsciiCodePoint } from "@yozora/character"; +import type { + IMatchInlineHookCreator, + IResultOfFindDelimiters, + IResultOfProcessSingleDelimiter, +} from "@yozora/core-tokenizer"; +import { SlackBroadcastType, type IDelimiter, type IThis, type IToken, type T } from "./types"; + +export const match: IMatchInlineHookCreator = function (api) { + return { findDelimiter, processSingleDelimiter }; + + function* findDelimiter(): IResultOfFindDelimiters { + const nodePoints: ReadonlyArray = api.getNodePoints(); + const blockStartIndex: number = api.getBlockStartIndex(); + const blockEndIndex: number = api.getBlockEndIndex(); + + const targets = ["@everyone", "@here", "@channel"]; + const potentialDelimiters: IDelimiter[] = []; + + for (let i = blockStartIndex; i < blockEndIndex; ++i) { + if (nodePoints[i]?.codePoint === AsciiCodePoint.AT_SIGN) { + for (const target of targets) { + if (matchTarget(nodePoints, i, target)) { + potentialDelimiters.push({ + type: "full", + startIndex: i, + endIndex: i + target.length, + thickness: target.length, + }); + i += target.length - 1; // Skip past the matched target + break; + } + } + } + } + + let pIndex = 0; + let lastEndIndex = -1; + let currentDelimiter: IDelimiter | null = null; + while (pIndex < potentialDelimiters.length) { + const [startIndex, endIndex] = yield currentDelimiter; + + if (lastEndIndex === endIndex) { + if (currentDelimiter == null || currentDelimiter.startIndex >= startIndex) continue; + } + lastEndIndex = endIndex; + + for (; pIndex < potentialDelimiters.length; ++pIndex) { + const delimiter = potentialDelimiters[pIndex]!; + if (delimiter.startIndex >= startIndex) { + currentDelimiter = { + type: "full", + startIndex: delimiter.startIndex, + endIndex: delimiter.endIndex, + thickness: delimiter.thickness, + }; + break; + } + } + } + } + + function matchTarget( + nodePoints: ReadonlyArray, + startIndex: number, + target: string, + ): boolean { + for (let j = 0; j < target.length; ++j) { + if (nodePoints[startIndex + j]?.codePoint !== target.charCodeAt(j)) { + return false; + } + } + return true; + } + + function processSingleDelimiter( + delimiter: IDelimiter, + ): IResultOfProcessSingleDelimiter { + const token: IToken = { + nodeType: SlackBroadcastType, + startIndex: delimiter.startIndex, + endIndex: delimiter.endIndex, + thickness: delimiter.thickness, + }; + return [token]; + } +}; diff --git a/src/utils/markdown_parser/tokenizers/slack_broadcast/parse.ts b/src/utils/markdown_parser/tokenizers/slack_broadcast/parse.ts new file mode 100644 index 0000000..c794b33 --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_broadcast/parse.ts @@ -0,0 +1,21 @@ +import type { INodePoint } from "@yozora/character"; +import { calcStringFromNodePoints } from "@yozora/character"; +import type { IParseInlineHookCreator } from "@yozora/core-tokenizer"; +import { SlackBroadcastType, type INode, type IThis, type IToken, type T } from "./types"; + +export const parse: IParseInlineHookCreator = function (api) { + return { + parse: (tokens) => + tokens.map((token) => { + const nodePoints: ReadonlyArray = api.getNodePoints(); + let startIndex: number = token.startIndex + 1; // skip @ + let endIndex: number = token.endIndex; + + const value = calcStringFromNodePoints(nodePoints, startIndex, endIndex); + const node: INode = api.shouldReservePosition + ? { type: SlackBroadcastType, position: api.calcPosition(token), value } + : { type: SlackBroadcastType, value }; + return node; + }), + }; +}; diff --git a/src/utils/markdown_parser/tokenizers/slack_broadcast/tokenizer.ts b/src/utils/markdown_parser/tokenizers/slack_broadcast/tokenizer.ts new file mode 100644 index 0000000..ff8e985 --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_broadcast/tokenizer.ts @@ -0,0 +1,32 @@ +import type { + IInlineTokenizer, + IMatchInlineHookCreator, + IParseInlineHookCreator, +} from "@yozora/core-tokenizer"; +import { BaseInlineTokenizer, TokenizerPriority } from "@yozora/core-tokenizer"; +import { match } from "./match"; +import { parse } from "./parse"; +import { + SlackBroadcastType, + type IDelimiter, + type INode, + type IThis, + type IToken, + type ITokenizerProps, + type T, +} from "./types"; + +export class SlackBroadcastTokenizer + extends BaseInlineTokenizer + implements IInlineTokenizer +{ + constructor(props: ITokenizerProps = {}) { + super({ + name: SlackBroadcastType, + priority: props.priority || TokenizerPriority.ATOMIC, + }); + } + + public override readonly match: IMatchInlineHookCreator = match; + public override readonly parse: IParseInlineHookCreator = parse; +} diff --git a/src/utils/markdown_parser/tokenizers/slack_broadcast/types.ts b/src/utils/markdown_parser/tokenizers/slack_broadcast/types.ts new file mode 100644 index 0000000..2ed174c --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_broadcast/types.ts @@ -0,0 +1,23 @@ +import { Literal } from "@yozora/ast"; +import type { + IBaseInlineTokenizerProps, + IPartialInlineToken, + ITokenDelimiter, + ITokenizer, +} from "@yozora/core-tokenizer"; + +export const SlackBroadcastType = "slack_broadcast"; +export type T = typeof SlackBroadcastType; +export type INode = Literal; + +export interface IToken extends IPartialInlineToken { + thickness: number; +} + +export interface IDelimiter extends ITokenDelimiter { + type: "full"; + thickness: number; +} + +export type IThis = ITokenizer; +export type ITokenizerProps = Partial; diff --git a/src/utils/markdown_parser/tokenizers/slack_channel_mention/index.ts b/src/utils/markdown_parser/tokenizers/slack_channel_mention/index.ts new file mode 100644 index 0000000..816278d --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_channel_mention/index.ts @@ -0,0 +1 @@ +export * from "./tokenizer"; diff --git a/src/utils/markdown_parser/tokenizers/slack_channel_mention/match.ts b/src/utils/markdown_parser/tokenizers/slack_channel_mention/match.ts new file mode 100644 index 0000000..457bb1c --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_channel_mention/match.ts @@ -0,0 +1,95 @@ +import type { INodeInterval, INodePoint } from "@yozora/character"; +import { AsciiCodePoint } from "@yozora/character"; +import type { + IMatchInlineHookCreator, + IResultOfFindDelimiters, + IResultOfProcessSingleDelimiter, + ITokenDelimiter, +} from "@yozora/core-tokenizer"; +import { eatOptionalCharacters } from "@yozora/core-tokenizer"; +import { SlackChannelMentionType, type IDelimiter, type IThis, type IToken, type T } from "./types"; + +export const match: IMatchInlineHookCreator = function (api) { + return { findDelimiter, processSingleDelimiter }; + + function* findDelimiter(): IResultOfFindDelimiters { + const nodePoints: ReadonlyArray = api.getNodePoints(); + const blockStartIndex: number = api.getBlockStartIndex(); + const blockEndIndex: number = api.getBlockEndIndex(); + + const potentialDelimiters: ITokenDelimiter[] = []; + for (let i = blockStartIndex; i < blockEndIndex; ++i) { + const c = nodePoints[i]?.codePoint; + if ( + c === AsciiCodePoint.OPEN_ANGLE && + nodePoints[i + 1]?.codePoint === AsciiCodePoint.NUMBER_SIGN + ) { + const j = eatOptionalCharacters(nodePoints, i + 2, blockEndIndex, AsciiCodePoint.AT_SIGN); + if (j < blockEndIndex) { + potentialDelimiters.push({ + type: "opener", + startIndex: i, + endIndex: j, + }); + } + } else if (c === AsciiCodePoint.CLOSE_ANGLE) { + potentialDelimiters.push({ + type: "closer", + startIndex: i, + endIndex: i + 1, + }); + } + } + + let pIndex = 0; + let lastEndIndex = -1; + let delimiter: IDelimiter | null = null; + while (pIndex < potentialDelimiters.length) { + const [startIndex, endIndex] = yield delimiter; + + if (lastEndIndex === endIndex) { + if (delimiter == null || delimiter.startIndex >= startIndex) continue; + } + lastEndIndex = endIndex; + + let openerDelimiter: INodeInterval | null = null; + let closerDelimiter: INodeInterval | null = null; + for (; pIndex < potentialDelimiters.length; ++pIndex) { + const delimiter = potentialDelimiters[pIndex]!; + if (delimiter.startIndex >= startIndex && delimiter.type !== "closer") { + openerDelimiter = delimiter; + break; + } + } + + for (let i = pIndex + 1; i < potentialDelimiters.length; ++i) { + const delimiter = potentialDelimiters[i]!; + if (delimiter.type === "closer") { + closerDelimiter = delimiter; + break; + } + } + + if (closerDelimiter == null) return; + + delimiter = { + type: "full", + startIndex: openerDelimiter!.startIndex, + endIndex: closerDelimiter.endIndex, + thickness: closerDelimiter.endIndex - closerDelimiter.startIndex, + }; + } + } + + function processSingleDelimiter( + delimiter: IDelimiter, + ): IResultOfProcessSingleDelimiter { + const token: IToken = { + nodeType: SlackChannelMentionType, + startIndex: delimiter.startIndex, + endIndex: delimiter.endIndex, + thickness: delimiter.thickness, + }; + return [token]; + } +}; diff --git a/src/utils/markdown_parser/tokenizers/slack_channel_mention/parse.ts b/src/utils/markdown_parser/tokenizers/slack_channel_mention/parse.ts new file mode 100644 index 0000000..f661c72 --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_channel_mention/parse.ts @@ -0,0 +1,21 @@ +import type { INodePoint } from "@yozora/character"; +import { calcStringFromNodePoints } from "@yozora/character"; +import type { IParseInlineHookCreator } from "@yozora/core-tokenizer"; +import { SlackChannelMentionType, type INode, type IThis, type IToken, type T } from "./types"; + +export const parse: IParseInlineHookCreator = function (api) { + return { + parse: (tokens) => + tokens.map((token) => { + const nodePoints: ReadonlyArray = api.getNodePoints(); + let startIndex: number = token.startIndex + 2; // Skip `<#` + let endIndex: number = token.endIndex - 1; // Skip `>` + + const value = calcStringFromNodePoints(nodePoints, startIndex, endIndex); + const node: INode = api.shouldReservePosition + ? { type: SlackChannelMentionType, position: api.calcPosition(token), value } + : { type: SlackChannelMentionType, value }; + return node; + }), + }; +}; diff --git a/src/utils/markdown_parser/tokenizers/slack_channel_mention/tokenizer.ts b/src/utils/markdown_parser/tokenizers/slack_channel_mention/tokenizer.ts new file mode 100644 index 0000000..0b06cec --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_channel_mention/tokenizer.ts @@ -0,0 +1,32 @@ +import type { + IInlineTokenizer, + IMatchInlineHookCreator, + IParseInlineHookCreator, +} from "@yozora/core-tokenizer"; +import { BaseInlineTokenizer, TokenizerPriority } from "@yozora/core-tokenizer"; +import { match } from "./match"; +import { parse } from "./parse"; +import { + SlackChannelMentionType, + type IDelimiter, + type INode, + type IThis, + type IToken, + type ITokenizerProps, + type T, +} from "./types"; + +export class SlackChannelMentionTokenizer + extends BaseInlineTokenizer + implements IInlineTokenizer +{ + constructor(props: ITokenizerProps = {}) { + super({ + name: SlackChannelMentionType, + priority: props.priority || TokenizerPriority.ATOMIC, + }); + } + + public override readonly match: IMatchInlineHookCreator = match; + public override readonly parse: IParseInlineHookCreator = parse; +} diff --git a/src/utils/markdown_parser/tokenizers/slack_channel_mention/types.ts b/src/utils/markdown_parser/tokenizers/slack_channel_mention/types.ts new file mode 100644 index 0000000..64a7076 --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_channel_mention/types.ts @@ -0,0 +1,23 @@ +import { Literal } from "@yozora/ast"; +import type { + IBaseInlineTokenizerProps, + IPartialInlineToken, + ITokenDelimiter, + ITokenizer, +} from "@yozora/core-tokenizer"; + +export const SlackChannelMentionType = "slack_channel_mention"; +export type T = typeof SlackChannelMentionType; +export type INode = Literal; + +export interface IToken extends IPartialInlineToken { + thickness: number; +} + +export interface IDelimiter extends ITokenDelimiter { + type: "full"; + thickness: number; +} + +export type IThis = ITokenizer; +export type ITokenizerProps = Partial; diff --git a/src/utils/markdown_parser/tokenizers/slack_user_group_mention/index.ts b/src/utils/markdown_parser/tokenizers/slack_user_group_mention/index.ts new file mode 100644 index 0000000..816278d --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_user_group_mention/index.ts @@ -0,0 +1 @@ +export * from "./tokenizer"; diff --git a/src/utils/markdown_parser/tokenizers/slack_user_group_mention/match.ts b/src/utils/markdown_parser/tokenizers/slack_user_group_mention/match.ts new file mode 100644 index 0000000..ab85518 --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_user_group_mention/match.ts @@ -0,0 +1,109 @@ +import type { INodeInterval, INodePoint } from "@yozora/character"; +import { AsciiCodePoint } from "@yozora/character"; +import type { + IMatchInlineHookCreator, + IResultOfFindDelimiters, + IResultOfProcessSingleDelimiter, + ITokenDelimiter, +} from "@yozora/core-tokenizer"; +import { eatOptionalCharacters } from "@yozora/core-tokenizer"; +import { + SlackUserGroupMentionType, + type IDelimiter, + type IThis, + type IToken, + type T, +} from "./types"; + +export const match: IMatchInlineHookCreator = function (api) { + return { findDelimiter, processSingleDelimiter }; + + function* findDelimiter(): IResultOfFindDelimiters { + const nodePoints: ReadonlyArray = api.getNodePoints(); + const blockStartIndex: number = api.getBlockStartIndex(); + const blockEndIndex: number = api.getBlockEndIndex(); + + const potentialDelimiters: ITokenDelimiter[] = []; + for (let i = blockStartIndex; i < blockEndIndex; ++i) { + const c = nodePoints[i]?.codePoint; + if ( + c === AsciiCodePoint.OPEN_ANGLE && + nodePoints[i + 1]?.codePoint === AsciiCodePoint.EXCLAMATION_MARK && + nodePoints[i + 2]?.codePoint === AsciiCodePoint.LOWERCASE_S && + nodePoints[i + 3]?.codePoint === AsciiCodePoint.LOWERCASE_U && + nodePoints[i + 4]?.codePoint === AsciiCodePoint.LOWERCASE_B && + nodePoints[i + 5]?.codePoint === AsciiCodePoint.LOWERCASE_T && + nodePoints[i + 6]?.codePoint === AsciiCodePoint.LOWERCASE_E && + nodePoints[i + 7]?.codePoint === AsciiCodePoint.LOWERCASE_A && + nodePoints[i + 8]?.codePoint === AsciiCodePoint.LOWERCASE_M && + nodePoints[i + 9]?.codePoint === AsciiCodePoint.CARET + ) { + const j = eatOptionalCharacters(nodePoints, i + 10, blockEndIndex, AsciiCodePoint.CARET); + if (j < blockEndIndex) { + potentialDelimiters.push({ + type: "opener", + startIndex: i, + endIndex: j, + }); + } + } else if (c === AsciiCodePoint.CLOSE_ANGLE) { + potentialDelimiters.push({ + type: "closer", + startIndex: i, + endIndex: i + 1, + }); + } + } + + let pIndex = 0; + let lastEndIndex = -1; + let delimiter: IDelimiter | null = null; + while (pIndex < potentialDelimiters.length) { + const [startIndex, endIndex] = yield delimiter; + + if (lastEndIndex === endIndex) { + if (delimiter == null || delimiter.startIndex >= startIndex) continue; + } + lastEndIndex = endIndex; + + let openerDelimiter: INodeInterval | null = null; + let closerDelimiter: INodeInterval | null = null; + for (; pIndex < potentialDelimiters.length; ++pIndex) { + const delimiter = potentialDelimiters[pIndex]!; + if (delimiter.startIndex >= startIndex && delimiter.type !== "closer") { + openerDelimiter = delimiter; + break; + } + } + + for (let i = pIndex + 1; i < potentialDelimiters.length; ++i) { + const delimiter = potentialDelimiters[i]!; + if (delimiter.type === "closer") { + closerDelimiter = delimiter; + break; + } + } + + if (closerDelimiter == null) return; + + delimiter = { + type: "full", + startIndex: openerDelimiter!.startIndex, + endIndex: closerDelimiter.endIndex, + thickness: closerDelimiter.endIndex - closerDelimiter.startIndex, + }; + } + } + + function processSingleDelimiter( + delimiter: IDelimiter, + ): IResultOfProcessSingleDelimiter { + const token: IToken = { + nodeType: SlackUserGroupMentionType, + startIndex: delimiter.startIndex, + endIndex: delimiter.endIndex, + thickness: delimiter.thickness, + }; + return [token]; + } +}; diff --git a/src/utils/markdown_parser/tokenizers/slack_user_group_mention/parse.ts b/src/utils/markdown_parser/tokenizers/slack_user_group_mention/parse.ts new file mode 100644 index 0000000..ca6096b --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_user_group_mention/parse.ts @@ -0,0 +1,21 @@ +import type { INodePoint } from "@yozora/character"; +import { calcStringFromNodePoints } from "@yozora/character"; +import type { IParseInlineHookCreator } from "@yozora/core-tokenizer"; +import { SlackUserGroupMentionType, type INode, type IThis, type IToken, type T } from "./types"; + +export const parse: IParseInlineHookCreator = function (api) { + return { + parse: (tokens) => + tokens.map((token) => { + const nodePoints: ReadonlyArray = api.getNodePoints(); + let startIndex: number = token.startIndex + 10; // Skip `` + + const value = calcStringFromNodePoints(nodePoints, startIndex, endIndex); + const node: INode = api.shouldReservePosition + ? { type: SlackUserGroupMentionType, position: api.calcPosition(token), value } + : { type: SlackUserGroupMentionType, value }; + return node; + }), + }; +}; diff --git a/src/utils/markdown_parser/tokenizers/slack_user_group_mention/tokenizer.ts b/src/utils/markdown_parser/tokenizers/slack_user_group_mention/tokenizer.ts new file mode 100644 index 0000000..49cf291 --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_user_group_mention/tokenizer.ts @@ -0,0 +1,32 @@ +import type { + IInlineTokenizer, + IMatchInlineHookCreator, + IParseInlineHookCreator, +} from "@yozora/core-tokenizer"; +import { BaseInlineTokenizer, TokenizerPriority } from "@yozora/core-tokenizer"; +import { match } from "./match"; +import { parse } from "./parse"; +import { + SlackUserGroupMentionType, + type IDelimiter, + type INode, + type IThis, + type IToken, + type ITokenizerProps, + type T, +} from "./types"; + +export class SlackUserGroupMentionTokenizer + extends BaseInlineTokenizer + implements IInlineTokenizer +{ + constructor(props: ITokenizerProps = {}) { + super({ + name: SlackUserGroupMentionType, + priority: props.priority || TokenizerPriority.ATOMIC, + }); + } + + public override readonly match: IMatchInlineHookCreator = match; + public override readonly parse: IParseInlineHookCreator = parse; +} diff --git a/src/utils/markdown_parser/tokenizers/slack_user_group_mention/types.ts b/src/utils/markdown_parser/tokenizers/slack_user_group_mention/types.ts new file mode 100644 index 0000000..e515fe2 --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_user_group_mention/types.ts @@ -0,0 +1,23 @@ +import { Literal } from "@yozora/ast"; +import type { + IBaseInlineTokenizerProps, + IPartialInlineToken, + ITokenDelimiter, + ITokenizer, +} from "@yozora/core-tokenizer"; + +export const SlackUserGroupMentionType = "slack_user_group_mention"; +export type T = typeof SlackUserGroupMentionType; +export type INode = Literal; + +export interface IToken extends IPartialInlineToken { + thickness: number; +} + +export interface IDelimiter extends ITokenDelimiter { + type: "full"; + thickness: number; +} + +export type IThis = ITokenizer; +export type ITokenizerProps = Partial; diff --git a/src/utils/markdown_parser/tokenizers/slack_user_mention/index.ts b/src/utils/markdown_parser/tokenizers/slack_user_mention/index.ts new file mode 100644 index 0000000..816278d --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_user_mention/index.ts @@ -0,0 +1 @@ +export * from "./tokenizer"; diff --git a/src/utils/markdown_parser/tokenizers/slack_user_mention/match.ts b/src/utils/markdown_parser/tokenizers/slack_user_mention/match.ts new file mode 100644 index 0000000..b376cba --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_user_mention/match.ts @@ -0,0 +1,95 @@ +import type { INodeInterval, INodePoint } from "@yozora/character"; +import { AsciiCodePoint } from "@yozora/character"; +import type { + IMatchInlineHookCreator, + IResultOfFindDelimiters, + IResultOfProcessSingleDelimiter, + ITokenDelimiter, +} from "@yozora/core-tokenizer"; +import { eatOptionalCharacters } from "@yozora/core-tokenizer"; +import { SlackUserMentionType, type IDelimiter, type IThis, type IToken, type T } from "./types"; + +export const match: IMatchInlineHookCreator = function (api) { + return { findDelimiter, processSingleDelimiter }; + + function* findDelimiter(): IResultOfFindDelimiters { + const nodePoints: ReadonlyArray = api.getNodePoints(); + const blockStartIndex: number = api.getBlockStartIndex(); + const blockEndIndex: number = api.getBlockEndIndex(); + + const potentialDelimiters: ITokenDelimiter[] = []; + for (let i = blockStartIndex; i < blockEndIndex; ++i) { + const c = nodePoints[i]?.codePoint; + if ( + c === AsciiCodePoint.OPEN_ANGLE && + nodePoints[i + 1]?.codePoint === AsciiCodePoint.AT_SIGN + ) { + const j = eatOptionalCharacters(nodePoints, i + 2, blockEndIndex, AsciiCodePoint.AT_SIGN); + if (j < blockEndIndex) { + potentialDelimiters.push({ + type: "opener", + startIndex: i, + endIndex: j, + }); + } + } else if (c === AsciiCodePoint.CLOSE_ANGLE) { + potentialDelimiters.push({ + type: "closer", + startIndex: i, + endIndex: i + 1, + }); + } + } + + let pIndex = 0; + let lastEndIndex = -1; + let delimiter: IDelimiter | null = null; + while (pIndex < potentialDelimiters.length) { + const [startIndex, endIndex] = yield delimiter; + + if (lastEndIndex === endIndex) { + if (delimiter == null || delimiter.startIndex >= startIndex) continue; + } + lastEndIndex = endIndex; + + let openerDelimiter: INodeInterval | null = null; + let closerDelimiter: INodeInterval | null = null; + for (; pIndex < potentialDelimiters.length; ++pIndex) { + const delimiter = potentialDelimiters[pIndex]!; + if (delimiter.startIndex >= startIndex && delimiter.type !== "closer") { + openerDelimiter = delimiter; + break; + } + } + + for (let i = pIndex + 1; i < potentialDelimiters.length; ++i) { + const delimiter = potentialDelimiters[i]!; + if (delimiter.type === "closer") { + closerDelimiter = delimiter; + break; + } + } + + if (closerDelimiter == null) return; + + delimiter = { + type: "full", + startIndex: openerDelimiter!.startIndex, + endIndex: closerDelimiter.endIndex, + thickness: closerDelimiter.endIndex - closerDelimiter.startIndex, + }; + } + } + + function processSingleDelimiter( + delimiter: IDelimiter, + ): IResultOfProcessSingleDelimiter { + const token: IToken = { + nodeType: SlackUserMentionType, + startIndex: delimiter.startIndex, + endIndex: delimiter.endIndex, + thickness: delimiter.thickness, + }; + return [token]; + } +}; diff --git a/src/utils/markdown_parser/tokenizers/slack_user_mention/parse.ts b/src/utils/markdown_parser/tokenizers/slack_user_mention/parse.ts new file mode 100644 index 0000000..b38186c --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_user_mention/parse.ts @@ -0,0 +1,21 @@ +import type { INodePoint } from "@yozora/character"; +import { calcStringFromNodePoints } from "@yozora/character"; +import type { IParseInlineHookCreator } from "@yozora/core-tokenizer"; +import { SlackUserMentionType, type INode, type IThis, type IToken, type T } from "./types"; + +export const parse: IParseInlineHookCreator = function (api) { + return { + parse: (tokens) => + tokens.map((token) => { + const nodePoints: ReadonlyArray = api.getNodePoints(); + let startIndex: number = token.startIndex + 2; // Skip `<@` + let endIndex: number = token.endIndex - 1; // Skip `>` + + const value = calcStringFromNodePoints(nodePoints, startIndex, endIndex); + const node: INode = api.shouldReservePosition + ? { type: SlackUserMentionType, position: api.calcPosition(token), value } + : { type: SlackUserMentionType, value }; + return node; + }), + }; +}; diff --git a/src/utils/markdown_parser/tokenizers/slack_user_mention/tokenizer.ts b/src/utils/markdown_parser/tokenizers/slack_user_mention/tokenizer.ts new file mode 100644 index 0000000..5d6c1fe --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_user_mention/tokenizer.ts @@ -0,0 +1,32 @@ +import type { + IInlineTokenizer, + IMatchInlineHookCreator, + IParseInlineHookCreator, +} from "@yozora/core-tokenizer"; +import { BaseInlineTokenizer, TokenizerPriority } from "@yozora/core-tokenizer"; +import { match } from "./match"; +import { parse } from "./parse"; +import { + SlackUserMentionType, + type IDelimiter, + type INode, + type IThis, + type IToken, + type ITokenizerProps, + type T, +} from "./types"; + +export class SlackUserMentionTokenizer + extends BaseInlineTokenizer + implements IInlineTokenizer +{ + constructor(props: ITokenizerProps = {}) { + super({ + name: SlackUserMentionType, + priority: props.priority || TokenizerPriority.ATOMIC, + }); + } + + public override readonly match: IMatchInlineHookCreator = match; + public override readonly parse: IParseInlineHookCreator = parse; +} diff --git a/src/utils/markdown_parser/tokenizers/slack_user_mention/types.ts b/src/utils/markdown_parser/tokenizers/slack_user_mention/types.ts new file mode 100644 index 0000000..6732eea --- /dev/null +++ b/src/utils/markdown_parser/tokenizers/slack_user_mention/types.ts @@ -0,0 +1,23 @@ +import { Literal } from "@yozora/ast"; +import type { + IBaseInlineTokenizerProps, + IPartialInlineToken, + ITokenDelimiter, + ITokenizer, +} from "@yozora/core-tokenizer"; + +export const SlackUserMentionType = "slack_user_mention"; +export type T = typeof SlackUserMentionType; +export type INode = Literal; + +export interface IToken extends IPartialInlineToken { + thickness: number; +} + +export interface IDelimiter extends ITokenDelimiter { + type: "full"; + thickness: number; +} + +export type IThis = ITokenizer; +export type ITokenizerProps = Partial; diff --git a/src/utils/markdown_parser/types.ts b/src/utils/markdown_parser/types.ts new file mode 100644 index 0000000..a8b964e --- /dev/null +++ b/src/utils/markdown_parser/types.ts @@ -0,0 +1,94 @@ +export type SlackBroadcastSubElement = { + type: "slack_broadcast"; + value: "here" | "everyone" | "channel"; +}; + +export type SlackUserMentionSubElement = { + type: "slack_user_mention"; + value: string; +}; + +export type SlackChannelMentionSubElement = { + type: "slack_channel_mention"; + value: string; +}; + +export type SlackUserGroupMentionSubElement = { + type: "slack_user_group_mention"; + value: string; +}; + +export type TextSubElement = { + type: "text"; + value: string; +}; + +export type InlineCodeSubElement = { + type: "inlineCode"; + value: string; +}; + +export type EmphasisSubElement = { + type: "emphasis"; + children: ( + | TextSubElement + | DeleteSubElement + | SlackUserMentionSubElement + | SlackChannelMentionSubElement + | SlackUserGroupMentionSubElement + | SlackBroadcastSubElement + )[]; +}; + +export type StrongSubElement = { + type: "strong"; + children: ( + | TextSubElement + | DeleteSubElement + | SlackUserMentionSubElement + | SlackChannelMentionSubElement + | SlackUserGroupMentionSubElement + | SlackBroadcastSubElement + )[]; +}; + +export type DeleteSubElement = { + type: "delete"; + children: TextSubElement[]; +}; + +export type LinkSubElement = { + type: "link"; + url: "http://www.example.com"; + children: (TextSubElement | EmphasisSubElement | StrongSubElement | DeleteSubElement)[]; +}; + +export type ParagraphElement = { + type: "paragraph"; + children: ( + | EmphasisSubElement + | TextSubElement + | StrongSubElement + | InlineCodeSubElement + | DeleteSubElement + | LinkSubElement + | SlackUserMentionSubElement + | SlackChannelMentionSubElement + | SlackUserGroupMentionSubElement + | SlackBroadcastSubElement + )[]; +}; + +export type BlockQuoteElement = { + type: "blockquote"; + children: ParagraphElement[]; +}; + +export type CodeElement = { + type: "code"; + lang: null | string; + meta: null | string; + value: string; +}; + +export type MarkdownElement = ParagraphElement | BlockQuoteElement | CodeElement; diff --git a/src/utils/slack_text_to_jsx.tsx b/src/utils/slack_text_to_jsx.tsx deleted file mode 100644 index 41fd1b1..0000000 --- a/src/utils/slack_text_to_jsx.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { ReactNode } from "react"; -import parse from "html-react-parser"; -import { toHTML as slack_text_parser } from "slack-markdown"; -import { GlobalStore } from "../store"; - -type Options = { - markdown: boolean; - users: GlobalStore["users"]; - channels: GlobalStore["channels"]; - hooks: GlobalStore["hooks"]; -}; - -export const slack_text_to_jsx = (text: string, parse_options: Options): ReactNode => { - const { users, channels } = parse_options; - - if (!text) return null; - - let text_string = text; - - // REPLACE LINE BREAKS - text_string = text_string.replace(/(\n)+/g, (match) => { - return match.replace(/\n/g, (newline, index) => { - return index === 0 - ? "" - : ""; - }); - }); - - text_string = slack_text_parser(text_string, { - escapeHTML: false, - slackCallbacks: { - user(data) { - const user = users.find((u) => u.id === data.id || u.name === data.name); - // if (hooks.user) return hooks.user(user || data); - const label = user?.name || data.id || data.name; - return `@${label}`; - }, - channel(data) { - const channel = channels.find((c) => c.id === data.id || c.name === data.name); - // if (hooks.channel) return hooks.channel(channel || data); - const label = channel?.name || data.id || data.name; - return `#${label}`; - }, - atChannel(data) { - const channel = channels.find((c) => c.name === data.name); - // if (hooks.atChannel) return hooks.atChannel(channel || data); - const label = channel?.name || data.name; - return `#${label}`; - }, - atEveryone() { - return `@everyone`; - }, - atHere() { - return `@everyone`; - }, - // ...(hooks.date && { date: hooks.date }), - // ...(hooks.usergroup && { usergroup: hooks.usergroup }), - }, - }); - - return parse(text_string, { trim: false }); -};