Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5152afc8de | ||
|
|
34667ae7e4 | ||
|
|
416fd02069 | ||
|
|
581963cfb4 | ||
|
|
3b14543e66 | ||
|
|
5dd2194eba | ||
|
|
5b7880f817 | ||
|
|
bafe1c5781 | ||
|
|
d760be58c3 | ||
|
|
3da9b70632 | ||
|
|
b7c5902f67 |
No files matched your search
@@ -11,7 +11,7 @@ jobs:
|
||||
PR_NUMBER: ${{github.event.number}}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.0.1
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Build app
|
||||
run: npm ci && npm run build
|
||||
- name: Upload artifact
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
PR_NUMBER: ${{github.event.number}}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.0.1
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Build Docker image
|
||||
uses: docker/build-push-action@v2.10.0
|
||||
with:
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.0.1
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Build and deploy to Netlify
|
||||
uses: jsmrcaga/action-netlify-deploy@v1.7.2
|
||||
with:
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.0.1
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Build and deploy to Netlify
|
||||
uses: jsmrcaga/action-netlify-deploy@v1.7.2
|
||||
with:
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.0.1
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1.14.1
|
||||
with:
|
||||
|
||||
Generated
+136
-45
@@ -20,11 +20,13 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"flux": "^4.0.3",
|
||||
"formik": "^2.2.9",
|
||||
"html-react-parser": "^1.4.11",
|
||||
"html-react-parser": "^1.4.12",
|
||||
"katex": "^0.15.3",
|
||||
"linkifyjs": "^2.1.9",
|
||||
"matrix-js-sdk": "^17.0.0",
|
||||
"micromark": "^3.0.10",
|
||||
"micromark-extension-gfm": "^2.0.1",
|
||||
"micromark-extension-math": "^2.0.2",
|
||||
"micromark-util-chunked": "^1.0.0",
|
||||
"micromark-util-resolve-all": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.1",
|
||||
@@ -45,7 +47,7 @@
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"assert": "^2.0.0",
|
||||
"babel-loader": "^8.2.4",
|
||||
"babel-loader": "^8.2.5",
|
||||
"browserify-fs": "^1.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
@@ -53,7 +55,7 @@
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"css-minimizer-webpack-plugin": "^3.4.1",
|
||||
"eslint": "^8.13.0",
|
||||
"eslint": "^8.14.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
@@ -65,7 +67,7 @@
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"mini-css-extract-plugin": "^2.6.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"sass": "^1.50.0",
|
||||
"sass": "^1.50.1",
|
||||
"sass-loader": "^12.6.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
@@ -1769,9 +1771,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz",
|
||||
"integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz",
|
||||
"integrity": "sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.4",
|
||||
@@ -1789,9 +1791,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/globals": {
|
||||
"version": "13.12.1",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz",
|
||||
"integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==",
|
||||
"version": "13.13.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz",
|
||||
"integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"type-fest": "^0.20.2"
|
||||
@@ -2585,6 +2587,11 @@
|
||||
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/katex": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.11.1.tgz",
|
||||
"integrity": "sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg=="
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||
@@ -3296,9 +3303,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/babel-loader": {
|
||||
"version": "8.2.4",
|
||||
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.4.tgz",
|
||||
"integrity": "sha512-8dytA3gcvPPPv4Grjhnt8b5IIiTcq/zeXOPk4iTYI0SVXcsmuGg7JtBRDp8S9X+gJfhQ8ektjXZlDu1Bb33U8A==",
|
||||
"version": "8.2.5",
|
||||
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz",
|
||||
"integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"find-cache-dir": "^3.3.1",
|
||||
@@ -4170,7 +4177,6 @@
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
@@ -5587,12 +5593,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "8.13.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.13.0.tgz",
|
||||
"integrity": "sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==",
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz",
|
||||
"integrity": "sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint/eslintrc": "^1.2.1",
|
||||
"@eslint/eslintrc": "^1.2.2",
|
||||
"@humanwhocodes/config-array": "^0.9.2",
|
||||
"ajv": "^6.10.0",
|
||||
"chalk": "^4.0.0",
|
||||
@@ -7284,9 +7290,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/html-react-parser": {
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-1.4.11.tgz",
|
||||
"integrity": "sha512-NDSz8MS89I286N7FzNScBtCRBs6MU7w72k+jWk4dBqcTgTrCQHHrIUlryJB9U5MJuKhq5/CFGz0JPPYS6OnjZw==",
|
||||
"version": "1.4.12",
|
||||
"resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-1.4.12.tgz",
|
||||
"integrity": "sha512-nqYQzr4uXh67G9ejAG7djupTHmQvSTgjY83zbXLRfKHJ0F06751jXx6WKSFARDdXxCngo2/7H4Rwtfeowql4gQ==",
|
||||
"dependencies": {
|
||||
"domhandler": "4.3.1",
|
||||
"html-dom-parser": "1.2.0",
|
||||
@@ -8359,6 +8365,21 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/katex": {
|
||||
"version": "0.15.3",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.15.3.tgz",
|
||||
"integrity": "sha512-Al6V7RJsmjklT9QItyHWGaQCt+NYTle1bZwB1e9MR/tLoIT1MXaHy9UpfGSB7eaqDgjjqqRxQOaQGrALCrEyBQ==",
|
||||
"funding": [
|
||||
"https://opencollective.com/katex",
|
||||
"https://github.com/sponsors/katex"
|
||||
],
|
||||
"dependencies": {
|
||||
"commander": "^8.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"katex": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -9095,6 +9116,40 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-math": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-2.0.2.tgz",
|
||||
"integrity": "sha512-cFv2B/E4pFPBBFuGgLHkkNiFAIQv08iDgPH2HCuR2z3AUgMLecES5Cq7AVtwOtZeRrbA80QgMUk8VVW0Z+D2FA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/katex": "^0.11.0",
|
||||
"katex": "^0.13.0",
|
||||
"micromark-factory-space": "^1.0.0",
|
||||
"micromark-util-character": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.0",
|
||||
"micromark-util-types": "^1.0.0",
|
||||
"uvu": "^0.5.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-math/node_modules/katex": {
|
||||
"version": "0.13.24",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.13.24.tgz",
|
||||
"integrity": "sha512-jZxYuKCma3VS5UuxOx/rFV1QyGSl3Uy/i0kTJF3HgQ5xMinCQVF8Zd4bMY/9aI9b9A2pjIBOsjSSm68ykTAr8w==",
|
||||
"funding": [
|
||||
"https://opencollective.com/katex",
|
||||
"https://github.com/sponsors/katex"
|
||||
],
|
||||
"dependencies": {
|
||||
"commander": "^8.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"katex": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-factory-destination": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz",
|
||||
@@ -11876,9 +11931,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.50.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.50.0.tgz",
|
||||
"integrity": "sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ==",
|
||||
"version": "1.50.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.50.1.tgz",
|
||||
"integrity": "sha512-noTnY41KnlW2A9P8sdwESpDmo+KBNkukI1i8+hOK3footBUcohNHtdOJbckp46XO95nuvcHDDZ+4tmOnpK3hjw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
@@ -15125,9 +15180,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@eslint/eslintrc": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz",
|
||||
"integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz",
|
||||
"integrity": "sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^6.12.4",
|
||||
@@ -15142,9 +15197,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"globals": {
|
||||
"version": "13.12.1",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz",
|
||||
"integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==",
|
||||
"version": "13.13.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz",
|
||||
"integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"type-fest": "^0.20.2"
|
||||
@@ -15793,6 +15848,11 @@
|
||||
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
||||
"dev": true
|
||||
},
|
||||
"@types/katex": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.11.1.tgz",
|
||||
"integrity": "sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg=="
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||
@@ -16407,9 +16467,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"babel-loader": {
|
||||
"version": "8.2.4",
|
||||
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.4.tgz",
|
||||
"integrity": "sha512-8dytA3gcvPPPv4Grjhnt8b5IIiTcq/zeXOPk4iTYI0SVXcsmuGg7JtBRDp8S9X+gJfhQ8ektjXZlDu1Bb33U8A==",
|
||||
"version": "8.2.5",
|
||||
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz",
|
||||
"integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"find-cache-dir": "^3.3.1",
|
||||
@@ -17121,8 +17181,7 @@
|
||||
"commander": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
||||
"dev": true
|
||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
|
||||
},
|
||||
"commondir": {
|
||||
"version": "1.0.1",
|
||||
@@ -18209,12 +18268,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"eslint": {
|
||||
"version": "8.13.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.13.0.tgz",
|
||||
"integrity": "sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==",
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz",
|
||||
"integrity": "sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@eslint/eslintrc": "^1.2.1",
|
||||
"@eslint/eslintrc": "^1.2.2",
|
||||
"@humanwhocodes/config-array": "^0.9.2",
|
||||
"ajv": "^6.10.0",
|
||||
"chalk": "^4.0.0",
|
||||
@@ -19542,9 +19601,9 @@
|
||||
}
|
||||
},
|
||||
"html-react-parser": {
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-1.4.11.tgz",
|
||||
"integrity": "sha512-NDSz8MS89I286N7FzNScBtCRBs6MU7w72k+jWk4dBqcTgTrCQHHrIUlryJB9U5MJuKhq5/CFGz0JPPYS6OnjZw==",
|
||||
"version": "1.4.12",
|
||||
"resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-1.4.12.tgz",
|
||||
"integrity": "sha512-nqYQzr4uXh67G9ejAG7djupTHmQvSTgjY83zbXLRfKHJ0F06751jXx6WKSFARDdXxCngo2/7H4Rwtfeowql4gQ==",
|
||||
"requires": {
|
||||
"domhandler": "4.3.1",
|
||||
"html-dom-parser": "1.2.0",
|
||||
@@ -20305,6 +20364,14 @@
|
||||
"object.assign": "^4.1.2"
|
||||
}
|
||||
},
|
||||
"katex": {
|
||||
"version": "0.15.3",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.15.3.tgz",
|
||||
"integrity": "sha512-Al6V7RJsmjklT9QItyHWGaQCt+NYTle1bZwB1e9MR/tLoIT1MXaHy9UpfGSB7eaqDgjjqqRxQOaQGrALCrEyBQ==",
|
||||
"requires": {
|
||||
"commander": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -20922,6 +20989,30 @@
|
||||
"uvu": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"micromark-extension-math": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-2.0.2.tgz",
|
||||
"integrity": "sha512-cFv2B/E4pFPBBFuGgLHkkNiFAIQv08iDgPH2HCuR2z3AUgMLecES5Cq7AVtwOtZeRrbA80QgMUk8VVW0Z+D2FA==",
|
||||
"requires": {
|
||||
"@types/katex": "^0.11.0",
|
||||
"katex": "^0.13.0",
|
||||
"micromark-factory-space": "^1.0.0",
|
||||
"micromark-util-character": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.0",
|
||||
"micromark-util-types": "^1.0.0",
|
||||
"uvu": "^0.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"katex": {
|
||||
"version": "0.13.24",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.13.24.tgz",
|
||||
"integrity": "sha512-jZxYuKCma3VS5UuxOx/rFV1QyGSl3Uy/i0kTJF3HgQ5xMinCQVF8Zd4bMY/9aI9b9A2pjIBOsjSSm68ykTAr8w==",
|
||||
"requires": {
|
||||
"commander": "^8.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"micromark-factory-destination": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz",
|
||||
@@ -22906,9 +22997,9 @@
|
||||
}
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.50.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.50.0.tgz",
|
||||
"integrity": "sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ==",
|
||||
"version": "1.50.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.50.1.tgz",
|
||||
"integrity": "sha512-noTnY41KnlW2A9P8sdwESpDmo+KBNkukI1i8+hOK3footBUcohNHtdOJbckp46XO95nuvcHDDZ+4tmOnpK3hjw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
|
||||
+6
-4
@@ -26,11 +26,13 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"flux": "^4.0.3",
|
||||
"formik": "^2.2.9",
|
||||
"html-react-parser": "^1.4.11",
|
||||
"html-react-parser": "^1.4.12",
|
||||
"katex": "^0.15.3",
|
||||
"linkifyjs": "^2.1.9",
|
||||
"matrix-js-sdk": "^17.0.0",
|
||||
"micromark": "^3.0.10",
|
||||
"micromark-extension-gfm": "^2.0.1",
|
||||
"micromark-extension-math": "^2.0.2",
|
||||
"micromark-util-chunked": "^1.0.0",
|
||||
"micromark-util-resolve-all": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.1",
|
||||
@@ -51,7 +53,7 @@
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"assert": "^2.0.0",
|
||||
"babel-loader": "^8.2.4",
|
||||
"babel-loader": "^8.2.5",
|
||||
"browserify-fs": "^1.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
@@ -59,7 +61,7 @@
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"css-minimizer-webpack-plugin": "^3.4.1",
|
||||
"eslint": "^8.13.0",
|
||||
"eslint": "^8.14.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
@@ -71,7 +73,7 @@
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"mini-css-extract-plugin": "^2.6.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"sass": "^1.50.0",
|
||||
"sass": "^1.50.1",
|
||||
"sass-loader": "^12.6.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import katex from 'katex';
|
||||
import 'katex/dist/katex.min.css';
|
||||
|
||||
import 'katex/dist/contrib/copy-tex';
|
||||
import 'katex/dist/contrib/copy-tex.css';
|
||||
|
||||
const Math = React.memo(({
|
||||
content, throwOnError, errorColor, displayMode,
|
||||
}) => {
|
||||
const ref = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
katex.render(content, ref.current, { throwOnError, errorColor, displayMode });
|
||||
}, [content, throwOnError, errorColor, displayMode]);
|
||||
|
||||
return <span ref={ref} />;
|
||||
});
|
||||
Math.defaultProps = {
|
||||
throwOnError: null,
|
||||
errorColor: null,
|
||||
displayMode: null,
|
||||
};
|
||||
Math.propTypes = {
|
||||
content: PropTypes.string.isRequired,
|
||||
throwOnError: PropTypes.bool,
|
||||
errorColor: PropTypes.string,
|
||||
displayMode: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Math;
|
||||
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './ConfirmDialog.scss';
|
||||
|
||||
import { openReusableDialog } from '../../../client/action/navigation';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
import Button from '../../atoms/button/Button';
|
||||
|
||||
function ConfirmDialog({
|
||||
desc, actionTitle, actionType, onComplete,
|
||||
}) {
|
||||
return (
|
||||
<div className="confirm-dialog">
|
||||
<Text>{desc}</Text>
|
||||
<div className="confirm-dialog__btn">
|
||||
<Button variant={actionType} onClick={() => onComplete(true)}>{actionTitle}</Button>
|
||||
<Button onClick={() => onComplete(false)}>Cancel</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
ConfirmDialog.propTypes = {
|
||||
desc: PropTypes.string.isRequired,
|
||||
actionTitle: PropTypes.string.isRequired,
|
||||
actionType: PropTypes.oneOf(['primary', 'positive', 'danger', 'caution']).isRequired,
|
||||
onComplete: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} title title of confirm dialog
|
||||
* @param {string} desc description of confirm dialog
|
||||
* @param {string} actionTitle title of main action to take
|
||||
* @param {'primary' | 'positive' | 'danger' | 'caution'} actionType type of action. default=primary
|
||||
* @return {Promise<boolean>} does it get's confirmed or not
|
||||
*/
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const confirmDialog = (title, desc, actionTitle, actionType = 'primary') => new Promise((resolve) => {
|
||||
let isCompleted = false;
|
||||
openReusableDialog(
|
||||
<Text variant="s1" weight="medium">{title}</Text>,
|
||||
(requestClose) => (
|
||||
<ConfirmDialog
|
||||
desc={desc}
|
||||
actionTitle={actionTitle}
|
||||
actionType={actionType}
|
||||
onComplete={(isConfirmed) => {
|
||||
isCompleted = true;
|
||||
resolve(isConfirmed);
|
||||
requestClose();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
() => {
|
||||
if (!isCompleted) resolve(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
.confirm-dialog {
|
||||
padding: var(--sp-normal);
|
||||
|
||||
& > .text {
|
||||
padding-bottom: var(--sp-normal);
|
||||
}
|
||||
&__btn {
|
||||
display: flex;
|
||||
gap: var(--sp-normal);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,8 @@ import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg';
|
||||
import CmdIC from '../../../../public/res/ic/outlined/cmd.svg';
|
||||
import BinIC from '../../../../public/res/ic/outlined/bin.svg';
|
||||
|
||||
import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
|
||||
|
||||
function PlaceholderMessage() {
|
||||
return (
|
||||
<div className="ph-msg">
|
||||
@@ -189,7 +191,7 @@ const MessageBody = React.memo(({
|
||||
let content = null;
|
||||
if (isCustomHTML) {
|
||||
try {
|
||||
content = twemojify(sanitizeCustomHtml(body), undefined, true, false);
|
||||
content = twemojify(sanitizeCustomHtml(body), undefined, true, false, true);
|
||||
} catch {
|
||||
console.error('Malformed custom html: ', body);
|
||||
content = twemojify(body, undefined);
|
||||
@@ -546,10 +548,15 @@ const MessageOptions = React.memo(({
|
||||
<MenuItem
|
||||
variant="danger"
|
||||
iconSrc={BinIC}
|
||||
onClick={() => {
|
||||
if (window.confirm('Are you sure that you want to delete this event?')) {
|
||||
redactEvent(roomId, mEvent.getId());
|
||||
}
|
||||
onClick={async () => {
|
||||
const isConfirmed = await confirmDialog(
|
||||
'Delete message',
|
||||
'Are you sure that you want to delete this message?',
|
||||
'Delete',
|
||||
'danger',
|
||||
);
|
||||
if (!isConfirmed) return;
|
||||
redactEvent(roomId, mEvent.getId());
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
|
||||
@@ -8,6 +8,8 @@ import Text from '../../atoms/text/Text';
|
||||
import Toggle from '../../atoms/button/Toggle';
|
||||
import SettingTile from '../setting-tile/SettingTile';
|
||||
|
||||
import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
|
||||
|
||||
function RoomEncryption({ roomId }) {
|
||||
const mx = initMatrix.matrixClient;
|
||||
const room = mx.getRoom(roomId);
|
||||
@@ -15,17 +17,20 @@ function RoomEncryption({ roomId }) {
|
||||
const [isEncrypted, setIsEncrypted] = useState(encryptionEvents.length > 0);
|
||||
const canEnableEncryption = room.currentState.maySendStateEvent('m.room.encryption', mx.getUserId());
|
||||
|
||||
const handleEncryptionEnable = () => {
|
||||
const handleEncryptionEnable = async () => {
|
||||
const joinRule = room.getJoinRule();
|
||||
const confirmMsg1 = 'It is not recommended to add encryption in public room. Anyone can find and join public rooms, so anyone can read messages in them.';
|
||||
const confirmMsg2 = 'Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly';
|
||||
if (joinRule === 'public' ? confirm(confirmMsg1) : true) {
|
||||
if (confirm(confirmMsg2)) {
|
||||
setIsEncrypted(true);
|
||||
mx.sendStateEvent(roomId, 'm.room.encryption', {
|
||||
algorithm: 'm.megolm.v1.aes-sha2',
|
||||
});
|
||||
}
|
||||
|
||||
const isConfirmed1 = (joinRule === 'public')
|
||||
? await confirmDialog('Enable encryption', confirmMsg1, 'Continue', 'caution')
|
||||
: true;
|
||||
if (!isConfirmed1) return;
|
||||
if (await confirmDialog('Enable encryption', confirmMsg2, 'Enable', 'caution')) {
|
||||
setIsEncrypted(true);
|
||||
mx.sendStateEvent(roomId, 'm.room.encryption', {
|
||||
algorithm: 'm.megolm.v1.aes-sha2',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import { openInviteUser, openNavigation } from '../../../client/action/navigation';
|
||||
import { openInviteUser } from '../../../client/action/navigation';
|
||||
import * as roomActions from '../../../client/action/room';
|
||||
import { markAsRead } from '../../../client/action/notifications';
|
||||
|
||||
@@ -15,6 +15,8 @@ import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg';
|
||||
import AddUserIC from '../../../../public/res/ic/outlined/add-user.svg';
|
||||
import LeaveArrowIC from '../../../../public/res/ic/outlined/leave-arrow.svg';
|
||||
|
||||
import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
|
||||
|
||||
function RoomOptions({ roomId, afterOptionSelect }) {
|
||||
const mx = initMatrix.matrixClient;
|
||||
const room = mx.getRoom(roomId);
|
||||
@@ -29,12 +31,16 @@ function RoomOptions({ roomId, afterOptionSelect }) {
|
||||
openInviteUser(roomId);
|
||||
afterOptionSelect();
|
||||
};
|
||||
const handleLeaveClick = () => {
|
||||
if (confirm('Are you sure that you want to leave this room?')) {
|
||||
roomActions.leave(roomId);
|
||||
afterOptionSelect();
|
||||
openNavigation();
|
||||
}
|
||||
const handleLeaveClick = async () => {
|
||||
afterOptionSelect();
|
||||
const isConfirmed = await confirmDialog(
|
||||
'Leave room',
|
||||
`Are you sure that you want to leave "${room.name}" room?`,
|
||||
'Leave',
|
||||
'danger',
|
||||
);
|
||||
if (!isConfirmed) return;
|
||||
roomActions.leave(roomId);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -19,6 +19,7 @@ import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
|
||||
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import { useForceUpdate } from '../../hooks/useForceUpdate';
|
||||
import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
|
||||
|
||||
function RoomProfile({ roomId }) {
|
||||
const isMountStore = useStore();
|
||||
@@ -117,7 +118,13 @@ function RoomProfile({ roomId }) {
|
||||
|
||||
const handleAvatarUpload = async (url) => {
|
||||
if (url === null) {
|
||||
if (confirm('Are you sure that you want to remove room avatar?')) {
|
||||
const isConfirmed = await confirmDialog(
|
||||
'Remove avatar',
|
||||
'Are you sure that you want to remove room avatar?',
|
||||
'Remove',
|
||||
'caution',
|
||||
);
|
||||
if (isConfirmed) {
|
||||
await mx.sendStateEvent(roomId, 'm.room.avatar', { url }, '');
|
||||
}
|
||||
} else await mx.sendStateEvent(roomId, 'm.room.avatar', { url }, '');
|
||||
|
||||
@@ -24,6 +24,8 @@ import LeaveArrowIC from '../../../../public/res/ic/outlined/leave-arrow.svg';
|
||||
import PinIC from '../../../../public/res/ic/outlined/pin.svg';
|
||||
import PinFilledIC from '../../../../public/res/ic/filled/pin.svg';
|
||||
|
||||
import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
|
||||
|
||||
function SpaceOptions({ roomId, afterOptionSelect }) {
|
||||
const mx = initMatrix.matrixClient;
|
||||
const room = mx.getRoom(roomId);
|
||||
@@ -54,11 +56,16 @@ function SpaceOptions({ roomId, afterOptionSelect }) {
|
||||
afterOptionSelect();
|
||||
};
|
||||
|
||||
const handleLeaveClick = () => {
|
||||
if (confirm('Are you sure that you want to leave this space?')) {
|
||||
leave(roomId);
|
||||
afterOptionSelect();
|
||||
}
|
||||
const handleLeaveClick = async () => {
|
||||
afterOptionSelect();
|
||||
const isConfirmed = await confirmDialog(
|
||||
'Leave space',
|
||||
`Are you sure that you want to leave "${room.name}" space?`,
|
||||
'Leave',
|
||||
'danger',
|
||||
);
|
||||
if (!isConfirmed) return;
|
||||
leave(roomId);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './EmojiVerification.scss';
|
||||
import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
import IconButton from '../../atoms/button/IconButton';
|
||||
import Button from '../../atoms/button/Button';
|
||||
import Spinner from '../../atoms/spinner/Spinner';
|
||||
import Dialog from '../../molecules/dialog/Dialog';
|
||||
|
||||
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
|
||||
function EmojiVerificationContent({ request, requestClose }) {
|
||||
const [sas, setSas] = useState(null);
|
||||
const [process, setProcess] = useState(false);
|
||||
const mountStore = useStore();
|
||||
mountStore.setItem(true);
|
||||
|
||||
const handleChange = () => {
|
||||
if (request.done || request.cancelled) requestClose();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
mountStore.setItem(true);
|
||||
if (request === null) return null;
|
||||
const req = request;
|
||||
req.on('change', handleChange);
|
||||
return () => req.off('change', handleChange);
|
||||
}, [request]);
|
||||
|
||||
const acceptRequest = async () => {
|
||||
setProcess(true);
|
||||
await request.accept();
|
||||
|
||||
const verifier = request.beginKeyVerification('m.sas.v1');
|
||||
verifier.on('show_sas', (data) => {
|
||||
if (!mountStore.getItem()) return;
|
||||
setSas(data);
|
||||
setProcess(false);
|
||||
});
|
||||
await verifier.verify();
|
||||
};
|
||||
|
||||
const sasMismatch = () => {
|
||||
sas.mismatch();
|
||||
setProcess(true);
|
||||
};
|
||||
|
||||
const sasConfirm = () => {
|
||||
sas.confirm();
|
||||
setProcess(true);
|
||||
};
|
||||
|
||||
const renderWait = () => (
|
||||
<>
|
||||
<Spinner size="small" />
|
||||
<Text>Waiting for response from other device...</Text>
|
||||
</>
|
||||
);
|
||||
|
||||
if (sas !== null) {
|
||||
return (
|
||||
<div className="emoji-verification__content">
|
||||
<Text>Confirm the emoji below are displayed on both devices, in the same order:</Text>
|
||||
<div className="emoji-verification__emojis">
|
||||
{sas.sas.emoji.map((emoji) => (
|
||||
<div className="emoji-verification__emoji-block" key={emoji[1]}>
|
||||
<Text variant="h1">{twemojify(emoji[0])}</Text>
|
||||
<Text>{emoji[1]}</Text>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="emoji-verification__buttons">
|
||||
{process ? renderWait() : (
|
||||
<>
|
||||
<Button variant="primary" onClick={sasConfirm}>They match</Button>
|
||||
<Button onClick={sasMismatch}>{'They don\'t match'}</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="emoji-verification__content">
|
||||
<Text>Click accept to start the verification process</Text>
|
||||
<div className="emoji-verification__buttons">
|
||||
{
|
||||
process
|
||||
? renderWait()
|
||||
: <Button variant="primary" onClick={acceptRequest}>Accept</Button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
EmojiVerificationContent.propTypes = {
|
||||
request: PropTypes.shape({}).isRequired,
|
||||
requestClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
function useVisibilityToggle() {
|
||||
const [request, setRequest] = useState(null);
|
||||
const mx = initMatrix.matrixClient;
|
||||
|
||||
useEffect(() => {
|
||||
const handleOpen = (req) => setRequest(req);
|
||||
navigation.on(cons.events.navigation.EMOJI_VERIFICATION_OPENED, handleOpen);
|
||||
mx.on('crypto.verification.request', handleOpen);
|
||||
return () => {
|
||||
navigation.removeListener(cons.events.navigation.EMOJI_VERIFICATION_OPENED, handleOpen);
|
||||
mx.removeListener('crypto.verification.request', handleOpen);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const requestClose = () => setRequest(null);
|
||||
|
||||
return [request, requestClose];
|
||||
}
|
||||
|
||||
function EmojiVerification() {
|
||||
const [request, requestClose] = useVisibilityToggle();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={request !== null}
|
||||
className="emoji-verification"
|
||||
title={(
|
||||
<Text variant="s1" weight="medium" primary>
|
||||
Emoji verification
|
||||
</Text>
|
||||
)}
|
||||
contentOptions={<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />}
|
||||
onRequestClose={requestClose}
|
||||
>
|
||||
{
|
||||
request !== null
|
||||
? <EmojiVerificationContent request={request} requestClose={requestClose} />
|
||||
: <div />
|
||||
}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default EmojiVerification;
|
||||
@@ -0,0 +1,35 @@
|
||||
@use '../../partials/flex';
|
||||
@use '../../partials/dir';
|
||||
|
||||
.emoji-verification {
|
||||
&__content {
|
||||
padding: var(--sp-normal);
|
||||
@include dir.side(padding, var(--sp-normal), var(--sp-extra-tight));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-normal);
|
||||
}
|
||||
|
||||
&__emojis {
|
||||
margin: var(--sp-loose) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
gap: var(--sp-extra-tight);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__emoji-block {
|
||||
@extend .cp-fx__column;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
gap: var(--sp-extra-tight);
|
||||
white-space: nowrap;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
display: flex;
|
||||
gap: var(--sp-normal);
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ function ProfileAvatarMenu() {
|
||||
|
||||
function CrossSigninAlert() {
|
||||
const deviceList = useDeviceList();
|
||||
const unverified = deviceList?.filter((device) => !isCrossVerified(device.device_id));
|
||||
const unverified = deviceList?.filter((device) => isCrossVerified(device.device_id) === false);
|
||||
|
||||
if (!unverified?.length) return null;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import ImageUpload from '../../molecules/image-upload/ImageUpload';
|
||||
import Input from '../../atoms/input/Input';
|
||||
|
||||
import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
|
||||
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
|
||||
|
||||
import './ProfileEditor.scss';
|
||||
|
||||
@@ -38,9 +39,15 @@ function ProfileEditor({ userId }) {
|
||||
};
|
||||
}, [userId]);
|
||||
|
||||
const handleAvatarUpload = (url) => {
|
||||
const handleAvatarUpload = async (url) => {
|
||||
if (url === null) {
|
||||
if (confirm('Are you sure that you want to remove avatar?')) {
|
||||
const isConfirmed = await confirmDialog(
|
||||
'Remove avatar',
|
||||
'Are you sure that you want to remove avatar?',
|
||||
'Remove',
|
||||
'caution',
|
||||
);
|
||||
if (isConfirmed) {
|
||||
mx.setAvatarUrl('');
|
||||
setAvatarSrc(null);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.s
|
||||
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
|
||||
|
||||
import { useForceUpdate } from '../../hooks/useForceUpdate';
|
||||
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
|
||||
|
||||
function ModerationTools({
|
||||
roomId, userId,
|
||||
@@ -362,7 +363,7 @@ function ProfileViewer() {
|
||||
&& (powerLevel < myPowerLevel || userId === mx.getUserId())
|
||||
);
|
||||
|
||||
const handleChangePowerLevel = (newPowerLevel) => {
|
||||
const handleChangePowerLevel = async (newPowerLevel) => {
|
||||
if (newPowerLevel === powerLevel) return;
|
||||
const SHARED_POWER_MSG = 'You will not be able to undo this change as you are promoting the user to have the same power level as yourself. Are you sure?';
|
||||
const DEMOTING_MYSELF_MSG = 'You will not be able to undo this change as you are demoting yourself. Are you sure?';
|
||||
@@ -370,9 +371,14 @@ function ProfileViewer() {
|
||||
const isSharedPower = newPowerLevel === myPowerLevel;
|
||||
const isDemotingMyself = userId === mx.getUserId();
|
||||
if (isSharedPower || isDemotingMyself) {
|
||||
if (confirm(isSharedPower ? SHARED_POWER_MSG : DEMOTING_MYSELF_MSG)) {
|
||||
roomActions.setPowerLevel(roomId, userId, newPowerLevel);
|
||||
}
|
||||
const isConfirmed = await confirmDialog(
|
||||
'Change power level',
|
||||
isSharedPower ? SHARED_POWER_MSG : DEMOTING_MYSELF_MSG,
|
||||
'Change',
|
||||
'caution',
|
||||
);
|
||||
if (!isConfirmed) return;
|
||||
roomActions.setPowerLevel(roomId, userId, newPowerLevel);
|
||||
} else {
|
||||
roomActions.setPowerLevel(roomId, userId, newPowerLevel);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import SpaceAddExisting from '../../molecules/space-add-existing/SpaceAddExistin
|
||||
import Search from '../search/Search';
|
||||
import ViewSource from '../view-source/ViewSource';
|
||||
import CreateRoom from '../create-room/CreateRoom';
|
||||
import EmojiVerification from '../emoji-verification/EmojiVerification';
|
||||
|
||||
import ReusableDialog from '../../molecules/dialog/ReusableDialog';
|
||||
|
||||
@@ -20,6 +21,7 @@ function Dialogs() {
|
||||
<CreateRoom />
|
||||
<SpaceAddExisting />
|
||||
<Search />
|
||||
<EmojiVerification />
|
||||
|
||||
<ReusableDialog />
|
||||
</>
|
||||
|
||||
@@ -36,6 +36,7 @@ import LeaveArrowIC from '../../../../public/res/ic/outlined/leave-arrow.svg';
|
||||
import ChevronTopIC from '../../../../public/res/ic/outlined/chevron-top.svg';
|
||||
|
||||
import { useForceUpdate } from '../../hooks/useForceUpdate';
|
||||
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
|
||||
|
||||
const tabText = {
|
||||
GENERAL: 'General',
|
||||
@@ -85,10 +86,15 @@ function GeneralSettings({ roomId }) {
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
variant="danger"
|
||||
onClick={() => {
|
||||
if (confirm('Are you sure that you want to leave this room?')) {
|
||||
roomActions.leave(roomId);
|
||||
}
|
||||
onClick={async () => {
|
||||
const isConfirmed = await confirmDialog(
|
||||
'Leave room',
|
||||
`Are you sure that you want to leave "${room.name}" room?`,
|
||||
'Leave',
|
||||
'danger',
|
||||
);
|
||||
if (!isConfirmed) return;
|
||||
roomActions.leave(roomId);
|
||||
}}
|
||||
iconSrc={LeaveArrowIC}
|
||||
>
|
||||
|
||||
@@ -4,10 +4,12 @@ import dateFormat from 'dateformat';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import { isCrossVerified } from '../../../util/matrixUtil';
|
||||
import { openReusableDialog, openEmojiVerification } from '../../../client/action/navigation';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
import Button from '../../atoms/button/Button';
|
||||
import IconButton from '../../atoms/button/IconButton';
|
||||
import Input from '../../atoms/input/Input';
|
||||
import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
|
||||
import InfoCard from '../../atoms/card/InfoCard';
|
||||
import Spinner from '../../atoms/spinner/Spinner';
|
||||
@@ -18,10 +20,46 @@ import BinIC from '../../../../public/res/ic/outlined/bin.svg';
|
||||
import InfoIC from '../../../../public/res/ic/outlined/info.svg';
|
||||
|
||||
import { authRequest } from './AuthRequest';
|
||||
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
|
||||
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import { useDeviceList } from '../../hooks/useDeviceList';
|
||||
import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus';
|
||||
import { accessSecretStorage } from './SecretStorageAccess';
|
||||
|
||||
const promptDeviceName = async (deviceName) => new Promise((resolve) => {
|
||||
let isCompleted = false;
|
||||
|
||||
const renderContent = (onComplete) => {
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const name = e.target.session.value;
|
||||
if (typeof name !== 'string') onComplete(null);
|
||||
onComplete(name);
|
||||
};
|
||||
return (
|
||||
<form className="device-manage__rename" onSubmit={handleSubmit}>
|
||||
<Input value={deviceName} label="Session name" name="session" />
|
||||
<div className="device-manage__rename-btn">
|
||||
<Button variant="primary" type="submit">Save</Button>
|
||||
<Button onClick={() => onComplete(null)}>Cancel</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
openReusableDialog(
|
||||
<Text variant="s1" weight="medium">Edit session name</Text>,
|
||||
(requestClose) => renderContent((name) => {
|
||||
isCompleted = true;
|
||||
resolve(name);
|
||||
requestClose();
|
||||
}),
|
||||
() => {
|
||||
if (!isCompleted) resolve(null);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
function DeviceManage() {
|
||||
const TRUNCATED_COUNT = 4;
|
||||
@@ -32,6 +70,7 @@ function DeviceManage() {
|
||||
const [truncated, setTruncated] = useState(true);
|
||||
const mountStore = useStore();
|
||||
mountStore.setItem(true);
|
||||
const isMeVerified = isCrossVerified(mx.deviceId);
|
||||
|
||||
useEffect(() => {
|
||||
setProcessing([]);
|
||||
@@ -59,7 +98,7 @@ function DeviceManage() {
|
||||
}
|
||||
|
||||
const handleRename = async (device) => {
|
||||
const newName = window.prompt('Edit session name', device.display_name);
|
||||
const newName = await promptDeviceName(device.display_name);
|
||||
if (newName === null || newName.trim() === '') return;
|
||||
if (newName.trim() === device.display_name) return;
|
||||
addToProcessing(device);
|
||||
@@ -74,15 +113,40 @@ function DeviceManage() {
|
||||
};
|
||||
|
||||
const handleRemove = async (device) => {
|
||||
if (window.confirm(`You are about to logout "${device.display_name}" session.`)) {
|
||||
addToProcessing(device);
|
||||
await authRequest(`Logout "${device.display_name}"`, async (auth) => {
|
||||
await mx.deleteDevice(device.device_id, auth);
|
||||
});
|
||||
const isConfirmed = await confirmDialog(
|
||||
`Logout ${device.display_name}`,
|
||||
`You are about to logout "${device.display_name}" session.`,
|
||||
'Logout',
|
||||
'danger',
|
||||
);
|
||||
if (!isConfirmed) return;
|
||||
addToProcessing(device);
|
||||
await authRequest(`Logout "${device.display_name}"`, async (auth) => {
|
||||
await mx.deleteDevice(device.device_id, auth);
|
||||
});
|
||||
|
||||
if (!mountStore.getItem()) return;
|
||||
removeFromProcessing(device);
|
||||
if (!mountStore.getItem()) return;
|
||||
removeFromProcessing(device);
|
||||
};
|
||||
|
||||
const verifyWithKey = async (device) => {
|
||||
const keyData = await accessSecretStorage('Session verification');
|
||||
if (!keyData) return;
|
||||
addToProcessing(device);
|
||||
await mx.checkOwnCrossSigningTrust();
|
||||
};
|
||||
|
||||
const verifyWithEmojis = async (deviceId) => {
|
||||
const req = await mx.requestVerification(mx.getUserId(), [deviceId]);
|
||||
openEmojiVerification(req);
|
||||
};
|
||||
|
||||
const verify = (deviceId, isCurrentDevice) => {
|
||||
if (isCurrentDevice) {
|
||||
verifyWithKey(deviceId);
|
||||
return;
|
||||
}
|
||||
verifyWithEmojis(deviceId);
|
||||
};
|
||||
|
||||
const renderDevice = (device, isVerified) => {
|
||||
@@ -90,13 +154,16 @@ function DeviceManage() {
|
||||
const displayName = device.display_name;
|
||||
const lastIP = device.last_seen_ip;
|
||||
const lastTS = device.last_seen_ts;
|
||||
const isCurrentDevice = mx.deviceId === deviceId;
|
||||
|
||||
return (
|
||||
<SettingTile
|
||||
key={deviceId}
|
||||
title={(
|
||||
<Text style={{ color: isVerified ? '' : 'var(--tc-danger-high)' }}>
|
||||
<Text style={{ color: isVerified !== false ? '' : 'var(--tc-danger-high)' }}>
|
||||
{displayName}
|
||||
<Text variant="b3" span>{` — ${deviceId}${mx.deviceId === deviceId ? ' (current)' : ''}`}</Text>
|
||||
<Text variant="b3" span>{`${displayName ? ' — ' : ''}${deviceId}`}</Text>
|
||||
{isCurrentDevice && <Text span className="device-manage__current-label" variant="b3">Current</Text>}
|
||||
</Text>
|
||||
)}
|
||||
options={
|
||||
@@ -104,19 +171,27 @@ function DeviceManage() {
|
||||
? <Spinner size="small" />
|
||||
: (
|
||||
<>
|
||||
{((isMeVerified && isVerified === false) || (isCurrentDevice && isVerified === false)) && <Button onClick={() => verify(deviceId, isCurrentDevice)} variant="positive">Verify</Button>}
|
||||
<IconButton size="small" onClick={() => handleRename(device)} src={PencilIC} tooltip="Rename" />
|
||||
<IconButton size="small" onClick={() => handleRemove(device)} src={BinIC} tooltip="Remove session" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
content={(
|
||||
<Text variant="b3">
|
||||
Last activity
|
||||
<span style={{ color: 'var(--tc-surface-normal)' }}>
|
||||
{dateFormat(new Date(lastTS), ' hh:MM TT, dd/mm/yyyy')}
|
||||
</span>
|
||||
{lastIP ? ` at ${lastIP}` : ''}
|
||||
</Text>
|
||||
<>
|
||||
<Text variant="b3">
|
||||
Last activity
|
||||
<span style={{ color: 'var(--tc-surface-normal)' }}>
|
||||
{dateFormat(new Date(lastTS), ' hh:MM TT, dd/mm/yyyy')}
|
||||
</span>
|
||||
{lastIP ? ` at ${lastIP}` : ''}
|
||||
</Text>
|
||||
{isCurrentDevice && (
|
||||
<Text style={{ marginTop: 'var(--sp-ultra-tight)' }} variant="b3">
|
||||
{`Session Key: ${initMatrix.matrixClient.getDeviceEd25519Key().match(/.{1,4}/g).join(' ')}`}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
@@ -124,9 +199,16 @@ function DeviceManage() {
|
||||
|
||||
const unverified = [];
|
||||
const verified = [];
|
||||
const noEncryption = [];
|
||||
deviceList.sort((a, b) => b.last_seen_ts - a.last_seen_ts).forEach((device) => {
|
||||
if (isCrossVerified(device.device_id)) verified.push(device);
|
||||
else unverified.push(device);
|
||||
const isVerified = isCrossVerified(device.device_id);
|
||||
if (isVerified === true) {
|
||||
verified.push(device);
|
||||
} else if (isVerified === false) {
|
||||
unverified.push(device);
|
||||
} else {
|
||||
noEncryption.push(device);
|
||||
}
|
||||
});
|
||||
return (
|
||||
<div className="device-manage">
|
||||
@@ -145,9 +227,15 @@ function DeviceManage() {
|
||||
{
|
||||
unverified.length > 0
|
||||
? unverified.map((device) => renderDevice(device, false))
|
||||
: <Text className="device-manage__info">No unverified session</Text>
|
||||
: <Text className="device-manage__info">No unverified sessions</Text>
|
||||
}
|
||||
</div>
|
||||
{noEncryption.length > 0 && (
|
||||
<div>
|
||||
<MenuHeader>Sessions without encryption support</MenuHeader>
|
||||
{noEncryption.map((device) => renderDevice(device, null))}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<MenuHeader>Verified sessions</MenuHeader>
|
||||
{
|
||||
@@ -156,7 +244,7 @@ function DeviceManage() {
|
||||
if (truncated && index >= TRUNCATED_COUNT) return null;
|
||||
return renderDevice(device, true);
|
||||
})
|
||||
: <Text className="device-manage__info">No verified session</Text>
|
||||
: <Text className="device-manage__info">No verified sessions</Text>
|
||||
}
|
||||
{ verified.length > TRUNCATED_COUNT && (
|
||||
<Button className="device-manage__info" onClick={() => setTruncated(!truncated)}>
|
||||
|
||||
@@ -15,4 +15,32 @@
|
||||
& .setting-tile:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
& .setting-tile__options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-ultra-tight);
|
||||
& .btn-positive {
|
||||
padding: 6px var(--sp-tight);
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__current-label {
|
||||
margin: 0 var(--sp-extra-tight);
|
||||
padding: 2px var(--sp-ultra-tight);
|
||||
color: var(--bg-surface);
|
||||
background-color: var(--tc-surface-low);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&__rename {
|
||||
padding: var(--sp-normal);
|
||||
& > *:not(:last-child) {
|
||||
margin-bottom: var(--sp-normal);
|
||||
}
|
||||
&-btn {
|
||||
display: flex;
|
||||
gap: var(--sp-normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import PowerIC from '../../../../public/res/ic/outlined/power.svg';
|
||||
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
|
||||
|
||||
import CinnySVG from '../../../../public/res/svg/cinny.svg';
|
||||
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
|
||||
|
||||
function AppearanceSection() {
|
||||
const [, updateState] = useState({});
|
||||
@@ -297,8 +298,10 @@ function Settings() {
|
||||
const [isOpen, requestClose] = useWindowToggle(setSelectedTab);
|
||||
|
||||
const handleTabChange = (tabItem) => setSelectedTab(tabItem);
|
||||
const handleLogout = () => {
|
||||
if (confirm('Confirm logout')) logout();
|
||||
const handleLogout = async () => {
|
||||
if (await confirmDialog('Logout', 'Are you sure that you want to logout your session?', 'Logout', 'danger')) {
|
||||
logout();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -36,6 +36,7 @@ import PinFilledIC from '../../../../public/res/ic/filled/pin.svg';
|
||||
import CategoryIC from '../../../../public/res/ic/outlined/category.svg';
|
||||
import CategoryFilledIC from '../../../../public/res/ic/filled/category.svg';
|
||||
|
||||
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
|
||||
import { useForceUpdate } from '../../hooks/useForceUpdate';
|
||||
|
||||
const tabText = {
|
||||
@@ -61,6 +62,7 @@ const tabItems = [{
|
||||
function GeneralSettings({ roomId }) {
|
||||
const isPinned = initMatrix.accountData.spaceShortcut.has(roomId);
|
||||
const isCategorized = initMatrix.accountData.categorizedSpaces.has(roomId);
|
||||
const roomName = initMatrix.matrixClient.getRoom(roomId)?.name;
|
||||
const [, forceUpdate] = useForceUpdate();
|
||||
|
||||
return (
|
||||
@@ -89,10 +91,14 @@ function GeneralSettings({ roomId }) {
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
variant="danger"
|
||||
onClick={() => {
|
||||
if (confirm('Are you sure that you want to leave this space?')) {
|
||||
leave(roomId);
|
||||
}
|
||||
onClick={async () => {
|
||||
const isConfirmed = await confirmDialog(
|
||||
'Leave space',
|
||||
`Are you sure that you want to leave "${roomName}" space?`,
|
||||
'Leave',
|
||||
'danger',
|
||||
);
|
||||
if (isConfirmed) leave(roomId);
|
||||
}}
|
||||
iconSrc={LeaveArrowIC}
|
||||
>
|
||||
|
||||
@@ -166,3 +166,10 @@ export function openReusableDialog(title, render, afterClose) {
|
||||
afterClose,
|
||||
});
|
||||
}
|
||||
|
||||
export function openEmojiVerification(request) {
|
||||
appDispatcher.dispatch({
|
||||
type: cons.actions.navigation.OPEN_EMOJI_VERIFICATION,
|
||||
request,
|
||||
});
|
||||
}
|
||||
@@ -38,6 +38,9 @@ class InitMatrix extends EventEmitter {
|
||||
deviceId: secret.deviceId,
|
||||
timelineSupport: true,
|
||||
cryptoCallbacks,
|
||||
verificationMethods: [
|
||||
'm.sas.v1',
|
||||
],
|
||||
});
|
||||
|
||||
await this.matrixClient.initCrypto();
|
||||
|
||||
@@ -2,8 +2,9 @@ import EventEmitter from 'events';
|
||||
import { micromark } from 'micromark';
|
||||
import { gfm, gfmHtml } from 'micromark-extension-gfm';
|
||||
import encrypt from 'browser-encrypt-attachment';
|
||||
import { math } from 'micromark-extension-math';
|
||||
import { getShortcodeToEmoji } from '../../app/organisms/emoji-board/custom-emoji';
|
||||
import { spoilerExtension, spoilerExtensionHtml } from '../../util/markdown';
|
||||
import { mathExtensionHtml, spoilerExtension, spoilerExtensionHtml } from '../../util/markdown';
|
||||
import cons from './cons';
|
||||
import settings from './settings';
|
||||
|
||||
@@ -85,8 +86,8 @@ function getVideoThumbnail(video, width, height, mimeType) {
|
||||
|
||||
function getFormattedBody(markdown) {
|
||||
const result = micromark(markdown, {
|
||||
extensions: [gfm(), spoilerExtension()],
|
||||
htmlExtensions: [gfmHtml(), spoilerExtensionHtml],
|
||||
extensions: [gfm(), spoilerExtension(), math()],
|
||||
htmlExtensions: [gfmHtml(), spoilerExtensionHtml, mathExtensionHtml],
|
||||
});
|
||||
const bodyParts = result.match(/^(<p>)(.*)(<\/p>)$/);
|
||||
if (bodyParts === null) return result;
|
||||
|
||||
@@ -49,6 +49,7 @@ const cons = {
|
||||
OPEN_REUSABLE_CONTEXT_MENU: 'OPEN_REUSABLE_CONTEXT_MENU',
|
||||
OPEN_NAVIGATION: 'OPEN_NAVIGATION',
|
||||
OPEN_REUSABLE_DIALOG: 'OPEN_REUSABLE_DIALOG',
|
||||
OPEN_EMOJI_VERIFICATION: 'OPEN_EMOJI_VERIFICATION',
|
||||
},
|
||||
room: {
|
||||
JOIN: 'JOIN',
|
||||
@@ -96,6 +97,7 @@ const cons = {
|
||||
REUSABLE_CONTEXT_MENU_OPENED: 'REUSABLE_CONTEXT_MENU_OPENED',
|
||||
NAVIGATION_OPENED: 'NAVIGATION_OPENED',
|
||||
REUSABLE_DIALOG_OPENED: 'REUSABLE_DIALOG_OPENED',
|
||||
EMOJI_VERIFICATION_OPENED: 'EMOJI_VERIFICATION_OPENED',
|
||||
},
|
||||
roomList: {
|
||||
ROOMLIST_UPDATED: 'ROOMLIST_UPDATED',
|
||||
|
||||
@@ -185,6 +185,12 @@ class Navigation extends EventEmitter {
|
||||
action.afterClose,
|
||||
);
|
||||
},
|
||||
[cons.actions.navigation.OPEN_EMOJI_VERIFICATION]: () => {
|
||||
this.emit(
|
||||
cons.events.navigation.EMOJI_VERIFICATION_OPENED,
|
||||
action.request,
|
||||
);
|
||||
},
|
||||
};
|
||||
actions[action.type]?.();
|
||||
}
|
||||
|
||||
+56
-1
@@ -140,4 +140,59 @@ const spoilerExtensionHtml = {
|
||||
},
|
||||
};
|
||||
|
||||
export { inlineExtension, spoilerExtension, spoilerExtensionHtml };
|
||||
const mathExtensionHtml = {
|
||||
enter: {
|
||||
mathFlow() {
|
||||
this.lineEndingIfNeeded();
|
||||
},
|
||||
mathFlowFenceMeta() {
|
||||
this.buffer();
|
||||
},
|
||||
mathText() {
|
||||
this.buffer();
|
||||
},
|
||||
},
|
||||
exit: {
|
||||
mathFlow() {
|
||||
const value = this.encode(this.resume().replace(/(?:\r?\n|\r)$/, ''));
|
||||
this.tag('<div data-mx-maths="');
|
||||
this.tag(value);
|
||||
this.tag('"><code>');
|
||||
this.raw(value);
|
||||
this.tag('</code></div>');
|
||||
this.setData('mathFlowOpen');
|
||||
this.setData('slurpOneLineEnding');
|
||||
},
|
||||
mathFlowFence() {
|
||||
// After the first fence.
|
||||
if (!this.getData('mathFlowOpen')) {
|
||||
this.setData('mathFlowOpen', true);
|
||||
this.setData('slurpOneLineEnding', true);
|
||||
this.buffer();
|
||||
}
|
||||
},
|
||||
mathFlowFenceMeta() {
|
||||
this.resume();
|
||||
},
|
||||
mathFlowValue(token) {
|
||||
this.raw(this.sliceSerialize(token));
|
||||
},
|
||||
mathText() {
|
||||
const value = this.encode(this.resume());
|
||||
this.tag('<span data-mx-maths="');
|
||||
this.tag(value);
|
||||
this.tag('"><code>');
|
||||
this.raw(value);
|
||||
this.tag('</code></span>');
|
||||
},
|
||||
mathTextData(token) {
|
||||
this.raw(this.sliceSerialize(token));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export {
|
||||
inlineExtension,
|
||||
spoilerExtension, spoilerExtensionHtml,
|
||||
mathExtensionHtml,
|
||||
};
|
||||
@@ -171,7 +171,8 @@ export function isCrossVerified(deviceId) {
|
||||
const deviceTrust = crossSignInfo.checkDeviceTrust(crossSignInfo, deviceInfo, false, true);
|
||||
return deviceTrust.isCrossSigningVerified();
|
||||
} catch {
|
||||
return false;
|
||||
// device does not support encryption
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ const urlSchemes = ['https', 'http', 'ftp', 'mailto', 'magnet'];
|
||||
|
||||
const permittedTagToAttributes = {
|
||||
font: ['style', 'data-mx-bg-color', 'data-mx-color', 'color'],
|
||||
span: ['style', 'data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'data-mx-pill', 'data-mx-ping'],
|
||||
span: ['style', 'data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'data-mx-maths', 'data-mx-pill', 'data-mx-ping'],
|
||||
div: ['data-mx-maths'],
|
||||
a: ['name', 'target', 'href', 'rel'],
|
||||
img: ['width', 'height', 'alt', 'title', 'src', 'data-mx-emoticon'],
|
||||
o: ['start'],
|
||||
|
||||
@@ -1,17 +1,41 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
|
||||
import linkifyHtml from 'linkifyjs/html';
|
||||
import parse from 'html-react-parser';
|
||||
import twemoji from 'twemoji';
|
||||
import { sanitizeText } from './sanitize';
|
||||
|
||||
const Math = lazy(() => import('../app/atoms/math/Math'));
|
||||
|
||||
const mathOptions = {
|
||||
replace: (node) => {
|
||||
const maths = node.attribs?.['data-mx-maths'];
|
||||
if (maths) {
|
||||
return (
|
||||
<Suspense fallback={<code>{maths}</code>}>
|
||||
<Math
|
||||
content={maths}
|
||||
throwOnError={false}
|
||||
errorColor="var(--tc-danger-normal)"
|
||||
displayMode={node.name === 'div'}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} text - text to twemojify
|
||||
* @param {object|undefined} opts - options for tweomoji.parse
|
||||
* @param {boolean} [linkify=false] - convert links to html tags (default: false)
|
||||
* @param {boolean} [sanitize=true] - sanitize html text (default: true)
|
||||
* @param {boolean} [maths=false] - render maths (default: false)
|
||||
* @returns React component
|
||||
*/
|
||||
export function twemojify(text, opts, linkify = false, sanitize = true) {
|
||||
export function twemojify(text, opts, linkify = false, sanitize = true, maths = false) {
|
||||
if (typeof text !== 'string') return text;
|
||||
let content = text;
|
||||
|
||||
@@ -25,5 +49,5 @@ export function twemojify(text, opts, linkify = false, sanitize = true) {
|
||||
rel: 'noreferrer noopener',
|
||||
});
|
||||
}
|
||||
return parse(content);
|
||||
return parse(content, maths ? mathOptions : null);
|
||||
}
|
||||
Reference in new issue
Block a user