"@tanstack/react-query": "5.24.1",
"@tanstack/react-query-devtools": "5.24.1",
"@tanstack/react-virtual": "3.2.0",
- "@tippyjs/react": "4.2.6",
"@vanilla-extract/css": "1.9.3",
"@vanilla-extract/recipes": "0.3.0",
"@vanilla-extract/vite-plugin": "3.7.1",
"emojibase": "15.3.1",
"emojibase-data": "15.3.2",
"file-saver": "2.0.5",
- "flux": "4.0.3",
"focus-trap-react": "10.0.2",
"folds": "2.2.0",
- "formik": "2.4.6",
"html-dom-parser": "4.0.0",
"html-react-parser": "4.2.0",
"i18next": "23.12.2",
"millify": "6.1.0",
"pdfjs-dist": "4.2.67",
"prismjs": "1.30.0",
- "prop-types": "15.8.1",
"react": "18.2.0",
"react-aria": "3.29.1",
- "react-autosize-textarea": "7.1.0",
"react-blurhash": "0.2.0",
"react-colorful": "5.6.1",
"react-dom": "18.2.0",
"react-error-boundary": "4.0.13",
"react-google-recaptcha": "2.1.0",
"react-i18next": "15.0.0",
- "react-modal": "3.16.1",
"react-range": "1.8.14",
"react-router-dom": "6.20.0",
"sanitize-html": "2.12.1",
"slate-dom": "0.112.2",
"slate-history": "0.110.3",
"slate-react": "0.112.1",
- "tippy.js": "6.3.7",
"ua-parser-js": "1.0.35"
},
"devDependencies": {
"eslint-plugin-react": "7.31.11",
"eslint-plugin-react-hooks": "4.6.0",
"prettier": "2.8.1",
- "sass": "1.56.2",
"typescript": "4.9.4",
"vite": "5.4.19",
"vite-plugin-pwa": "0.20.5",
"node": ">= 8"
}
},
- "node_modules/@popperjs/core": {
- "version": "2.11.8",
- "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
- "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/popperjs"
- }
- },
"node_modules/@react-aria/breadcrumbs": {
"version": "3.5.20",
"resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.20.tgz",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
- "node_modules/@tippyjs/react": {
- "version": "4.2.6",
- "resolved": "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz",
- "integrity": "sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==",
- "dependencies": {
- "tippy.js": "^6.3.1"
- },
- "peerDependencies": {
- "react": ">=16.8",
- "react-dom": ">=16.8"
- }
- },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
"integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==",
"dev": true
},
- "node_modules/@types/hoist-non-react-statics": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz",
- "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==",
- "dependencies": {
- "@types/react": "*",
- "hoist-non-react-statics": "^3.3.0"
- }
- },
"node_modules/@types/is-hotkey": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.10.tgz",
"node_modules/@types/prop-types": {
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
- "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="
+ "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
+ "dev": true
},
"node_modules/@types/react": {
"version": "18.2.39",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz",
"integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==",
+ "dev": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"node_modules/@types/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.23.0.tgz",
- "integrity": "sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw=="
+ "integrity": "sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==",
+ "dev": true
},
"node_modules/@types/semver": {
"version": "7.5.8",
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/asap": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
- "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
- },
"node_modules/ast-types-flow": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
"node": ">= 4.0.0"
}
},
- "node_modules/autosize": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/autosize/-/autosize-4.0.4.tgz",
- "integrity": "sha512-5yxLQ22O0fCRGoxGfeLSNt3J8LB1v+umtpMnPW6XjkTWXKoN0AmXAIhelJcDtFT/Y/wYWmfE+oqU10Q0b8FhaQ=="
- },
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
"integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
"license": "MIT"
},
- "node_modules/computed-style": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/computed-style/-/computed-style-0.1.4.tgz",
- "integrity": "sha512-WpAmaKbMNmS3OProfHIdJiNleNJdgUrJfbKArXua28QF7+0CoZjlLn0lp6vlc+dl5r2/X9GQiQRQQU4BzSa69w=="
- },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"url": "https://opencollective.com/core-js"
}
},
- "node_modules/cross-fetch": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz",
- "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==",
- "dependencies": {
- "node-fetch": "^2.7.0"
- }
- },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"node": ">=0.8.x"
}
},
- "node_modules/exenv": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
- "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw=="
- },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"reusify": "^1.0.4"
}
},
- "node_modules/fbemitter": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-3.0.0.tgz",
- "integrity": "sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==",
- "dependencies": {
- "fbjs": "^3.0.0"
- }
- },
- "node_modules/fbjs": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz",
- "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==",
- "dependencies": {
- "cross-fetch": "^3.1.5",
- "fbjs-css-vars": "^1.0.0",
- "loose-envify": "^1.0.0",
- "object-assign": "^4.1.0",
- "promise": "^7.1.1",
- "setimmediate": "^1.0.5",
- "ua-parser-js": "^1.0.35"
- }
- },
- "node_modules/fbjs-css-vars": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
- "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="
- },
"node_modules/fdir": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
"integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
"dev": true
},
- "node_modules/flux": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/flux/-/flux-4.0.3.tgz",
- "integrity": "sha512-yKAbrp7JhZhj6uiT1FTuVMlIAT1J4jqEyBpFApi1kxpGZCvacMVc/t1pMQyotqHhAgvoE3bNvAykhCo2CLjnYw==",
- "dependencies": {
- "fbemitter": "^3.0.0",
- "fbjs": "^3.0.1"
- },
- "peerDependencies": {
- "react": "^15.0.2 || ^16.0.0 || ^17.0.0"
- }
- },
"node_modules/focus-trap": {
"version": "7.6.4",
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.4.tgz",
"is-callable": "^1.1.3"
}
},
- "node_modules/formik": {
- "version": "2.4.6",
- "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz",
- "integrity": "sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==",
- "funding": [
- {
- "type": "individual",
- "url": "https://opencollective.com/formik"
- }
- ],
- "dependencies": {
- "@types/hoist-non-react-statics": "^3.3.1",
- "deepmerge": "^2.1.1",
- "hoist-non-react-statics": "^3.3.0",
- "lodash": "^4.17.21",
- "lodash-es": "^4.17.21",
- "react-fast-compare": "^2.0.1",
- "tiny-warning": "^1.0.2",
- "tslib": "^2.0.0"
- },
- "peerDependencies": {
- "react": ">=16.8.0"
- }
- },
- "node_modules/formik/node_modules/deepmerge": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
- "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/fs-extra": {
"version": "11.3.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
"url": "https://opencollective.com/immer"
}
},
- "node_modules/immutable": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
- "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==",
- "dev": true
- },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"node": ">=10"
}
},
- "node_modules/line-height": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/line-height/-/line-height-0.3.1.tgz",
- "integrity": "sha512-YExecgqPwnp5gplD2+Y8e8A5+jKpr25+DzMbFdI1/1UAr0FJrTFv4VkHLf8/6B590i1wUPJWMKKldkd/bdQ//w==",
- "dependencies": {
- "computed-style": "~0.1.3"
- },
- "engines": {
- "node": ">= 4.0.0"
- }
- },
"node_modules/linkify-react": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/linkify-react/-/linkify-react-4.1.3.tgz",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
- "node_modules/lodash-es": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
- "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
- },
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"node": ">=6"
}
},
- "node_modules/promise": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
- "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
- "dependencies": {
- "asap": "~2.0.3"
- }
- },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"react": ">=16.4.1"
}
},
- "node_modules/react-autosize-textarea": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/react-autosize-textarea/-/react-autosize-textarea-7.1.0.tgz",
- "integrity": "sha512-BHpjCDkuOlllZn3nLazY2F8oYO1tS2jHnWhcjTWQdcKiiMU6gHLNt/fzmqMSyerR0eTdKtfSIqtSeTtghNwS+g==",
- "dependencies": {
- "autosize": "^4.0.2",
- "line-height": "^0.3.1",
- "prop-types": "^15.5.6"
- },
- "peerDependencies": {
- "react": "^0.14.0 || ^15.0.0 || ^16.0.0",
- "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0"
- }
- },
"node_modules/react-blurhash": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/react-blurhash/-/react-blurhash-0.2.0.tgz",
"react": ">=16.13.1"
}
},
- "node_modules/react-fast-compare": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
- "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
- },
"node_modules/react-google-recaptcha": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-2.1.0.tgz",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
- "node_modules/react-lifecycles-compat": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
- "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
- },
- "node_modules/react-modal": {
- "version": "3.16.1",
- "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.1.tgz",
- "integrity": "sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==",
- "dependencies": {
- "exenv": "^1.2.0",
- "prop-types": "^15.7.2",
- "react-lifecycles-compat": "^3.0.0",
- "warning": "^4.0.3"
- },
- "engines": {
- "node": ">=8"
- },
- "peerDependencies": {
- "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18",
- "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18"
- }
- },
"node_modules/react-property": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz",
"postcss": "^8.3.11"
}
},
- "node_modules/sass": {
- "version": "1.56.2",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.56.2.tgz",
- "integrity": "sha512-ciEJhnyCRwzlBCB+h5cCPM6ie/6f8HrhZMQOf5vlU60Y1bI1rx5Zb0vlDZvaycHsg/MqFfF1Eq2eokAa32iw8w==",
- "dev": true,
- "dependencies": {
- "chokidar": ">=3.0.0 <4.0.0",
- "immutable": "^4.0.0",
- "source-map-js": ">=0.6.2 <2.0.0"
- },
- "bin": {
- "sass": "sass.js"
- },
- "engines": {
- "node": ">=12.0.0"
- }
- },
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"node": ">= 0.4"
}
},
- "node_modules/setimmediate": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
- "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
- },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"node": ">=12.0.0"
}
},
- "node_modules/tippy.js": {
- "version": "6.3.7",
- "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
- "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
- "dependencies": {
- "@popperjs/core": "^2.9.0"
- }
- },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"node": ">=0.10.0"
}
},
- "node_modules/warning": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
- "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
- "dependencies": {
- "loose-envify": "^1.0.0"
- }
- },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"@tanstack/react-query": "5.24.1",
"@tanstack/react-query-devtools": "5.24.1",
"@tanstack/react-virtual": "3.2.0",
- "@tippyjs/react": "4.2.6",
"@vanilla-extract/css": "1.9.3",
"@vanilla-extract/recipes": "0.3.0",
"@vanilla-extract/vite-plugin": "3.7.1",
"emojibase": "15.3.1",
"emojibase-data": "15.3.2",
"file-saver": "2.0.5",
- "flux": "4.0.3",
"focus-trap-react": "10.0.2",
"folds": "2.2.0",
- "formik": "2.4.6",
"html-dom-parser": "4.0.0",
"html-react-parser": "4.2.0",
"i18next": "23.12.2",
"millify": "6.1.0",
"pdfjs-dist": "4.2.67",
"prismjs": "1.30.0",
- "prop-types": "15.8.1",
"react": "18.2.0",
"react-aria": "3.29.1",
- "react-autosize-textarea": "7.1.0",
"react-blurhash": "0.2.0",
"react-colorful": "5.6.1",
"react-dom": "18.2.0",
"react-error-boundary": "4.0.13",
"react-google-recaptcha": "2.1.0",
"react-i18next": "15.0.0",
- "react-modal": "3.16.1",
"react-range": "1.8.14",
"react-router-dom": "6.20.0",
"sanitize-html": "2.12.1",
"slate-dom": "0.112.2",
"slate-history": "0.110.3",
"slate-react": "0.112.1",
- "tippy.js": "6.3.7",
"ua-parser-js": "1.0.35"
},
"devDependencies": {
"eslint-plugin-react": "7.31.11",
"eslint-plugin-react-hooks": "4.6.0",
"prettier": "2.8.1",
- "sass": "1.56.2",
"typescript": "4.9.4",
"vite": "5.4.19",
"vite-plugin-pwa": "0.20.5",
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path fill="#231F20" d="M9,11H5c-1.1,0-2-0.9-2-2V5c0-1.1,0.9-2,2-2h4c1.1,0,2,0.9,2,2v4C11,10.1,10.1,11,9,11z"/>
-</g>
-<g>
- <path fill="#231F20" d="M19,11h-4c-1.1,0-2-0.9-2-2V5c0-1.1,0.9-2,2-2h4c1.1,0,2,0.9,2,2v4C21,10.1,20.1,11,19,11z"/>
-</g>
-<g>
- <path fill="#231F20" d="M9,21H5c-1.1,0-2-0.9-2-2v-4c0-1.1,0.9-2,2-2h4c1.1,0,2,0.9,2,2v4C11,20.1,10.1,21,9,21z"/>
-</g>
-<g>
- <path fill="#231F20" d="M19,21h-4c-1.1,0-2-0.9-2-2v-4c0-1.1,0.9-2,2-2h4c1.1,0,2,0.9,2,2v4C21,20.1,20.1,21,19,21z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M13.8,4.5l0.7,0.7l-3.4,3.4L7.7,9.7l-1-1l-1.4,1.4l3.5,3.5l-5.7,5.7l1.4,1.4l5.7-5.7l3.5,3.5l1.4-1.4l-1-1l1.1-3.4l3.4-3.4
- l0.7,0.7l1.4-1.4l-5.7-5.7L13.8,4.5z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <polygon points="12,2 15.1,8.6 22,9.6 17,14.8 18.2,22 12,18.6 5.8,22 7,14.8 2,9.6 8.9,8.6 "/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M13.8,4.7l0.7,0.7l-3.4,3.4L7.7,10l-1-1l-1.4,1.4l3.5,3.5l-5.7,5.7L4.6,21l5.7-5.7l3.5,3.5l1.4-1.4l-1-1l1.1-3.4l3.4-3.4
- l0.7,0.7L20.9,9l-5.7-5.7L13.8,4.7z M13.7,12l-1,2.9l-3.4-3.4l2.9-1l3.7-3.7l1.4,1.4L13.7,12z"/>
- <polygon points="10,3.3 7.8,3.3 7.8,1 6.3,1 6.3,3.3 4,3.3 4,4.8 6.3,4.8 6.3,7 7.8,7 7.8,4.8 10,4.8 "/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M14,11c2.2,0,4-1.8,4-4c0-2.2-1.8-4-4-4s-4,1.8-4,4C10,9.2,11.8,11,14,11z M14,5c1.1,0,2,0.9,2,2c0,1.1-0.9,2-2,2
- s-2-0.9-2-2C12,5.9,12.9,5,14,5z"/>
- <path d="M16,13h-4c-3.3,0-6,2.7-6,6v2h16v-2C22,15.7,19.3,13,16,13z M8,19c0-2.2,1.8-4,4-4h4c2.2,0,4,1.8,4,4H8z"/>
- <polygon points="8,9 5,9 5,6 3,6 3,9 0,9 0,11 3,11 3,14 5,14 5,11 8,11 "/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M19.5,5.4c-0.5-0.5-1-1-1.5-1.4c-1.7-1.3-3.8-2-6-2S7.7,2.8,6,4C5.5,4.4,5,4.9,4.5,5.4C2.9,7.2,2,9.5,2,12
- c0,2.5,0.9,4.8,2.5,6.6c0.5,0.5,1,1,1.5,1.4c1.7,1.3,3.8,2,6,2s4.3-0.8,6-2c0.5-0.4,1-0.9,1.5-1.4c1.6-1.8,2.5-4.1,2.5-6.6
- C22,9.5,21.1,7.2,19.5,5.4z M4,12c0-2,0.8-3.9,2-5.3C7.2,8.1,8,10,8,12c0,2-0.8,3.9-2,5.3C4.8,15.9,4,14,4,12z M12,20
- c-1.7,0-3.2-0.5-4.5-1.4C9.1,16.8,10,14.5,10,12c0-2.5-0.9-4.8-2.5-6.6C8.8,4.5,10.3,4,12,4s3.2,0.5,4.5,1.4C14.9,7.2,14,9.5,14,12
- c0,2.5,0.9,4.8,2.5,6.6C15.2,19.5,13.7,20,12,20z M18,17.3c-1.2-1.4-2-3.3-2-5.3c0-2,0.8-3.9,2-5.3c1.2,1.4,2,3.3,2,5.3
- C20,14,19.2,15.9,18,17.3z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,22c1.1,0,2-0.9,2-2h-4C10,21.1,10.9,22,12,22z"/>
- <path d="M20.1,18.1L20.1,18.1L16,14L9.2,7.2L7.8,5.8L5.9,3.9L4.5,5.3l2.1,2.1C6.2,8.2,6,9.1,6,10v6H4v2h13.2l1.5,1.5L20.1,18.1z
- M8,16v-6c0-0.4,0.1-0.7,0.1-1l7,7H8z"/>
- <path d="M12,6c2.2,0,4,1.8,4,4v1.2l2,2V10c0-3-2.2-5.4-5-5.9V3h-2v1.1c-0.6,0.1-1.1,0.3-1.6,0.5L11,6.1C11.3,6.1,11.6,6,12,6z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <circle cx="17" cy="8" r="3"/>
- <path d="M12,22c1.1,0,2-0.9,2-2h-4C10,21.1,10.9,22,12,22z"/>
- <path d="M18,12.9C17.7,13,17.3,13,17,13s-0.7,0-1-0.1V16H8v-6c0-2.2,1.8-4,4-4c0.1,0,0.3,0,0.4,0c0.3-0.7,0.7-1.3,1.3-1.8
- c-0.2-0.1-0.5-0.1-0.7-0.2V3h-2v1.1C8.2,4.6,6,7,6,10v6H4v2h16v-2h-2V12.9z"/>
- <path d="M6.3,4.3L4.9,2.9C3.1,4.7,2,7.2,2,10h2C4,7.8,4.9,5.8,6.3,4.3z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,22c1.1,0,2-0.9,2-2h-4C10,21.1,10.9,22,12,22z"/>
- <path d="M18,10c0-3-2.2-5.4-5-5.9V3h-2v1.1C8.2,4.6,6,7,6,10v6H4v2h16v-2h-2V10z M16,16H8v-6c0-2.2,1.8-4,4-4s4,1.8,4,4V16z"/>
- <path d="M6.3,4.3L4.9,2.9C3.1,4.7,2,7.2,2,10h2C4,7.8,4.9,5.8,6.3,4.3z"/>
- <path d="M19.1,2.9l-1.4,1.4C19.1,5.8,20,7.8,20,10h2C22,7.2,20.9,4.7,19.1,2.9z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,22c1.1,0,2-0.9,2-2h-4C10,21.1,10.9,22,12,22z"/>
- <path d="M18,16v-6c0-3-2.2-5.4-5-5.9V3h-2v1.1C8.2,4.6,6,7,6,10v6H4v2h16v-2H18z M16,16H8v-6c0-2.2,1.8-4,4-4s4,1.8,4,4V16z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <g>
- <g>
- <rect x="9" y="8" width="2" height="8"/>
- </g>
- <g>
- <rect x="13" y="8" width="2" height="8"/>
- </g>
- </g>
- <path d="M21,3h-5l-1.4-1.4C14.2,1.2,13.7,1,13.2,1h-2.3c-0.5,0-1,0.2-1.4,0.6L8,3H3v2h2v14c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2V5h2
- V3z M17,19H7V5h10V19z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M12,2c-0.3,0-0.6,0-0.9,0.1c-3.5,0.4-6.4,3.2-7,6.7c-0.5,3.1,0.8,6,3.1,7.7C7.7,16.8,8,17.4,8,18v4h8v-4
- c0-0.6,0.3-1.2,0.8-1.6c1.9-1.5,3.2-3.8,3.2-6.4C20,5.6,16.4,2,12,2z M15.6,14.8c-1,0.7-1.6,1.9-1.6,3.2v2h-1.3v-8.1
- c0.7-0.3,1.3-1,1.3-1.9c0-1.1-0.9-2-2-2s-2,0.9-2,2c0,0.8,0.5,1.6,1.3,1.9V20H10v-2c0-1.2-0.6-2.4-1.6-3.2C6.6,13.4,5.7,11.3,6.1,9
- c0.4-2.6,2.6-4.7,5.2-5c0.2,0,0.5,0,0.7,0c3.3,0,6,2.7,6,6C18,11.9,17.1,13.7,15.6,14.8z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path fill="#221F1F" d="M9,5v4H5V5H9 M9,3H5C3.9,3,3,3.9,3,5v4c0,1.1,0.9,2,2,2h4c1.1,0,2-0.9,2-2V5C11,3.9,10.1,3,9,3L9,3z"/>
- <path fill="#221F1F" d="M19,5v4h-4V5H19 M19,3h-4c-1.1,0-2,0.9-2,2v4c0,1.1,0.9,2,2,2h4c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3L19,3
- z"/>
- <path fill="#221F1F" d="M9,15v4H5v-4H9 M9,13H5c-1.1,0-2,0.9-2,2v4c0,1.1,0.9,2,2,2h4c1.1,0,2-0.9,2-2v-4C11,13.9,10.1,13,9,13
- L9,13z"/>
- <path fill="#221F1F" d="M19,15v4h-4v-4H19 M19,13h-4c-1.1,0-2,0.9-2,2v4c0,1.1,0.9,2,2,2h4c1.1,0,2-0.9,2-2v-4
- C21,13.9,20.1,13,19,13L19,13z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<polygon points="9.5,14.1 6,10.6 4.6,12 8.1,15.5 9.5,16.9 19.4,7 18,5.6 "/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <polygon points="4.2,9.2 5.6,7.8 12,14.2 18.4,7.8 19.8,9.2 12,17 "/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <polygon points="14.8,4.2 16.2,5.6 9.8,12 16.2,18.4 14.8,19.8 7,12 "/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <polygon points="9.2,4.2 7.8,5.6 14.2,12 7.8,18.4 9.2,19.8 17,12 "/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <polygon points="4.2,14.8 5.6,16.2 12,9.8 18.4,16.2 19.8,14.8 12,7 "/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8s8,3.6,8,8
- S16.4,20,12,20z"/>
- <polygon points="13,7 11,7 11,11 7,11 7,13 11,13 11,17 13,17 13,13 17,13 17,11 13,11 "/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M20,4H4C2.9,4,2,4.9,2,6v12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V6C22,4.9,21.1,4,20,4z M20,18H4V6h16V18z"/>
- <polygon points="7.5,16.5 12.1,12 7.5,7.5 6.5,8.5 9.9,12 6.5,15.5 "/>
- <rect x="13" y="14.5" width="5" height="1.5"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8s8,3.6,8,8
- S16.4,20,12,20z"/>
- <path d="M13,11.3h-2c-0.7,0-1.3-0.6-1.3-1.3s0.6-1.3,1.3-1.3h4V7.3h-2.3V6h-1.5v1.3H11c-1.5,0-2.8,1.2-2.8,2.8s1.2,2.8,2.8,2.8h2
- c0.7,0,1.3,0.6,1.3,1.3s-0.6,1.3-1.3,1.3H9v1.5h2.3V18h1.5v-1.3H13c1.5,0,2.8-1.2,2.8-2.8S14.5,11.3,13,11.3z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<polygon points="17.7,16.2 13.4,12 17.7,7.8 16.2,6.3 12,10.6 7.8,6.3 6.3,7.8 10.6,12 6.3,16.2 7.8,17.7 12,13.4 16.2,17.7 "/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M23,12c0-2.4-1.7-4.4-4-4.9V5c0-1.1-0.9-2-2-2H5C3.9,3,3,3.9,3,5v10c0,3.3,2.7,6,6,6h4c2.6,0,4.9-1.7,5.7-4.1
- C21.1,16.6,23,14.5,23,12z M17,5v2H5V5H17z M13,19H9c-2.2,0-4-1.8-4-4V9h12v6C17,17.2,15.2,19,13,19z M19,14.8V9.2
- c1.2,0.4,2,1.5,2,2.8S20.2,14.4,19,14.8z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <circle cx="10" cy="11" r="1"/>
- <circle cx="14" cy="11" r="1"/>
- <path d="M20.3,16.2C20,14.7,19.6,13,19,11.3c0.6-2.1,1.6-6.7,0.4-8.4c-0.3-0.5-0.8-0.7-1.4-0.7c-1.2,0-2.8,1.3-3.7,2.3
- C13.6,4.2,12.8,4,12,4s-1.6,0.2-2.3,0.6c-1-1-2.5-2.3-3.7-2.3c-0.6,0-1.1,0.2-1.4,0.7C3.4,4.6,4.4,9.2,5,11.3
- c-0.6,1.7-1,3.4-1.3,4.9C3.2,18.7,5.1,21,7.6,21h8.7C18.9,21,20.8,18.7,20.3,16.2z M5.8,3.8C5.9,3.8,5.9,3.8,6,3.8
- c0.5,0,1.5,0.8,2.5,1.7C7.5,6.3,6.7,7.6,6,9C5.6,6.8,5.3,4.5,5.8,3.8z M17.9,18.3c-0.4,0.5-1,0.7-1.6,0.7H7.6
- c-0.6,0-1.2-0.3-1.6-0.7c-0.2-0.3-0.6-0.8-0.4-1.6C7,10,9.4,6,12,6s5,4,6.3,10.6C18.5,17.4,18.1,18,17.9,18.3z M18,9
- c-0.7-1.4-1.5-2.6-2.5-3.5c0.9-0.9,2-1.7,2.5-1.7c0.1,0,0.1,0,0.2,0.1C18.7,4.5,18.5,6.6,18,9z"/>
- <path d="M12.6,14h-1.2c-0.8,0-1.4,0.6-1.4,1.4v0c0,0.4,0.1,0.7,0.4,1l0.9,0.9c0.4,0.4,1,0.4,1.4,0l0.9-0.9c0.3-0.3,0.4-0.6,0.4-1v0
- C14,14.6,13.4,14,12.6,14z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <g>
- <path d="M19,21H5c-1.1,0-2-0.9-2-2v-3h2v3h14v-3h2v3C21,20.1,20.1,21,19,21z"/>
- </g>
- <polygon points="15.3,11.3 13,13.6 13,3 11,3 11,13.6 8.7,11.3 7.3,12.7 12,17.4 16.7,12.7 "/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8V2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10h-2C20,16.4,16.4,20,12,20z"/>
- <circle cx="9.5" cy="8.5" r="1.5"/>
- <circle cx="14.5" cy="8.5" r="1.5"/>
- <path d="M6,12c0,3.3,2.7,6,6,6s6-2.7,6-6h-2c0,2.2-1.8,4-4,4s-4-1.8-4-4H6z"/>
- <polygon points="20.8,3.3 20.8,0 19.3,0 19.3,3.3 16,3.3 16,4.8 19.3,4.8 19.3,8 20.8,8 20.8,4.8 24,4.8 24,3.3 "/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8s8,3.6,8,8
- S16.4,20,12,20z"/>
- <circle cx="9.5" cy="8.5" r="1.5"/>
- <circle cx="14.5" cy="8.5" r="1.5"/>
- <path d="M16,12c0,2.2-1.8,4-4,4s-4-1.8-4-4H6c0,3.3,2.7,6,6,6s6-2.7,6-6H16z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8s8,3.6,8,8
- S16.4,20,12,20z"/>
- <path d="M9,12l3,6l3-6l-3-6L9,12z M13,12c0,0.6-0.4,1-1,1c-0.6,0-1-0.4-1-1c0-0.6,0.4-1,1-1C12.6,11,13,11.4,13,12z"/>
-</g>
-</svg>
+++ /dev/null
-<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M14 3V5H17.8L12.9 9.9L14.3 11.3L19 6.6V10.2H21V3H14Z" fill="black"/>
-<path d="M5 5H10V3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V14.2H19V19H5V5Z" fill="black"/>
-</svg>
+++ /dev/null
-<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path fill-rule="evenodd" clip-rule="evenodd" d="M4.92896 3.51471L3.51474 4.92892L5.97515 7.38933C4.46742 8.5776 3.32116 9.93994 2.7 10.8C2.1 11.5 2.1 12.5 2.7 13.2C4 15 7.6 19 12 19C13.5709 19 15.0398 18.4902 16.3384 17.7526L19.0711 20.4853L20.4853 19.0711L4.92896 3.51471ZM4.2 12C4.68291 11.3561 5.85678 9.9637 7.39721 8.81139L9.29238 10.7066C9.10496 11.0982 9 11.5368 9 12C9 13.6569 10.3431 15 12 15C12.4632 15 12.9018 14.895 13.2934 14.7076L14.8573 16.2715C13.9566 16.7128 12.9896 17 12 17C8.4 17 5.1 13.2 4.2 12Z" fill="black"/>
-<path d="M9.6226 5.37995L11.2906 7.04797C11.5254 7.01661 11.762 7 12 7C15.6 7 18.9 10.8 19.8 12C19.493 12.4094 18.9066 13.1213 18.1244 13.8817L19.5194 15.2768C20.2973 14.4974 20.9049 13.7471 21.3 13.2C21.9 12.5 21.9 11.5 21.3 10.8C20 9 16.4 5 12 5C11.1762 5 10.3805 5.14021 9.6226 5.37995Z" fill="black"/>
-</svg>
+++ /dev/null
-<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12 19C7.6 19 4 15 2.7 13.2C2.1 12.5 2.1 11.5 2.7 10.8C4 9 7.6 5 12 5C16.4 5 20 9 21.3 10.8C21.9 11.5 21.9 12.5 21.3 13.2C20 15 16.4 19 12 19ZM12 7C8.4 7 5.1 10.8 4.2 12C5.1 13.2 8.4 17 12 17C15.6 17 18.9 13.2 19.8 12C18.9 10.8 15.6 7 12 7Z" fill="black"/>
-<path d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" fill="black"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M13.8,2H6C4.9,2,4,2.9,4,4v16c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V8.2L13.8,2z M18,20H6V4h7v3c0,1.1,0.9,2,2,2h3V20z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M18.5,5l-2.1,2.8L15.5,9l0.9,1.2l2.1,2.8H9H7v2v4H5V5H18.5 M21,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h2c1.1,0,2-0.9,2-2
- v-4h12v-2l-3-4l3-4V3L21,3z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <polygon points="16,12 14,12 14,14 10,14 10,10 12,10 12,8 10,8 10,3 8,3 8,8 3,8 3,10 8,10 8,14 3,14 3,16 8,16 8,21 10,21 10,16
- 14,16 14,21 16,21 16,16 21,16 21,14 16,14 "/>
- <path d="M18.5,1C16,1,14,3,14,5.5s2,4.5,4.5,4.5S23,8,23,5.5S21,1,18.5,1z M17.5,7C17.5,7,17.5,7,17.5,7c-0.2-0.1-0.3-0.1-0.3-0.2
- c-0.6-0.5-1.7-1.1-1.8-2c-0.1-0.7,0.8-1.6,1.3-2c0.8-0.6,2.3-1.1,3.2-0.5c0.6,0.4-1.2,1-1.4,1.3c-0.3,0.4-0.3,0.9-0.3,1.4
- c0,0.5,0.1,1.2-0.2,1.6C17.9,6.9,17.7,7,17.5,7z M20.8,7.9c-0.4,0.3-0.9,0.2-1.3,0.5c-0.1,0.1-0.2,0.2-0.3,0.2
- c-0.2,0.1-0.5-0.1-0.5-0.3c-0.3-0.8,0.3-1.2,0.9-1.3c0.3,0,0.7,0,1,0c0.2,0,0.4,0.1,0.4,0.3C21.1,7.5,20.9,7.7,20.8,7.9z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <polygon points="16,12 14,12 14,14 10,14 10,10 12,10 12,8 10,8 10,3 8,3 8,8 3,8 3,10 8,10 8,14 3,14 3,16 8,16 8,21 10,21 10,16
- 14,16 14,21 16,21 16,16 21,16 21,14 16,14 "/>
- <path d="M21,4V3c0-1.7-1.3-3-3-3s-3,1.3-3,3v1c-0.6,0-1,0.4-1,1v4c0,0.6,0.4,1,1,1h6c0.6,0,1-0.4,1-1V5C22,4.4,21.6,4,21,4z
- M19.5,4h-3V3c0-0.8,0.7-1.5,1.5-1.5s1.5,0.7,1.5,1.5V4z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <polygon points="16,12 14,12 14,14 10,14 10,10 12,10 12,8 10,8 10,3 8,3 8,8 3,8 3,10 8,10 8,14 3,14 3,16 8,16 8,21 10,21 10,16
- 14,16 14,21 16,21 16,16 21,16 21,14 16,14 "/>
- <g>
- <path d="M19,0c-2.8,0-5,2.2-5,5s2.2,5,5,5s5-2.2,5-5S21.8,0,19,0z M22,5.8h-2.3V8h-1.5V5.8H16V4.3h2.3V2h1.5v2.3H22V5.8z"/>
- </g>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <polygon points="16.1,12 14.1,12 14.1,14 10.1,14 10.1,10 12.1,10 12.1,8 10.1,8 10.1,3 8.1,3 8.1,8 3.1,8 3.1,10 8.1,10 8.1,14
- 3.1,14 3.1,16 8.1,16 8.1,21 10.1,21 10.1,16 14.1,16 14.1,21 16.1,21 16.1,16 21.1,16 21.1,14 16.1,14 "/>
- <path d="M24,9l-2.7-2.7c0.5-0.7,0.8-1.5,0.8-2.3c0-2.2-1.8-4-4-4s-4,1.8-4,4s1.8,4,4,4c0.8,0,1.5-0.2,2.2-0.6l2.7,2.7L24,9z
- M18.1,6.5c-1.4,0-2.5-1.1-2.5-2.5s1.1-2.5,2.5-2.5s2.5,1.1,2.5,2.5S19.5,6.5,18.1,6.5z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <polygon points="16,12 14,12 14,14 10,14 10,10 12,10 12,8 10,8 10,3 8,3 8,8 3,8 3,10 8,10 8,14 3,14 3,16 8,16 8,21 10,21 10,16
- 14,16 14,21 16,21 16,16 21,16 21,14 16,14 "/>
- <path d="M18,0l-4,2v2c0,1.9,0.9,3.7,2.4,4.8L18,10l1.6-1.2C21.1,7.7,22,5.9,22,4V2L18,0z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M21,10V8h-5V3h-2v5h-4V3H8v5H3v2h5v4H3v2h5v5h2v-5h4v5h2v-5h5v-2h-5v-4H21z M14,14h-4v-4h4V14z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M16.6,5.1c0.9,0,1.8,0.4,2.5,1c0.7,0.7,1,1.5,1,2.5c0,0.9-0.4,1.8-1,2.5l-0.7,0.7L12,18.2l-7.1-7.1c-0.7-0.7-1-1.5-1-2.5
- c0-0.9,0.4-1.8,1-2.5c0.7-0.7,1.5-1,2.5-1s1.8,0.4,2.5,1l0.7,0.7L12,8.2l1.4-1.4l0.7-0.7C14.8,5.5,15.7,5.1,16.6,5.1 M16.6,3.1
- c-1.4,0-2.8,0.5-3.9,1.6L12,5.4l-0.7-0.7c-1.1-1.1-2.5-1.6-3.9-1.6C6,3.1,4.6,3.6,3.5,4.7c-2.2,2.1-2.2,5.6,0,7.8L12,21l7.8-7.8
- l0.7-0.7c1.1-1.1,1.6-2.5,1.6-3.9c0-1.4-0.5-2.8-1.6-3.9C19.4,3.6,18,3.1,16.6,3.1L16.6,3.1z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,3L2,12h3v7c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2v-7h3L12,3z M17,12v7H7v-7v-1.8l5-4.5l5,4.5V12z"/>
- <circle cx="12" cy="14" r="2"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <circle cx="5" cy="12" r="2"/>
- <circle cx="12" cy="12" r="2"/>
- <circle cx="19" cy="12" r="2"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M19,12V5c0-1.1-0.9-2-2-2H7C5.9,3,5,3.9,5,5v7c-1.1,0-2,0.9-2,2v5c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2v-5
- C21,12.9,20.1,12,19,12z M7,5h10v7h-3v1c0,1.1-0.9,2-2,2c-1.1,0-2-0.9-2-2v-1H7V5z M19,19H5v-5h3.1c0.4,1.7,2,3,3.9,3
- c1.9,0,3.4-1.3,3.9-3H19V19z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8s8,3.6,8,8
- S16.4,20,12,20z"/>
- <g>
- <polygon points="12.8,15.5 12.8,10 12.2,10 11.3,10 10,10 10,11 11.3,11.5 11.3,15.5 10,16 10,17 14,17 14,16 "/>
- <circle cx="12" cy="8" r="1"/>
- </g>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M16.3,7.3l-1.4,1.4l2.3,2.3H8.2c-0.8,0-1.6-0.3-2.1-0.9C5.6,9.6,5.2,8.8,5.2,8s0.3-1.6,0.9-2.1l1.2-1.2L5.9,3.3L4.7,4.5
- c-1.9,1.9-1.9,5.1,0,7.1C5.7,12.5,6.9,13,8.2,13h8.9l-2.3,2.3l1.4,1.4L21,12L16.3,7.3z"/>
- <polygon points="9,17.3 6.8,17.3 6.8,15 5.3,15 5.3,17.3 3,17.3 3,18.8 5.3,18.8 5.3,21 6.8,21 6.8,18.8 9,18.8 "/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M16.3,7.3l-1.4,1.4l2.3,2.3H8.2c-0.8,0-1.6-0.3-2.1-0.9C5.6,9.6,5.2,8.8,5.2,8s0.3-1.6,0.9-2.1l1.2-1.2L5.9,3.3L4.7,4.5
- c-1.9,1.9-1.9,5.1,0,7.1C5.7,12.5,6.9,13,8.2,13h8.9l-2.3,2.3l1.4,1.4L21,12L16.3,7.3z"/>
- <polygon points="8.7,19.6 7.1,18 8.7,16.4 7.6,15.4 6,17 4.4,15.4 3.3,16.4 4.9,18 3.3,19.6 4.4,20.7 6,19.1 7.6,20.7 "/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <polygon points="19.8,16 18.3,16 18.3,18.3 16,18.3 16,19.8 18.3,19.8 18.3,22 19.8,22 19.8,19.8 22,19.8 22,18.3 19.8,18.3 "/>
- <path d="M20,4H4C2.9,4,2,4.9,2,6v12c0,1.1,0.9,2,2,2h10v-2H4V9.9l8,5.3l8-5.3V14h2V6C22,4.9,21.1,4,20,4z M20,7.5l-8,5.3L4,7.5V6
- h16V7.5z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M4.6,4.5l1.2-1.2l1.4,1.4L6,5.9C4.8,7,4.8,9,6,10.1C6.6,10.7,7.3,11,8.1,11H17l-2.3-2.3l1.4-1.4l4.7,4.7l-4.7,4.7l-1.4-1.4
- L17,13H8.1c-1.3,0-2.6-0.5-3.5-1.5C2.6,9.6,2.6,6.4,4.6,4.5z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M19.4,4.5l-1.2-1.2l-1.4,1.4L18,5.9c1.2,1.2,1.2,3.1,0,4.2c-0.6,0.6-1.3,0.9-2.1,0.9H7l2.3-2.3L7.8,7.3L3.1,12l4.7,4.7
- l1.4-1.4L7,13h8.9c1.3,0,2.6-0.5,3.5-1.5C21.4,9.6,21.4,6.4,19.4,4.5z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M18,10h-1V7.2c0-2.6-1.9-4.9-4.5-5.2C9.5,1.7,7,4.1,7,7v3H6c-1.1,0-2,0.9-2,2v8c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-8
- C20,10.9,19.1,10,18,10z M9,7c0-1.7,1.3-3,3-3c1.7,0,3,1.3,3,3v3H9V7z M18,20H6v-8h12V20z"/>
- <circle cx="12" cy="16" r="2"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <polygon points="12,7 12,7 8,11 4,7 4,7 4,7 2,7 2,17 4,17 4,9.8 8,13.8 12,9.8 12,17 14,17 14,7 12,7 "/>
- <path d="M20,14V7h-2v7h-2l3,3c0.1,0,0.5-0.4,1-0.9c0.9-0.9,2-2.1,2-2.1H20z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <rect x="7" y="12" fill="#010101" width="10" height="2"/>
- <g>
- <circle fill="#010101" cx="19" cy="6" r="4"/>
- </g>
- <path fill="#010101" d="M13.3,8H7v2h7.5C14,9.4,13.6,8.7,13.3,8z"/>
- <path fill="#010101" d="M19,12v5.6l-2.4-1.3L16.1,16h-0.5H5V6h8c0-0.7,0.1-1.4,0.3-2H4.8C3.8,4,3,4.9,3,6v10c0,1.1,0.8,2,1.8,2
- h10.8l5.4,3v-9.3C20.4,11.9,19.7,12,19,12z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path fill="#010101" d="M19,6v11.6l-2.4-1.3L16.1,16h-0.5H5V6H19 M19.2,4H4.8C3.8,4,3,4.9,3,6v10c0,1.1,0.8,2,1.8,2h10.8l5.4,3V6
- C21,4.9,20.2,4,19.2,4L19.2,4z"/>
- <rect x="7" y="8" fill="#010101" width="10" height="2"/>
- <rect x="7" y="12" fill="#010101" width="10" height="2"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <g>
- <rect x="9" y="8" width="2" height="8"/>
- </g>
- <g>
- <rect x="13" y="8" width="2" height="8"/>
- </g>
- <path d="M12,4c4.4,0,8,3.6,8,8s-3.6,8-8,8s-8-3.6-8-8S7.6,4,12,4 M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2
- L12,2z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M20,12c0,1.6-0.5,3.1-1.3,4.3L13,10.6V4.1
- C16.9,4.6,20,7.9,20,12z M11,19.9c-1.7-0.2-3.2-1-4.4-2.1l4.4-4.4V19.9z M13,13.4l4.4,4.4c-1.2,1.1-2.7,1.9-4.4,2.1V13.4z M11,4.1
- v6.5l-5.7,5.7C4.5,15.1,4,13.6,4,12C4,7.9,7.1,4.6,11,4.1z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M20.6,5.6l-2.2-2.2C18,3,17.5,2.8,17,2.8S16,3,15.6,3.4L3,16v5h5L20.6,8.4C21.4,7.6,21.4,6.4,20.6,5.6z M7.2,19H5v-2.2
- l9.2-9.2l2.2,2.2L7.2,19z M15.6,6.2L17,4.8c0,0,0,0,0,0L19.2,7l-1.4,1.4L15.6,6.2z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M19,5v12.7l-5-5.8l-2,2L8.9,9.6
- l-3.9,7V5H19z M18.2,19H5.4l3.7-6.6l2.8,3.8l2-2L18.2,19z"/>
- <circle cx="14.5" cy="8.5" r="1.5"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M13.8,4.5l0.7,0.7l-3.4,3.4L7.7,9.7l-1-1l-1.4,1.4l3.5,3.5l-5.7,5.7l1.4,1.4l5.7-5.7l3.5,3.5l1.4-1.4l-1-1l1.1-3.4l3.4-3.4
- l0.7,0.7l1.4-1.4l-5.7-5.7L13.8,4.5z M13.7,11.8l-1,2.9l-3.4-3.4l2.9-1l3.7-3.7l1.4,1.4L13.7,11.8z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,4c4.4,0,8,3.6,8,8s-3.6,8-8,8s-8-3.6-8-8S7.6,4,12,4 M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2
- L12,2z"/>
- <polygon points="10,8 10,16 16,12 "/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<polygon points="19,11 13,11 13,5 11,5 11,11 5,11 5,13 11,13 11,19 13,19 13,13 19,13 "/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M17.6,5l-1.3,1.6C18,7.8,19,9.8,19,12c0,3.9-3.1,7-7,7s-7-3.1-7-7c0-2.2,1-4.2,2.6-5.5L6.4,5C4.3,6.6,3,9.2,3,12
- c0,5,4,9,9,9c5,0,9-4,9-9C21,9.2,19.7,6.6,17.6,5z"/>
- <rect x="11" y="2" width="2" height="10"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <polygon points="11,7 11,13 14.5,16.5 15.9,15.1 13,12.2 13,7 "/>
- <path d="M12,2C8.7,2,5.8,3.6,4,6V4H2.5v5h5V7.5H5.4C6.9,5.4,9.3,4,12,4c4.4,0,8,3.6,8,8s-3.6,8-8,8s-8-3.6-8-8H2
- c0,5.5,4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z"/>
-</g>
-<g>
- <polygon points="49,44 49,50 53.2,54.2 54.7,52.8 51,49.2 51,44 "/>
- <polygon points="45.5,47 40.5,47 40.5,42 42,42 42,45.5 45.5,45.5 "/>
- <path d="M50,40c-4.1,0-7.6,2.5-9.2,6h2.2c1.4-2.4,4-4,6.9-4c4.4,0,8,3.6,8,8s-3.6,8-8,8s-8-3.6-8-8h-2c0,5.5,4.5,10,10,10
- s10-4.5,10-10S55.5,40,50,40z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M21,13v-2H9c-1.1,0-2,0.9-2,2v4.6l-2.3-2.3l-1.4,1.4L8,21.4l4.7-4.7l-1.4-1.4L9,17.6V13H21z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M21.7,20.3l-5.4-5.4c1.1-1.4,1.7-3.1,1.7-4.9c0-4.4-3.6-8-8-8s-8,3.6-8,8s3.6,8,8,8c1.8,0,3.5-0.6,4.9-1.7l5.4,5.4
- L21.7,20.3z M10,16c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S13.3,16,10,16z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M4,5.2L17.5,12L4,18.8v-4.3l1.6-0.5l5.7-1.9l-5.7-1.9L4,9.6V5.2 M2,2v11v-2l3,1l-3,1v9l20-10L2,2L2,2z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M13,4v0.6v1.3l1.2,0.5c0.5,0.2,1,0.5,1.4,0.8l1.1,0.8l1.2-0.7l0.5-0.3l1,1.7l-0.5,0.3l-1.2,0.7l0.2,1.3
- c0,0.3,0.1,0.6,0.1,0.8s0,0.5-0.1,0.8l-0.2,1.3l1.2,0.7l0.5,0.3l-1,1.7l-0.5-0.3l-1.2-0.7l-1.1,0.8c-0.4,0.3-0.9,0.6-1.4,0.8
- L13,18.1v1.3V20h-2v-0.6v-1.3l-1.2-0.5c-0.5-0.2-1-0.5-1.4-0.8l-1.1-0.8l-1.2,0.7l-0.5,0.3l-1-1.7l0.5-0.3l1.2-0.7l-0.2-1.3
- C6,12.5,6,12.2,6,12s0-0.5,0.1-0.8l0.2-1.3L5.1,9.2L4.6,8.9l1-1.7l0.5,0.3l1.2,0.7l1.1-0.8c0.4-0.3,0.9-0.6,1.4-0.8L11,5.9V4.6V4
- H13 M13,2h-2C9.9,2,9,2.9,9,4v0.6C8.3,4.9,7.7,5.2,7.1,5.7L6.6,5.4c-0.3-0.2-0.7-0.3-1-0.3c-0.7,0-1.4,0.4-1.7,1l-1,1.7
- c-0.6,1-0.2,2.2,0.7,2.7l0.5,0.3C4,11.3,4,11.6,4,12s0,0.7,0.1,1.1l-0.5,0.3c-1,0.6-1.3,1.8-0.7,2.7l1,1.7c0.4,0.6,1,1,1.7,1
- c0.3,0,0.7-0.1,1-0.3l0.5-0.3c0.6,0.5,1.2,0.8,1.9,1.1V20c0,1.1,0.9,2,2,2h2c1.1,0,2-0.9,2-2v-0.6c0.7-0.3,1.3-0.7,1.9-1.1l0.5,0.3
- c0.3,0.2,0.7,0.3,1,0.3c0.7,0,1.4-0.4,1.7-1l1-1.7c0.6-1,0.2-2.2-0.7-2.7l-0.5-0.3C20,12.7,20,12.4,20,12s0-0.7-0.1-1.1l0.5-0.3
- c1-0.6,1.3-1.8,0.7-2.7l-1-1.7c-0.4-0.6-1-1-1.7-1c-0.3,0-0.7,0.1-1,0.3l-0.5,0.3c-0.6-0.5-1.2-0.8-1.9-1.1V4C15,2.9,14.1,2,13,2
- L13,2z"/>
- <g>
- <path d="M12,16c-2.2,0-4-1.8-4-4s1.8-4,4-4s4,1.8,4,4S14.2,16,12,16z M12,10c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S13.1,10,12,10z
- "/>
- </g>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M12,2L3,6v7c0,5,4,9,9,9c5,0,9-4,9-9V6L12,2z M19,13c0,3.9-3.1,7-7,7s-7-3.1-7-7V7.3l7-3.1l7,3.1V13z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,2L3,6v7c0,5,4,9,9,9c5,0,9-4,9-9V6L12,2z M19,13c0,3.9-3.1,7-7,7s-7-3.1-7-7V7.3l7-3.1l7,3.1V13z"/>
- <circle cx="12" cy="9" r="2"/>
- <path d="M15,16H9v-1c0-1.7,1.3-3,3-3h0c1.7,0,3,1.3,3,3V16z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,2L3,6v7c0,5,4,9,9,9c5,0,9-4,9-9V6L12,2z M19,13c0,3.9-3.1,7-7,7s-7-3.1-7-7V7.3l7-3.1l7,3.1V13z"/>
- <path d="M10,11c0,0.7,0.4,1.4,1,1.7V16h2v-3.3c0.6-0.3,1-1,1-1.7c0-1.1-0.9-2-2-2S10,9.9,10,11z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M15.4,13.7L14,14l-0.2,1.4c-0.4,2.8-1.3,4.2-1.7,4.6c-0.4-0.3-1.3-1.8-1.7-4.6L10,14l-1.4-0.2c-2.8-0.4-4.2-1.3-4.6-1.7
- c0.3-0.4,1.8-1.3,4.6-1.7L10,10l0.2-1.4c0.4-2.8,1.3-4.2,1.7-4.6V2c-1.7,0-3.1,2.6-3.7,6.3C4.6,8.9,2,10.3,2,12s2.6,3.1,6.3,3.7
- c0.6,3.7,2,6.3,3.7,6.3s3.1-2.6,3.7-6.3c3.7-0.6,6.3-2,6.3-3.7h-2.1C19.6,12.4,18.2,13.3,15.4,13.7z"/>
- <path d="M18.5,1C16,1,14,3,14,5.5s2,4.5,4.5,4.5S23,8,23,5.5S21,1,18.5,1z M17.5,7C17.5,7,17.5,7,17.5,7c-0.2-0.1-0.3-0.1-0.3-0.2
- c-0.6-0.5-1.7-1.1-1.8-2c-0.1-0.7,0.8-1.6,1.3-2c0.8-0.6,2.3-1.1,3.2-0.5c0.6,0.4-1.2,1-1.4,1.3c-0.3,0.4-0.3,0.9-0.3,1.4
- c0,0.5,0.1,1.2-0.2,1.6C17.9,6.9,17.7,7,17.5,7z M20.8,7.9c-0.4,0.3-0.9,0.2-1.3,0.5c-0.1,0.1-0.2,0.2-0.3,0.2
- c-0.2,0.1-0.5-0.1-0.5-0.3c-0.3-0.8,0.3-1.2,0.9-1.3c0.3,0,0.7,0,1,0c0.2,0,0.4,0.1,0.4,0.3C21.1,7.5,20.9,7.7,20.8,7.9z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M21,4V3c0-1.7-1.3-3-3-3s-3,1.3-3,3v1c-0.6,0-1,0.4-1,1v4c0,0.6,0.4,1,1,1h6c0.6,0,1-0.4,1-1V5C22,4.4,21.5,4,21,4z
- M19.5,4h-3V3c0-0.8,0.7-1.5,1.5-1.5s1.5,0.7,1.5,1.5V4z"/>
- <path d="M15.4,13.7L14,14l-0.2,1.4c-0.4,2.8-1.3,4.2-1.7,4.6c-0.4-0.3-1.3-1.8-1.7-4.6L10,14l-1.4-0.2C5.8,13.3,4.4,12.4,4,12
- c0.3-0.4,1.8-1.3,4.6-1.7L10,10l0.2-1.4c0.4-2.8,1.3-4.2,1.7-4.6V2c-1.7,0-3.1,2.6-3.7,6.3C4.6,8.9,2,10.3,2,12s2.6,3.1,6.3,3.7
- c0.6,3.7,2,6.3,3.7,6.3s3.1-2.6,3.7-6.3c3.7-0.6,6.3-2,6.3-3.7h-2.1C19.6,12.4,18.2,13.3,15.4,13.7z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M15.4,13.7L14,14l-0.2,1.4c-0.4,2.8-1.3,4.2-1.7,4.6c-0.4-0.3-1.3-1.8-1.7-4.6L10,14l-1.4-0.2c-2.8-0.4-4.2-1.3-4.6-1.7
- c0.3-0.4,1.8-1.3,4.6-1.7L10,10l0.2-1.4c0.4-2.8,1.3-4.2,1.7-4.6V2c-1.7,0-3.1,2.6-3.7,6.3C4.6,8.9,2,10.3,2,12s2.6,3.1,6.3,3.7
- c0.6,3.7,2,6.3,3.7,6.3s3.1-2.6,3.7-6.3c3.7-0.6,6.3-2,6.3-3.7h-2.1C19.6,12.4,18.2,13.3,15.4,13.7z"/>
- <path d="M19,0c-2.8,0-5,2.2-5,5s2.2,5,5,5s5-2.2,5-5S21.8,0,19,0z M22,5.8h-2.3V8h-1.5V5.8H16V4.3h2.3V2h1.5v2.3H22V5.8z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M12,4.1c0.4,0.3,1.3,1.8,1.7,4.6L14,10l1.4,0.2c2.8,0.4,4.2,1.3,4.6,1.7c-0.3,0.4-1.8,1.3-4.6,1.7L14,14l-0.2,1.4
- c-0.4,2.8-1.3,4.2-1.7,4.6c-0.4-0.3-1.3-1.8-1.7-4.6L10,14l-1.4-0.2c-2.8-0.4-4.2-1.3-4.6-1.7c0.3-0.4,1.8-1.3,4.6-1.7L10,10
- l0.2-1.4C10.7,5.8,11.6,4.4,12,4.1 M12,2c-1.7,0-3.1,2.6-3.7,6.3C4.6,8.9,2,10.3,2,12s2.6,3.1,6.3,3.7c0.6,3.7,2,6.3,3.7,6.3
- s3.1-2.6,3.7-6.3c3.7-0.6,6.3-2,6.3-3.7s-2.6-3.1-6.3-3.7C15.1,4.6,13.7,2,12,2L12,2z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M12,6.7l1.7,3.7l4.1,0.6l-3,3.1l0.7,4.2l-3.5-2l-3.5,2l0.7-4.2l-3-3.1l4.1-0.6L12,6.7 M12,2L8.9,8.6L2,9.6l5,5.1L5.8,22
- l6.2-3.4l6.2,3.4L17,14.8l5-5.1l-6.9-1.1L12,2L12,2z"/>
-</svg>
+++ /dev/null
-<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path fill-rule="evenodd" clip-rule="evenodd" d="M16 3L21 8V19C21 20.1046 20.1046 21 19 21H5C3.89543 21 3 20.1046 3 19V5C3 3.89543 3.89543 3 5 3H16ZM19 9H17C15.8954 9 15 8.10457 15 7V5H5V19H19V9Z" fill="black"/>
-<path fill-rule="evenodd" clip-rule="evenodd" d="M9 12C9 13.6569 10.3431 15 12 15C13.6569 15 15 13.6569 15 12H17C17 14.7614 14.7614 17 12 17C9.23858 17 7 14.7614 7 12H9Z" fill="black"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,9c1.7,0,3,1.3,3,3s-1.3,3-3,3s-3-1.3-3-3S10.3,9,12,9 M12,7c-2.8,0-5,2.2-5,5s2.2,5,5,5s5-2.2,5-5S14.8,7,12,7L12,7z"
- />
- <g>
- <rect x="11" y="2" width="2" height="3"/>
- </g>
- <g>
- <rect x="19" y="11" width="3" height="2"/>
- </g>
- <g>
- <rect x="11" y="19" width="2" height="3"/>
- </g>
- <g>
- <rect x="2" y="11" width="3" height="2"/>
- </g>
- <g>
- <rect x="17" y="4.5" transform="matrix(0.7071 0.7071 -0.7071 0.7071 9.5104 -10.9811)" width="2" height="3"/>
- </g>
- <g>
- <rect x="16.5" y="17" transform="matrix(0.7071 0.7071 -0.7071 0.7071 18.0105 -7.4602)" width="3" height="2"/>
- </g>
- <g>
- <rect x="5" y="16.5" transform="matrix(0.7071 0.7071 -0.7071 0.7071 14.4896 1.0399)" width="2" height="3"/>
- </g>
- <g>
- <rect x="4.5" y="5" transform="matrix(0.7071 0.7071 -0.7071 0.7071 5.9895 -2.4809)" width="3" height="2"/>
- </g>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <rect x="13.6" y="6" transform="matrix(0.7071 0.7071 -0.7071 0.7071 10.9064 -7.6107)" width="2" height="6.7"/>
- <rect x="1.8" y="12.9" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 0.1967 27.7885)" width="8.1" height="2"/>
- <polygon points="21.3,6.3 13,14.6 8.7,10.3 7.3,11.7 13,17.4 22.7,7.7 "/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M12,11c2.2,0,4-1.8,4-4s-1.8-4-4-4S8,4.8,8,7S9.8,11,12,11z M12,5c1.1,0,2,0.9,2,2s-0.9,2-2,2s-2-0.9-2-2S10.9,5,12,5z"/>
- <path d="M14,13h-4c-3.3,0-6,2.7-6,6v2h16v-2C20,15.7,17.3,13,14,13z M6,19c0-2.2,1.8-4,4-4h4c2.2,0,4,1.8,4,4H6z"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <circle cx="12" cy="5" r="2"/>
- <circle cx="12" cy="12" r="2"/>
- <circle cx="12" cy="19" r="2"/>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<path d="M19.4,20L14,2h-4L4.6,20H2v2h2h16h2v-2H19.4z M15.8,15H8.2l0.9-3h5.8L15.8,15z M11.5,4h1l1.8,6H9.7L11.5,4z M6.7,20l0.9-3
- h8.8l0.9,3H6.7z"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M9,7.8v8.3L6.8,14H4v-4h2.8L9,7.8 M11,3L6,8H4c-1.1,0-2,0.9-2,2v4c0,1.1,0.9,2,2,2h2l5,5V3L11,3z"/>
- <path d="M13,3v2c3.9,0,7,3.1,7,7s-3.1,7-7,7v2c5,0,9-4,9-9S18,3,13,3z"/>
- <g>
- <path d="M13,17v-2c1.7,0,3-1.3,3-3s-1.3-3-3-3V7c2.8,0,5,2.2,5,5S15.8,17,13,17z"/>
- </g>
-</g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
-<g>
- <path d="M2,10v4c0,1.1,0.9,2,2,2h2l5,5V3L6,8H4C2.9,8,2,8.9,2,10z M9,7.8v8.3L6.8,14H4v-4h2.8L9,7.8z"/>
- <polygon points="20.7,9.7 19.3,8.3 17,10.6 14.7,8.3 13.3,9.7 15.6,12 13.3,14.3 14.7,15.7 17,13.4 19.3,15.7 20.7,14.3 18.4,12
- "/>
-</g>
-</svg>
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './Avatar.scss';
-
-import Text from '../text/Text';
-import RawIcon from '../system-icons/RawIcon';
-
-import ImageBrokenSVG from '../../../../public/res/svg/image-broken.svg';
-import { avatarInitials } from '../../../util/common';
-
-const Avatar = React.forwardRef(({ text, bgColor, iconSrc, iconColor, imageSrc, size }, ref) => {
- let textSize = 's1';
- if (size === 'large') textSize = 'h1';
- if (size === 'small') textSize = 'b1';
- if (size === 'extra-small') textSize = 'b3';
-
- return (
- <div ref={ref} className={`avatar-container avatar-container__${size} noselect`}>
- {imageSrc !== null ? (
- <img
- draggable="false"
- src={imageSrc}
- onLoad={(e) => {
- e.target.style.backgroundColor = 'transparent';
- }}
- onError={(e) => {
- e.target.src = ImageBrokenSVG;
- }}
- alt=""
- />
- ) : (
- <span
- style={{ backgroundColor: iconSrc === null ? bgColor : 'transparent' }}
- className={`avatar__border${iconSrc !== null ? '--active' : ''}`}
- >
- {iconSrc !== null ? (
- <RawIcon size={size} src={iconSrc} color={iconColor} />
- ) : (
- text !== null && (
- <Text variant={textSize} primary>
- {avatarInitials(text)}
- </Text>
- )
- )}
- </span>
- )}
- </div>
- );
-});
-
-Avatar.defaultProps = {
- text: null,
- bgColor: 'transparent',
- iconSrc: null,
- iconColor: null,
- imageSrc: null,
- size: 'normal',
-};
-
-Avatar.propTypes = {
- text: PropTypes.string,
- bgColor: PropTypes.string,
- iconSrc: PropTypes.string,
- iconColor: PropTypes.string,
- imageSrc: PropTypes.string,
- size: PropTypes.oneOf(['large', 'normal', 'small', 'extra-small']),
-};
-
-export default Avatar;
+++ /dev/null
-@use '../../partials/flex';
-
-.avatar-container {
- display: inline-flex;
- width: 42px;
- height: 42px;
- border-radius: var(--bo-radius);
- position: relative;
-
- &__large {
- width: var(--av-large);
- height: var(--av-large);
- }
- &__normal {
- width: var(--av-normal);
- height: var(--av-normal);
- }
-
- &__small {
- width: var(--av-small);
- height: var(--av-small);
- }
-
- &__extra-small {
- width: var(--av-extra-small);
- height: var(--av-extra-small);
- }
-
- > img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- border-radius: inherit;
- background-color: var(--bg-surface-hover);
- }
-
- .avatar__border {
- @extend .cp-fx__row--c-c;
-
- position: absolute;
- top: 0;
- left: 0;
-
- width: 100%;
- height: 100%;
- border-radius: inherit;
-
- .text {
- color: white;
- }
- &--active {
- @extend .avatar__border;
- box-shadow: var(--bs-surface-border);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-import { avatarInitials, cssVar } from '../../../util/common';
-
-// renders the avatar and returns it as an URL
-export default async function renderAvatar({
- text, bgColor, imageSrc, size, borderRadius, scale,
-}) {
- try {
- const canvas = document.createElement('canvas');
- canvas.width = size * scale;
- canvas.height = size * scale;
-
- const ctx = canvas.getContext('2d');
-
- ctx.scale(scale, scale);
-
- // rounded corners
- ctx.beginPath();
- ctx.moveTo(size, size);
- ctx.arcTo(0, size, 0, 0, borderRadius);
- ctx.arcTo(0, 0, size, 0, borderRadius);
- ctx.arcTo(size, 0, size, size, borderRadius);
- ctx.arcTo(size, size, 0, size, borderRadius);
-
- if (imageSrc) {
- // clip corners of image
- ctx.closePath();
- ctx.clip();
-
- const img = new Image();
- img.crossOrigin = 'anonymous';
- const promise = new Promise((resolve, reject) => {
- img.onerror = reject;
- img.onload = resolve;
- });
- img.src = imageSrc;
- await promise;
-
- ctx.drawImage(img, 0, 0, size, size);
- } else {
- // colored background
- ctx.fillStyle = cssVar(bgColor);
- ctx.fill();
-
- // centered letter
- ctx.fillStyle = '#fff';
- ctx.font = `${cssVar('--fs-s1')} ${cssVar('--font-primary')}`;
- ctx.textBaseline = 'middle';
- ctx.textAlign = 'center';
- ctx.fillText(avatarInitials(text), size / 2, size / 2);
- }
-
- return canvas.toDataURL();
- } catch (e) {
- console.error(e);
- return imageSrc;
- }
-}
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './NotificationBadge.scss';
-
-import Text from '../text/Text';
-
-function NotificationBadge({ alert, content }) {
- const notificationClass = alert ? ' notification-badge--alert' : '';
- return (
- <div className={`notification-badge${notificationClass}`}>
- {content !== null && <Text variant="b3" weight="bold">{content}</Text>}
- </div>
- );
-}
-
-NotificationBadge.defaultProps = {
- alert: false,
- content: null,
-};
-
-NotificationBadge.propTypes = {
- alert: PropTypes.bool,
- content: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.number,
- ]),
-};
-
-export default NotificationBadge;
+++ /dev/null
-.notification-badge {
- min-width: 16px;
- min-height: 8px;
- padding: 0 var(--sp-ultra-tight);
- background-color: var(--bg-badge);
- border-radius: var(--bo-radius);
-
- .text {
- color: var(--tc-badge);
- text-align: center;
- }
-
- &--alert {
- background-color: var(--bg-positive);
- }
-
- &:empty {
- min-width: 8px;
- margin: 0 var(--sp-ultra-tight);
- }
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './Button.scss';
-
-import Text from '../text/Text';
-import RawIcon from '../system-icons/RawIcon';
-import { blurOnBubbling } from './script';
-
-const Button = React.forwardRef(({
- id, className, variant, iconSrc,
- type, onClick, children, disabled,
-}, ref) => {
- const iconClass = (iconSrc === null) ? '' : `btn-${variant}--icon`;
- return (
- <button
- ref={ref}
- id={id === '' ? undefined : id}
- className={`${className ? `${className} ` : ''}btn-${variant} ${iconClass} noselect`}
- onMouseUp={(e) => blurOnBubbling(e, `.btn-${variant}`)}
- onClick={onClick}
- // eslint-disable-next-line react/button-has-type
- type={type}
- disabled={disabled}
- >
- {iconSrc !== null && <RawIcon size="small" src={iconSrc} />}
- {typeof children === 'string' && <Text variant="b1">{ children }</Text>}
- {typeof children !== 'string' && children }
- </button>
- );
-});
-
-Button.defaultProps = {
- id: '',
- className: null,
- variant: 'surface',
- iconSrc: null,
- type: 'button',
- onClick: null,
- disabled: false,
-};
-
-Button.propTypes = {
- id: PropTypes.string,
- className: PropTypes.string,
- variant: PropTypes.oneOf(['surface', 'primary', 'positive', 'caution', 'danger']),
- iconSrc: PropTypes.string,
- type: PropTypes.oneOf(['button', 'submit', 'reset']),
- onClick: PropTypes.func,
- children: PropTypes.node.isRequired,
- disabled: PropTypes.bool,
-};
-
-export default Button;
+++ /dev/null
-@use 'state';
-@use '../../partials/dir';
-@use '../../partials/text';
-
-.btn-surface,
-.btn-primary,
-.btn-positive,
-.btn-caution,
-.btn-danger {
- display: inline-flex;
- align-items: center;
- justify-content: center;
-
- min-width: 80px;
- padding: var(--sp-extra-tight) var(--sp-normal);
- background-color: transparent;
- border: none;
- border-radius: var(--bo-radius);
- cursor: pointer;
- @include state.disabled;
-
- & .text {
- @extend .cp-txt__ellipsis;
- }
-
- &--icon {
- @include dir.side(padding, var(--sp-tight), var(--sp-loose));
-
- }
- .ic-raw {
- @include dir.side(margin, 0, var(--sp-extra-tight));
- flex-shrink: 0;
- }
-}
-
-@mixin color($textColor, $iconColor) {
- .text {
- color: $textColor;
- }
- .ic-raw {
- background-color: $iconColor;
- }
-}
-
-
-.btn-surface {
- box-shadow: var(--bs-surface-border);
- @include color(var(--tc-surface-high), var(--ic-surface-normal));
- @include state.hover(var(--bg-surface-hover));
- @include state.focus(var(--bs-surface-outline));
- @include state.active(var(--bg-surface-active));
-}
-
-.btn-primary {
- background-color: var(--bg-primary);
- @include color(var(--tc-primary-high), var(--ic-primary-normal));
- @include state.hover(var(--bg-primary-hover));
- @include state.focus(var(--bs-primary-outline));
- @include state.active(var(--bg-primary-active));
-}
-.btn-positive {
- box-shadow: var(--bs-positive-border);
- @include color(var(--tc-positive-high), var(--ic-positive-normal));
- @include state.hover(var(--bg-positive-hover));
- @include state.focus(var(--bs-positive-outline));
- @include state.active(var(--bg-positive-active));
-}
-.btn-caution {
- box-shadow: var(--bs-caution-border);
- @include color(var(--tc-caution-high), var(--ic-caution-normal));
- @include state.hover(var(--bg-caution-hover));
- @include state.focus(var(--bs-caution-outline));
- @include state.active(var(--bg-caution-active));
-}
-.btn-danger {
- box-shadow: var(--bs-danger-border);
- @include color(var(--tc-danger-high), var(--ic-danger-normal));
- @include state.hover(var(--bg-danger-hover));
- @include state.focus(var(--bs-danger-outline));
- @include state.active(var(--bg-danger-active));
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './Checkbox.scss';
-
-function Checkbox({
- variant, isActive, onToggle,
- disabled, tabIndex,
-}) {
- const className = `checkbox checkbox-${variant}${isActive ? ' checkbox--active' : ''}`;
- if (onToggle === null) return <span className={className} />;
- return (
- // eslint-disable-next-line jsx-a11y/control-has-associated-label
- <button
- onClick={() => onToggle(!isActive)}
- className={className}
- type="button"
- disabled={disabled}
- tabIndex={tabIndex}
- />
- );
-}
-
-Checkbox.defaultProps = {
- variant: 'primary',
- isActive: false,
- onToggle: null,
- disabled: false,
- tabIndex: 0,
-};
-
-Checkbox.propTypes = {
- variant: PropTypes.oneOf(['primary', 'positive', 'caution', 'danger']),
- isActive: PropTypes.bool,
- onToggle: PropTypes.func,
- disabled: PropTypes.bool,
- tabIndex: PropTypes.number,
-};
-
-export default Checkbox;
+++ /dev/null
-@use '../../partials/flex';
-@use './state';
-
-.checkbox {
- width: 20px;
- height: 20px;
-
- border-radius: calc(var(--bo-radius) / 2);
- background-color: var(--bg-surface-border);
- box-shadow: var(--bs-surface-border);
- cursor: pointer;
- @include state.disabled;
- @extend .cp-fx__row--c-c;
-
- &--active {
- background-color: black;
- &::before {
- content: "";
- display: inline-block;
- width: 12px;
- height: 6px;
- border: 6px solid white;
- border-width: 0 0 3px 3px;
- transform: rotateZ(-45deg) translate(1px, -1px);
- }
- }
-}
-.checkbox-primary.checkbox--active {
- background-color: var(--bg-primary);
-}
-.checkbox-positive.checkbox--active {
- background-color: var(--bg-positive);
-}
-.checkbox-caution.checkbox--active {
- background-color: var(--bg-caution);
-}
-.checkbox-danger.checkbox--active {
- background-color: var(--bg-danger);
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './IconButton.scss';
-
-import RawIcon from '../system-icons/RawIcon';
-import Tooltip from '../tooltip/Tooltip';
-import { blurOnBubbling } from './script';
-import Text from '../text/Text';
-
-const IconButton = React.forwardRef(({
- variant, size, type,
- tooltip, tooltipPlacement, src,
- onClick, tabIndex, disabled, isImage,
- className,
-}, ref) => {
- const btn = (
- <button
- ref={ref}
- className={`ic-btn ic-btn-${variant} ${className}`}
- onMouseUp={(e) => blurOnBubbling(e, `.ic-btn-${variant}`)}
- onClick={onClick}
- // eslint-disable-next-line react/button-has-type
- type={type}
- tabIndex={tabIndex}
- disabled={disabled}
- >
- <RawIcon size={size} src={src} isImage={isImage} />
- </button>
- );
- if (tooltip === null) return btn;
- return (
- <Tooltip
- placement={tooltipPlacement}
- content={<Text variant="b2">{tooltip}</Text>}
- >
- {btn}
- </Tooltip>
- );
-});
-
-IconButton.defaultProps = {
- variant: 'surface',
- size: 'normal',
- type: 'button',
- tooltip: null,
- tooltipPlacement: 'top',
- onClick: null,
- tabIndex: 0,
- disabled: false,
- isImage: false,
- className: '',
-};
-
-IconButton.propTypes = {
- variant: PropTypes.oneOf(['surface', 'primary', 'positive', 'caution', 'danger']),
- size: PropTypes.oneOf(['normal', 'small', 'extra-small']),
- type: PropTypes.oneOf(['button', 'submit', 'reset']),
- tooltip: PropTypes.string,
- tooltipPlacement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
- src: PropTypes.string.isRequired,
- onClick: PropTypes.func,
- tabIndex: PropTypes.number,
- disabled: PropTypes.bool,
- isImage: PropTypes.bool,
- className: PropTypes.string,
-};
-
-export default IconButton;
+++ /dev/null
-@use 'state';
-
-.ic-btn {
- padding: var(--sp-extra-tight);
- border: none;
- border-radius: var(--bo-radius);
- background-color: transparent;
- font-size: 0;
- line-height: 0;
- cursor: pointer;
- @include state.disabled;
-}
-
-@mixin color($color) {
- .ic-raw {
- background-color: $color;
- }
-}
-@mixin focus($color) {
- &:focus {
- outline: none;
- background-color: $color;
- }
-}
-
-.ic-btn-surface {
- @include color(var(--ic-surface-normal));
- @include state.hover(var(--bg-surface-hover));
- @include focus(var(--bg-surface-hover));
- @include state.active(var(--bg-surface-active));
-}
-.ic-btn-primary {
- @include color(var(--ic-primary-normal));
- @include state.hover(var(--bg-primary-hover));
- @include focus(var(--bg-primary-hover));
- @include state.active(var(--bg-primary-active));
- background-color: var(--bg-primary);
-}
-.ic-btn-positive {
- @include color(var(--ic-positive-normal));
- @include state.hover(var(--bg-positive-hover));
- @include focus(var(--bg-positive-hover));
- @include state.active(var(--bg-positive-active));
-}
-.ic-btn-caution {
- @include color(var(--ic-caution-normal));
- @include state.hover(var(--bg-caution-hover));
- @include focus(var(--bg-caution-hover));
- @include state.active(var(--bg-caution-active));
-}
-.ic-btn-danger {
- @include color(var(--ic-danger-normal));
- @include state.hover(var(--bg-danger-hover));
- @include focus(var(--bg-danger-hover));
- @include state.active(var(--bg-danger-active));
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './RadioButton.scss';
-
-function RadioButton({ isActive, onToggle, disabled }) {
- if (onToggle === null) return <span className={`radio-btn${isActive ? ' radio-btn--active' : ''}`} />;
- return (
- // eslint-disable-next-line jsx-a11y/control-has-associated-label
- <button
- onClick={() => onToggle(!isActive)}
- className={`radio-btn${isActive ? ' radio-btn--active' : ''}`}
- type="button"
- disabled={disabled}
- />
- );
-}
-
-RadioButton.defaultProps = {
- isActive: false,
- onToggle: null,
- disabled: false,
-};
-
-RadioButton.propTypes = {
- isActive: PropTypes.bool,
- onToggle: PropTypes.func,
- disabled: PropTypes.bool,
-};
-
-export default RadioButton;
+++ /dev/null
-@use '../../partials/flex';
-@use './state';
-
-.radio-btn {
- @extend .cp-fx__row--c-c;
- width: 20px;
- height: 20px;
- border-radius: 50%;
- background-color: var(--bg-surface-border);
- border: 2px solid var(--bg-surface-border);
- cursor: pointer;
- @include state.disabled;
-
- &::before {
- content: '';
- display: inline-block;
- width: 12px;
- height: 12px;
- background-color: var(--bg-surface-border);
- border-radius: 50%;
- }
- &--active {
- border: 2px solid var(--bg-positive);
- &::before {
- background-color: var(--bg-positive);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './Toggle.scss';
-
-function Toggle({ isActive, onToggle, disabled }) {
- const className = `toggle${isActive ? ' toggle--active' : ''}`;
- if (onToggle === null) return <span className={className} />;
- return (
- // eslint-disable-next-line jsx-a11y/control-has-associated-label
- <button
- onClick={() => onToggle(!isActive)}
- className={className}
- type="button"
- disabled={disabled}
- />
- );
-}
-
-Toggle.defaultProps = {
- isActive: false,
- disabled: false,
- onToggle: null,
-};
-
-Toggle.propTypes = {
- isActive: PropTypes.bool,
- onToggle: PropTypes.func,
- disabled: PropTypes.bool,
-};
-
-export default Toggle;
+++ /dev/null
-@use '../../partials/dir';
-@use './state';
-
-.toggle {
- width: 44px;
- height: 24px;
- padding: 0 var(--sp-ultra-tight);
- display: flex;
- align-items: center;
- border-radius: var(--bo-radius);
- box-shadow: var(--bs-surface-border);
- cursor: pointer;
- background-color: var(--bg-surface-low);
- @include state.disabled;
-
- transition: background 200ms ease-in-out;
-
- &::before {
- content: '';
- display: inline-block;
- width: 16px;
- height: 16px;
- background-color: var(--tc-surface-low);
- border-radius: calc(var(--bo-radius) / 2);
- transition: transform 200ms ease-in-out, opacity 200ms ease-in-out;
- opacity: 0.6;
- }
-
- &--active {
- background-color: var(--bg-positive);
-
- &::before {
- --ltr: translateX(calc(125%));
- --rtl: translateX(calc(-125%));
- @include dir.prop(transform, var(--ltr), var(--rtl));
-
- transform: translateX(calc(125%));
- background-color: var(--bg-surface);
- opacity: 1;
- }
- }
-}
+++ /dev/null
-
-@mixin hover($color) {
- @media (hover: hover) {
- &:hover {
- background-color: $color;
- }
- }
-}
-@mixin focus($outline) {
- &:focus {
- outline: none;
- box-shadow: $outline;
- }
-}
-@mixin active($color) {
- &:active {
- background-color: $color !important;
- }
-}
-@mixin disabled {
- &:disabled {
- opacity: 0.4;
- cursor: no-drop;
- }
-}
\ No newline at end of file
+++ /dev/null
-/**
- * blur [selector] element in bubbling path.
- * @param {Event} e Event
- * @param {string} selector element selector for Element.matches([selector])
- * @return {boolean} if blured return true, else return false with warning in console
- */
-
-function blurOnBubbling(e, selector) {
- const bubblingPath = e.nativeEvent.composedPath();
-
- for (let elIndex = 0; elIndex < bubblingPath.length; elIndex += 1) {
- if (bubblingPath[elIndex] === document) {
- console.warn(blurOnBubbling, 'blurOnBubbling: not found selector in bubbling path');
- break;
- }
- if (bubblingPath[elIndex].matches(selector)) {
- setTimeout(() => bubblingPath[elIndex].blur(), 50);
- return true;
- }
- }
- return false;
-}
-export { blurOnBubbling };
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './InfoCard.scss';
-
-import Text from '../text/Text';
-import RawIcon from '../system-icons/RawIcon';
-import IconButton from '../button/IconButton';
-
-import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
-
-function InfoCard({
- className, style,
- variant, iconSrc,
- title, content,
- rounded, requestClose,
-}) {
- const classes = [`info-card info-card--${variant}`];
- if (rounded) classes.push('info-card--rounded');
- if (className) classes.push(className);
- return (
- <div className={classes.join(' ')} style={style}>
- {iconSrc && (
- <div className="info-card__icon">
- <RawIcon color={`var(--ic-${variant}-high)`} src={iconSrc} />
- </div>
- )}
- <div className="info-card__content">
- <Text>{title}</Text>
- {content}
- </div>
- {requestClose && (
- <IconButton src={CrossIC} variant={variant} onClick={requestClose} />
- )}
- </div>
- );
-}
-
-InfoCard.defaultProps = {
- className: null,
- style: null,
- variant: 'surface',
- iconSrc: null,
- content: null,
- rounded: false,
- requestClose: null,
-};
-
-InfoCard.propTypes = {
- className: PropTypes.string,
- style: PropTypes.shape({}),
- variant: PropTypes.oneOf(['surface', 'primary', 'positive', 'caution', 'danger']),
- iconSrc: PropTypes.string,
- title: PropTypes.string.isRequired,
- content: PropTypes.node,
- rounded: PropTypes.bool,
- requestClose: PropTypes.func,
-};
-
-export default InfoCard;
+++ /dev/null
-@use '.././../partials/flex';
-@use '.././../partials/dir';
-
-.info-card {
- display: flex;
- align-items: flex-start;
- line-height: 0;
- padding: var(--sp-tight);
- @include dir.prop(border-left, 4px solid transparent, none);
- @include dir.prop(border-right, none, 4px solid transparent);
-
- & > .ic-btn {
- padding: 0;
- border-radius: 4;
- }
-
- &__content {
- margin: 0 var(--sp-tight);
- @extend .cp-fx__item-one;
-
- & > *:nth-child(2) {
- margin-top: var(--sp-ultra-tight);
- }
- }
-
- &--rounded {
- @include dir.prop(
- border-radius,
- 0 var(--bo-radius) var(--bo-radius) 0,
- var(--bo-radius) 0 0 var(--bo-radius)
- );
- }
-
- &--surface {
- border-color: var(--bg-surface-border);
- background-color: var(--bg-surface-hover);
-
- }
- &--primary {
- border-color: var(--bg-primary);
- background-color: var(--bg-primary-hover);
- & .text {
- color: var(--tc-primary-high);
- &-b3 {
- color: var(--tc-primary-normal);
- }
- }
- }
- &--positive {
- border-color: var(--bg-positive-border);
- background-color: var(--bg-positive-hover);
- & .text {
- color: var(--tc-positive-high);
- &-b3 {
- color: var(--tc-positive-normal);
- }
- }
- }
- &--caution {
- border-color: var(--bg-caution-border);
- background-color: var(--bg-caution-hover);
- & .text {
- color: var(--tc-caution-high);
- &-b3 {
- color: var(--tc-caution-normal);
- }
- }
- }
- &--danger {
- border-color: var(--bg-danger-border);
- background-color: var(--bg-danger-hover);
- & .text {
- color: var(--tc-danger-high);
- &-b3 {
- color: var(--tc-danger-normal);
- }
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './Chip.scss';
-
-import Text from '../text/Text';
-import RawIcon from '../system-icons/RawIcon';
-
-function Chip({
- iconSrc, iconColor, text, children,
- onClick,
-}) {
- return (
- <button className="chip" type="button" onClick={onClick}>
- {iconSrc != null && <RawIcon src={iconSrc} color={iconColor} size="extra-small" />}
- {(text != null && text !== '') && <Text variant="b3">{text}</Text>}
- {children}
- </button>
- );
-}
-
-Chip.propTypes = {
- iconSrc: PropTypes.string,
- iconColor: PropTypes.string,
- text: PropTypes.string,
- children: PropTypes.element,
- onClick: PropTypes.func,
-};
-
-Chip.defaultProps = {
- iconSrc: null,
- iconColor: null,
- text: null,
- children: null,
- onClick: null,
-};
-
-export default Chip;
+++ /dev/null
-@use '../../partials/dir';
-
-.chip {
- padding: var(--sp-ultra-tight) var(--sp-extra-tight);
-
- display: inline-flex;
- flex-direction: row;
- align-items: center;
-
- background: var(--bg-surface-low);
- border-radius: var(--bo-radius);
- box-shadow: var(--bs-surface-border);
- cursor: pointer;
-
- @media (hover: hover) {
- &:hover {
- background-color: var(--bg-surface-hover);
- }
- }
-
- & > .text {
- flex: 1;
- color: var(--tc-surface-high);
- }
-
- & > .ic-raw {
- width: 16px;
- height: 16px;
- @include dir.side(margin, 0, var(--sp-ultra-tight));
- }
-}
\ No newline at end of file
+++ /dev/null
-import React, { useState, useEffect } from 'react';
-import PropTypes from 'prop-types';
-import './ContextMenu.scss';
-
-import Tippy from '@tippyjs/react';
-import 'tippy.js/animations/scale-extreme.css';
-
-import Text from '../text/Text';
-import Button from '../button/Button';
-import ScrollView from '../scroll/ScrollView';
-
-function ContextMenu({
- content, placement, maxWidth, render, afterToggle,
-}) {
- const [isVisible, setVisibility] = useState(false);
- const showMenu = () => setVisibility(true);
- const hideMenu = () => setVisibility(false);
-
- useEffect(() => {
- if (afterToggle !== null) afterToggle(isVisible);
- }, [isVisible]);
-
- return (
- <Tippy
- animation="scale-extreme"
- className="context-menu"
- visible={isVisible}
- onClickOutside={hideMenu}
- content={<ScrollView invisible>{typeof content === 'function' ? content(hideMenu) : content}</ScrollView>}
- placement={placement}
- interactive
- arrow={false}
- maxWidth={maxWidth}
- duration={200}
- >
- {render(isVisible ? hideMenu : showMenu)}
- </Tippy>
- );
-}
-
-ContextMenu.defaultProps = {
- maxWidth: 'unset',
- placement: 'right',
- afterToggle: null,
-};
-
-ContextMenu.propTypes = {
- content: PropTypes.oneOfType([
- PropTypes.node,
- PropTypes.func,
- ]).isRequired,
- placement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
- maxWidth: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.number,
- ]),
- render: PropTypes.func.isRequired,
- afterToggle: PropTypes.func,
-};
-
-function MenuHeader({ children }) {
- return (
- <div className="context-menu__header">
- <Text variant="b3">{ children }</Text>
- </div>
- );
-}
-
-MenuHeader.propTypes = {
- children: PropTypes.node.isRequired,
-};
-
-function MenuItem({
- variant, iconSrc, type,
- onClick, children, disabled,
-}) {
- return (
- <div className="context-menu__item">
- <Button
- variant={variant}
- iconSrc={iconSrc}
- type={type}
- onClick={onClick}
- disabled={disabled}
- >
- { children }
- </Button>
- </div>
- );
-}
-
-MenuItem.defaultProps = {
- variant: 'surface',
- iconSrc: null,
- type: 'button',
- disabled: false,
- onClick: null,
-};
-
-MenuItem.propTypes = {
- variant: PropTypes.oneOf(['surface', 'positive', 'caution', 'danger']),
- iconSrc: PropTypes.string,
- type: PropTypes.oneOf(['button', 'submit']),
- onClick: PropTypes.func,
- children: PropTypes.node.isRequired,
- disabled: PropTypes.bool,
-};
-
-function MenuBorder() {
- return <div style={{ borderBottom: '1px solid var(--bg-surface-border)' }}> </div>;
-}
-
-export {
- ContextMenu as default, MenuHeader, MenuItem, MenuBorder,
-};
+++ /dev/null
-@use '../../partials/flex';
-@use '../../partials/text';
-@use '../../partials/dir';
-
-.context-menu {
- background-color: var(--bg-surface);
- box-shadow: var(--bs-popup);
- border-radius: var(--bo-radius);
- overflow: hidden;
-
- &:focus {
- outline: none;
- }
- & .tippy-content > div > .scrollbar {
- max-height: 90vh;
- }
-}
-
-.context-menu__click-wrapper {
- display: inline-flex;
-
- &:focus {
- outline: none;
- }
-}
-
-.context-menu__header {
- height: 34px;
- padding: 0 var(--sp-normal);
- margin-bottom: var(--sp-ultra-tight);
- display: flex;
- align-items: center;
- border-bottom: 1px solid var(--bg-surface-border);
-
- .text {
- @extend .cp-txt__ellipsis;
- color: var(--tc-surface-low);
- }
-
- &:not(:first-child) {
- margin-top: var(--sp-extra-tight);
- border-top: 1px solid var(--bg-surface-border);
- }
-}
-
-.context-menu__item {
- display: flex;
- button[class^="btn"] {
- @extend .cp-fx__item-one;
- justify-content: flex-start;
- border-radius: 0;
- box-shadow: none;
- white-space: nowrap;
- padding: var(--sp-extra-tight) var(--sp-normal);
-
- & > .ic-raw {
- @include dir.side(margin, 0, var(--sp-tight));
- }
-
- // if item doesn't have icon
- .text:first-child {
- @include dir.side(
- margin,
- calc(var(--ic-small) + var(--sp-tight)),
- 0
- );
- }
- }
- .btn-surface:focus {
- background-color: var(--bg-surface-hover);
- }
- .btn-positive:focus {
- background-color: var(--bg-positive-hover);
- }
- .btn-caution:focus {
- background-color: var(--bg-caution-hover);
- }
- .btn-danger:focus {
- background-color: var(--bg-danger-hover);
- }
-}
\ No newline at end of file
+++ /dev/null
-import React, { useState, useEffect, useRef } from 'react';
-
-import cons from '../../../client/state/cons';
-import navigation from '../../../client/state/navigation';
-
-import ContextMenu from './ContextMenu';
-
-let key = null;
-function ReusableContextMenu() {
- const [data, setData] = useState(null);
- const openerRef = useRef(null);
-
- const closeMenu = () => {
- key = null;
- if (data) openerRef.current.click();
- };
-
- useEffect(() => {
- if (data) {
- const { cords } = data;
- openerRef.current.style.transform = `translate(${cords.x}px, ${cords.y}px)`;
- openerRef.current.style.width = `${cords.width}px`;
- openerRef.current.style.height = `${cords.height}px`;
- openerRef.current.click();
- }
- const handleContextMenuOpen = (placement, cords, render, afterClose) => {
- if (key) {
- closeMenu();
- return;
- }
- setData({
- placement, cords, render, afterClose,
- });
- };
- navigation.on(cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED, handleContextMenuOpen);
- return () => {
- navigation.removeListener(
- cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED,
- handleContextMenuOpen,
- );
- };
- }, [data]);
-
- const handleAfterToggle = (isVisible) => {
- if (isVisible) {
- key = Math.random();
- return;
- }
- data?.afterClose?.();
- if (setData) setData(null);
-
- if (key === null) return;
- const copyKey = key;
- setTimeout(() => {
- if (key === copyKey) key = null;
- }, 200);
- };
-
- return (
- <ContextMenu
- afterToggle={handleAfterToggle}
- placement={data?.placement || 'right'}
- content={data?.render(closeMenu) ?? ''}
- render={(toggleMenu) => (
- <input
- ref={openerRef}
- onClick={toggleMenu}
- type="button"
- style={{
- width: '32px',
- height: '32px',
- backgroundColor: 'transparent',
- position: 'fixed',
- top: 0,
- left: 0,
- padding: 0,
- border: 'none',
- visibility: 'hidden',
- appearance: 'none',
- }}
- />
- )}
- />
- );
-}
-
-export default ReusableContextMenu;
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './Divider.scss';
-
-import Text from '../text/Text';
-
-function Divider({ text, variant, align }) {
- const dividerClass = ` divider--${variant} divider--${align}`;
- return (
- <div className={`divider${dividerClass}`}>
- {text !== null && <Text className="divider__text" variant="b3" weight="bold">{text}</Text>}
- </div>
- );
-}
-
-Divider.defaultProps = {
- text: null,
- variant: 'surface',
- align: 'center',
-};
-
-Divider.propTypes = {
- text: PropTypes.string,
- variant: PropTypes.oneOf(['surface', 'primary', 'positive', 'caution', 'danger']),
- align: PropTypes.oneOf(['left', 'center', 'right']),
-};
-
-export default Divider;
+++ /dev/null
-.divider-line {
- content: '';
- display: inline-block;
- flex: 1;
- border-bottom: 1px solid var(--local-divider-color);
- opacity: var(--local-divider-opacity);
-}
-
-.divider {
- display: flex;
- align-items: center;
-
- &--center::before,
- &--right::before {
- @extend .divider-line;
- }
- &--center::after,
- &--left::after {
- @extend .divider-line;
- }
-
- &__text {
- padding: 2px var(--sp-extra-tight);
- border-radius: calc(var(--bo-radius) / 2);
- }
-}
-
-.divider--surface {
- --local-divider-color: var(--bg-divider);
- --local-divider-opacity: 1;
-
- .divider__text {
- color: var(--tc-surface-low);
- border: 1px solid var(--bg-divider);
- }
-}
-.divider--primary {
- --local-divider-color: var(--bg-primary);
- --local-divider-opacity: .8;
- .divider__text {
- color: var(--tc-primary-high);
- background-color: var(--bg-primary);
- }
-}
-.divider--positive {
- --local-divider-color: var(--bg-positive);
- --local-divider-opacity: .8;
- .divider__text {
- color: var(--bg-surface);
- background-color: var(--bg-positive);
- }
-}
-.divider--danger {
- --local-divider-color: var(--bg-danger);
- --local-divider-opacity: .8;
- .divider__text {
- color: var(--bg-surface);
- background-color: var(--bg-danger);
- }
-}
-.divider--caution {
- --local-divider-color: var(--bg-caution);
- --local-divider-opacity: .8;
- .divider__text {
- color: var(--bg-surface);
- background-color: var(--bg-caution);
- }
-}
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './Header.scss';
-
-function Header({ children }) {
- return (
- <div className="header">
- {children}
- </div>
- );
-}
-
-Header.propTypes = {
- children: PropTypes.node.isRequired,
-};
-
-function TitleWrapper({ children }) {
- return (
- <div className="header__title-wrapper">
- {children}
- </div>
- );
-}
-
-TitleWrapper.propTypes = {
- children: PropTypes.node.isRequired,
-};
-
-export { Header as default, TitleWrapper };
+++ /dev/null
-@use '../../partials/text';
-@use '../../partials/dir';
-
-.header {
- @include dir.side(padding, var(--sp-normal), var(--sp-extra-tight));
- width: 100%;
- height: var(--header-height);
- border-bottom: 1px solid var(--bg-surface-border);
- display: flex;
- align-items: center;
-
- &__title-wrapper {
- flex: 1;
- min-width: 0;
- display: flex;
- align-items: center;
- margin: 0 var(--sp-tight);
-
- &:first-child {
- @include dir.side(margin, 0, var(--sp-tight));
- }
-
- & > .text:first-child {
- @extend .cp-txt__ellipsis;
- min-width: 0;
- }
- & > .text-b3{
- flex: 1;
- min-width: 0;
-
- margin-top: var(--sp-ultra-tight);
- @include dir.side(margin, var(--sp-tight), 0);
- @include dir.side(padding, var(--sp-tight), 0);
- @include dir.side(border, 1px solid var(--bg-surface-border), none);
-
- max-height: calc(2 * var(--lh-b3));
- overflow: hidden;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 2;
- display: -webkit-box;
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './Input.scss';
-
-import TextareaAutosize from 'react-autosize-textarea';
-
-function Input({
- id, label, name, value, placeholder,
- required, type, onChange, forwardRef,
- resizable, minHeight, onResize, state,
- onKeyDown, disabled, autoFocus,
-}) {
- return (
- <div className="input-container">
- { label !== '' && <label className="input__label text-b2" htmlFor={id}>{label}</label> }
- { resizable
- ? (
- <TextareaAutosize
- dir="auto"
- style={{ minHeight: `${minHeight}px` }}
- name={name}
- id={id}
- className={`input input--resizable${state !== 'normal' ? ` input--${state}` : ''}`}
- ref={forwardRef}
- type={type}
- placeholder={placeholder}
- required={required}
- defaultValue={value}
- autoComplete="off"
- onChange={onChange}
- onResize={onResize}
- onKeyDown={onKeyDown}
- disabled={disabled}
- autoFocus={autoFocus}
- />
- ) : (
- <input
- dir="auto"
- ref={forwardRef}
- id={id}
- name={name}
- className={`input ${state !== 'normal' ? ` input--${state}` : ''}`}
- type={type}
- placeholder={placeholder}
- required={required}
- defaultValue={value}
- autoComplete="off"
- onChange={onChange}
- onKeyDown={onKeyDown}
- disabled={disabled}
- // eslint-disable-next-line jsx-a11y/no-autofocus
- autoFocus={autoFocus}
- />
- )}
- </div>
- );
-}
-
-Input.defaultProps = {
- id: null,
- name: '',
- label: '',
- value: '',
- placeholder: '',
- type: 'text',
- required: false,
- onChange: null,
- forwardRef: null,
- resizable: false,
- minHeight: 46,
- onResize: null,
- state: 'normal',
- onKeyDown: null,
- disabled: false,
- autoFocus: false,
-};
-
-Input.propTypes = {
- id: PropTypes.string,
- name: PropTypes.string,
- label: PropTypes.string,
- value: PropTypes.string,
- placeholder: PropTypes.string,
- required: PropTypes.bool,
- type: PropTypes.string,
- onChange: PropTypes.func,
- forwardRef: PropTypes.shape({}),
- resizable: PropTypes.bool,
- minHeight: PropTypes.number,
- onResize: PropTypes.func,
- state: PropTypes.oneOf(['normal', 'success', 'error']),
- onKeyDown: PropTypes.func,
- disabled: PropTypes.bool,
- autoFocus: PropTypes.bool,
-};
-
-export default Input;
+++ /dev/null
-@use '../../atoms/scroll/scrollbar';
-
-.input {
- display: block;
- width: 100%;
- min-width: 0px;
- margin: 0;
- padding: var(--sp-tight) var(--sp-normal);
- background-color: var(--bg-surface-low);
- color: var(--tc-surface-normal);
- box-shadow: none;
- border-radius: var(--bo-radius);
- border: 1px solid var(--bg-surface-border);
- font-size: var(--fs-b2);
- letter-spacing: var(--ls-b2);
- line-height: var(--lh-b2);
-
- :disabled {
- opacity: 0.4;
- cursor: no-drop;
- }
-
- &__label {
- display: inline-block;
- margin-bottom: var(--sp-ultra-tight);
- color: var(--tc-surface-low);
- }
-
- &--resizable {
- resize: vertical !important;
- overflow-y: auto !important;
- @include scrollbar.scroll;
- @include scrollbar.scroll__v;
- @include scrollbar.scroll--auto-hide;
- }
- &--success {
- border: 1px solid var(--bg-positive);
- box-shadow: none !important;
- }
- &--error {
- border: 1px solid var(--bg-danger);
- box-shadow: none !important;
- }
-
- &:focus {
- outline: none;
- box-shadow: var(--bs-primary-border);
- }
- &::placeholder {
- color: var(--tc-surface-low)
- }
-}
\ No newline at end of file
+++ /dev/null
-import React, { useEffect } from 'react';
-import PropTypes from 'prop-types';
-import './RawModal.scss';
-
-import Modal from 'react-modal';
-
-import navigation from '../../../client/state/navigation';
-
-Modal.setAppElement('#root');
-
-function RawModal({
- className, overlayClassName,
- isOpen, size, onAfterOpen, onAfterClose,
- onRequestClose, closeFromOutside, children,
-}) {
- let modalClass = (className !== null) ? `${className} ` : '';
- switch (size) {
- case 'large':
- modalClass += 'raw-modal__large ';
- break;
- case 'medium':
- modalClass += 'raw-modal__medium ';
- break;
- case 'small':
- default:
- modalClass += 'raw-modal__small ';
- }
-
- useEffect(() => {
- navigation.setIsRawModalVisible(isOpen);
- }, [isOpen]);
-
- const modalOverlayClass = (overlayClassName !== null) ? `${overlayClassName} ` : '';
- return (
- <Modal
- className={`${modalClass}raw-modal`}
- overlayClassName={`${modalOverlayClass}raw-modal__overlay`}
- isOpen={isOpen}
- onAfterOpen={onAfterOpen}
- onAfterClose={onAfterClose}
- onRequestClose={onRequestClose}
- shouldCloseOnEsc={closeFromOutside}
- shouldCloseOnOverlayClick={closeFromOutside}
- shouldReturnFocusAfterClose={false}
- >
- {children}
- </Modal>
- );
-}
-
-RawModal.defaultProps = {
- className: null,
- overlayClassName: null,
- size: 'small',
- onAfterOpen: null,
- onAfterClose: null,
- onRequestClose: null,
- closeFromOutside: true,
-};
-
-RawModal.propTypes = {
- className: PropTypes.string,
- overlayClassName: PropTypes.string,
- isOpen: PropTypes.bool.isRequired,
- size: PropTypes.oneOf(['large', 'medium', 'small']),
- onAfterOpen: PropTypes.func,
- onAfterClose: PropTypes.func,
- onRequestClose: PropTypes.func,
- closeFromOutside: PropTypes.bool,
- children: PropTypes.node.isRequired,
-};
-
-export default RawModal;
+++ /dev/null
-.raw-modal {
- --small-modal-width: 525px;
- --medium-modal-width: 712px;
- --large-modal-width: 1024px;
-
- position: relative;
- width: 100%;
- max-height: 100%;
- border-radius: var(--bo-radius);
- box-shadow: var(--bs-popup);
- outline: none;
- overflow: hidden;
-
- &__small {
- max-width: var(--small-modal-width);
- }
- &__medium {
- max-width: var(--medium-modal-width);
- }
- &__large {
- max-width: var(--large-modal-width);
- }
-
- &__overlay {
- position: fixed;
- top: 0;
- left: 0;
- z-index: 999;
-
- display: flex;
- justify-content: center;
- align-items: center;
-
- padding: var(--sp-normal);
- width: 100%;
- height: 100%;
- background-color: var(--bg-overlay);
- }
-}
-
-.ReactModal__Overlay {
- animation: raw-modal--overlay 150ms;
-}
-
-.ReactModal__Content {
- animation: raw-modal--content 150ms;
-}
-
-@keyframes raw-modal--content {
- 0% {
- transform: translateY(100px);
- opacity: .5;
- }
- 100% {
- transform: translateY(0);
- opacity: 1;
- }
-}
-@keyframes raw-modal--overlay {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
-}
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './ScrollView.scss';
-
-const ScrollView = React.forwardRef(({
- horizontal, vertical, autoHide, invisible, onScroll, children,
-}, ref) => {
- let scrollbarClasses = '';
- if (horizontal) scrollbarClasses += ' scrollbar__h';
- if (vertical) scrollbarClasses += ' scrollbar__v';
- if (autoHide) scrollbarClasses += ' scrollbar--auto-hide';
- if (invisible) scrollbarClasses += ' scrollbar--invisible';
- return (
- <div onScroll={onScroll} ref={ref} className={`scrollbar${scrollbarClasses}`}>
- {children}
- </div>
- );
-});
-
-ScrollView.defaultProps = {
- horizontal: false,
- vertical: true,
- autoHide: false,
- invisible: false,
- onScroll: null,
-};
-
-ScrollView.propTypes = {
- horizontal: PropTypes.bool,
- vertical: PropTypes.bool,
- autoHide: PropTypes.bool,
- invisible: PropTypes.bool,
- onScroll: PropTypes.func,
- children: PropTypes.node.isRequired,
-};
-
-export default ScrollView;
+++ /dev/null
-@use '../../partials/dir';
-@use '_scrollbar';
-
-@mixin paddingForSafari($padding) {
- @media not all and (min-resolution:.001dpcm) {
- @include dir.side(padding, 0, $padding);
- }
-}
-
-.scrollbar {
- width: 100%;
- height: 100%;
- @include scrollbar.scroll;
- @include paddingForSafari(var(--sp-extra-tight));
-
- &__h {
- @include scrollbar.scroll__h;
- }
-
- &__v {
- @include scrollbar.scroll__v;
- }
-
- &--auto-hide {
- @include scrollbar.scroll--auto-hide;
- }
- &--invisible {
- @include scrollbar.scroll--invisible;
- @include paddingForSafari(0);
- }
-}
\ No newline at end of file
+++ /dev/null
-.firefox-scrollbar {
- scrollbar-width: thin;
- scrollbar-color: var(--bg-surface-hover) transparent;
- &--transparent {
- scrollbar-color: transparent transparent;
- }
-}
-.webkit-scrollbar {
- &::-webkit-scrollbar {
- width: 8px;
- height: 8px;
- }
-}
-.webkit-scrollbar-track {
- &::-webkit-scrollbar-track {
- background-color: transparent;
- }
-}
-.webkit-scrollbar-thumb {
- &::-webkit-scrollbar-thumb {
- background-color: var(--bg-surface-hover);
- }
- &::-webkit-scrollbar-thumb:hover {
- background-color: var(--bg-surface-active);
- }
- &--transparent {
- &::-webkit-scrollbar-thumb {
- background-color: transparent;
- }
- }
-}
-
-@mixin scroll {
- overflow: hidden;
- // Below code stop scroll when x-scrollable content come in timeline
- // overscroll-behavior: none;
- @extend .firefox-scrollbar;
- @extend .webkit-scrollbar;
- @extend .webkit-scrollbar-track;
- @extend .webkit-scrollbar-thumb;
-}
-
-@mixin scroll__h {
- overflow-x: scroll;
-}
-@mixin scroll__v {
- overflow-y: scroll;
-}
-@mixin scroll--auto-hide {
- @extend .firefox-scrollbar--transparent;
- @extend .webkit-scrollbar-thumb--transparent;
-
- &:hover {
- @extend .firefox-scrollbar;
- @extend .webkit-scrollbar-thumb;
- }
-}
-@mixin scroll--invisible {
- -ms-overflow-style: none;
- scrollbar-width: none;
-
- &::-webkit-scrollbar {
- display: none;
- }
-}
\ No newline at end of file
+++ /dev/null
-import React, { useState, useEffect } from 'react';
-import PropTypes from 'prop-types';
-import './SegmentedControls.scss';
-
-import { blurOnBubbling } from '../button/script';
-
-import Text from '../text/Text';
-import RawIcon from '../system-icons/RawIcon';
-
-function SegmentedControls({
- selected, segments, onSelect,
-}) {
- const [select, setSelect] = useState(selected);
-
- function selectSegment(segmentIndex) {
- setSelect(segmentIndex);
- onSelect(segmentIndex);
- }
-
- useEffect(() => {
- setSelect(selected);
- }, [selected]);
-
- return (
- <div className="segmented-controls">
- {
- segments.map((segment, index) => (
- <button
- key={Math.random().toString(20).substr(2, 6)}
- className={`segment-btn${select === index ? ' segment-btn--active' : ''}`}
- type="button"
- onClick={() => selectSegment(index)}
- onMouseUp={(e) => blurOnBubbling(e, '.segment-btn')}
- >
- <div className="segment-btn__base">
- {segment.iconSrc && <RawIcon size="small" src={segment.iconSrc} />}
- {segment.text && <Text variant="b2">{segment.text}</Text>}
- </div>
- </button>
- ))
- }
- </div>
- );
-}
-
-SegmentedControls.propTypes = {
- selected: PropTypes.number.isRequired,
- segments: PropTypes.arrayOf(PropTypes.shape({
- iconSrc: PropTypes.string,
- text: PropTypes.string,
- })).isRequired,
- onSelect: PropTypes.func.isRequired,
-};
-
-export default SegmentedControls;
+++ /dev/null
-@use '../button/state';
-@use '../../partials/dir';
-
-.segmented-controls {
- background-color: var(--bg-surface-low);
- border-radius: var(--bo-radius);
- border: 1px solid var(--bg-surface-border);
-
- display: inline-flex;
- overflow: hidden;
-}
-
-.segment-btn {
- padding: var(--sp-extra-tight) 0;
- cursor: pointer;
- @include state.hover(var(--bg-surface-hover));
- @include state.active(var(--bg-surface-active));
-
- &__base {
- padding: 0 var(--sp-normal);
- display: flex;
- align-items: center;
- justify-content: center;
- @include dir.side(border, 1px solid var(--bg-surface-border), none);
-
- & .text:nth-child(2) {
- margin: 0 var(--sp-extra-tight);
- }
- }
- &:first-child &__base {
- border: none;
- }
-
- &--active {
- background-color: var(--bg-surface);
- border: 1px solid var(--bg-surface-border);
- border-width: 0 1px 0 1px;
-
- & .segment-btn__base,
- & + .segment-btn .segment-btn__base {
- border: none;
- }
- &:first-child{
- border-left: none;
- }
- &:last-child {
- border-right: none;
- }
- [dir=rtl] & {
- border-left: 1px solid var(--bg-surface-border);
- border-right: 1px solid var(--bg-surface-border);
-
- &:first-child { border-right: none;}
- &:last-child { border-left: none;}
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './Spinner.scss';
-
-function Spinner({ size }) {
- return (
- <div className={`donut-spinner donut-spinner--${size}`}> </div>
- );
-}
-
-Spinner.defaultProps = {
- size: 'normal',
-};
-
-Spinner.propTypes = {
- size: PropTypes.oneOf(['normal', 'small']),
-};
-
-export default Spinner;
+++ /dev/null
-.donut-spinner {
- display: inline-block;
- border: 4px solid var(--bg-surface-border);
- border-left-color: var(--tc-surface-normal);
- border-radius: 50%;
- animation: donut-spin 1.2s cubic-bezier(0.73, 0.32, 0.67, 0.86) infinite;
-
- &--normal {
- width: 40px;
- height: 40px;
- }
- &--small {
- width: 28px;
- height: 28px;
- }
-}
-
-@keyframes donut-spin {
- to {
- transform: rotate(1turn);
- }
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './RawIcon.scss';
-
-function RawIcon({ color, size, src, isImage }) {
- const style = {};
- if (color !== null) style.backgroundColor = color;
- if (isImage) {
- style.backgroundColor = 'transparent';
- style.backgroundImage = `url("${src}")`;
- } else {
- style.WebkitMaskImage = `url("${src}")`;
- style.maskImage = `url("${src}")`;
- }
-
- return <span className={`ic-raw ic-raw-${size}`} style={style} />;
-}
-
-RawIcon.defaultProps = {
- color: null,
- size: 'normal',
- isImage: false,
-};
-
-RawIcon.propTypes = {
- color: PropTypes.string,
- size: PropTypes.oneOf(['large', 'normal', 'small', 'extra-small']),
- src: PropTypes.string.isRequired,
- isImage: PropTypes.bool,
-};
-
-export default RawIcon;
+++ /dev/null
-@mixin icSize($size) {
- width: $size;
- height: $size;
-}
-
-.ic-raw {
- display: inline-block;
- -webkit-mask-repeat: no-repeat;
- mask-repeat: no-repeat;
- -webkit-mask-size: cover;
- mask-size: cover;
- background-color: var(--ic-surface-normal);
-
- background-size: cover;
- background-repeat: no-repeat;
-}
-.ic-raw-large {
- @include icSize(var(--ic-large));
-}
-.ic-raw-normal {
- @include icSize(var(--ic-normal));
-}
-.ic-raw-small {
- @include icSize(var(--ic-small));
-}
-.ic-raw-extra-small {
- @include icSize(var(--ic-extra-small));
-}
\ No newline at end of file
+++ /dev/null
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import './Tabs.scss';
-
-import Button from '../button/Button';
-import ScrollView from '../scroll/ScrollView';
-
-function TabItem({
- selected, iconSrc,
- onClick, children, disabled,
-}) {
- const isSelected = selected ? 'tab-item--selected' : '';
-
- return (
- <Button
- className={`tab-item ${isSelected}`}
- iconSrc={iconSrc}
- onClick={onClick}
- disabled={disabled}
- >
- {children}
- </Button>
- );
-}
-
-TabItem.defaultProps = {
- selected: false,
- iconSrc: null,
- onClick: null,
- disabled: false,
-};
-
-TabItem.propTypes = {
- selected: PropTypes.bool,
- iconSrc: PropTypes.string,
- onClick: PropTypes.func,
- children: PropTypes.node.isRequired,
- disabled: PropTypes.bool,
-};
-
-function Tabs({ items, defaultSelected, onSelect }) {
- const [selectedItem, setSelectedItem] = useState(items[defaultSelected]);
-
- const handleTabSelection = (item, index, target) => {
- if (selectedItem === item) return;
- target.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
- setSelectedItem(item);
- onSelect(item, index);
- };
-
- return (
- <div className="tabs">
- <ScrollView horizontal vertical={false} invisible>
- <div className="tabs__content">
- {items.map((item, index) => (
- <TabItem
- key={item.text}
- selected={selectedItem.text === item.text}
- iconSrc={item.iconSrc}
- disabled={item.disabled}
- onClick={(e) => handleTabSelection(item, index, e.currentTarget)}
- >
- {item.text}
- </TabItem>
- ))}
- </div>
- </ScrollView>
- </div>
- );
-}
-
-Tabs.defaultProps = {
- defaultSelected: 0,
-};
-
-Tabs.propTypes = {
- items: PropTypes.arrayOf(
- PropTypes.shape({
- iconSrc: PropTypes.string,
- text: PropTypes.string,
- disabled: PropTypes.bool,
- }),
- ).isRequired,
- defaultSelected: PropTypes.number,
- onSelect: PropTypes.func.isRequired,
-};
-
-export default Tabs;
+++ /dev/null
-@use '../../partials/dir';
-
-.tabs {
- height: var(--header-height);
- box-shadow: inset 0 -1px 0 var(--bg-surface-border);
-
- &__content {
- min-width: 100%;
- height: 100%;
- display: flex;
- }
-}
-
-.tab-item {
- flex-shrink: 0;
-
- @include dir.side(padding, var(--sp-normal), 24px);
- border-radius: 0;
- height: 100%;
- box-shadow: none;
- border-radius: var(--bo-radius) var(--bo-radius) 0 0;
-
- &:focus,
- &:active {
- background-color: var(--bg-surface-active);
- box-shadow: none;
- }
-
- &--selected {
- --bs-tab-selected: inset 0 -2px 0 var(--tc-surface-high);
- box-shadow: var(--bs-tab-selected);
-
- & .ic-raw {
- background-color: var(--ic-surface-high);
- }
- & .text {
- font-weight: var(--fw-medium);
- }
- &:focus,
- &:active {
- background-color: var(--bg-surface-active);
- box-shadow: var(--bs-tab-selected);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './Text.scss';
-
-function Text({
- className, style, variant, weight,
- primary, span, children,
-}) {
- const classes = [];
- if (className) classes.push(className);
-
- classes.push(`text text-${variant} text-${weight}`);
- if (primary) classes.push('font-primary');
-
- const textClass = classes.join(' ');
- if (span) return <span className={textClass} style={style}>{ children }</span>;
- if (variant === 'h1') return <h1 className={textClass} style={style}>{ children }</h1>;
- if (variant === 'h2') return <h2 className={textClass} style={style}>{ children }</h2>;
- if (variant === 's1') return <h4 className={textClass} style={style}>{ children }</h4>;
- return <p className={textClass} style={style}>{ children }</p>;
-}
-
-Text.defaultProps = {
- className: null,
- style: null,
- variant: 'b1',
- weight: 'normal',
- primary: false,
- span: false,
-};
-
-Text.propTypes = {
- className: PropTypes.string,
- style: PropTypes.shape({}),
- variant: PropTypes.oneOf(['h1', 'h2', 's1', 'b1', 'b2', 'b3']),
- weight: PropTypes.oneOf(['light', 'normal', 'medium', 'bold']),
- primary: PropTypes.bool,
- span: PropTypes.bool,
- children: PropTypes.node.isRequired,
-};
-
-export default Text;
+++ /dev/null
-@mixin font($type) {
- font-size: var(--fs-#{$type});
- letter-spacing: var(--ls-#{$type});
- line-height: var(--lh-#{$type});
-
- & img.emoji,
- & img[data-mx-emoticon] {
- height: calc(var(--lh-#{$type}) - .25rem);
- }
-}
-
-.text {
- margin: 0;
- padding: 0;
- color: var(--tc-surface-high);
-
- & img.emoji,
- & img[data-mx-emoticon] {
- margin: 0 !important;
- margin-right: 2px !important;
- padding: 0 !important;
- position: relative;
- top: -.1rem;
- vertical-align: middle;
- }
-}
-
-.text-light {
- font-weight: var(--fw-light);
-}
-.text-normal {
- font-weight: var(--fw-normal);
-}
-.text-medium {
- font-weight: var(--fw-medium);
-}
-.text-bold {
- font-weight: var(--fw-bold);
-}
-
-.text-h1 {
- @include font(h1);
-}
-.text-h2 {
- @include font(h2);
-}
-.text-s1 {
- @include font(s1);
-}
-.text-b1 {
- @include font(b1);
- color: var(--tc-surface-normal);
-}
-.text-b2 {
- @include font(b2);
- color: var(--tc-surface-normal);
-}
-.text-b3 {
- @include font(b3);
- color: var(--tc-surface-low);
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import dateFormat from 'dateformat';
-import { isInSameDay } from '../../../util/common';
-
-/**
- * Renders a formatted timestamp.
- *
- * Displays the time in hour:minute format if the message is from today or yesterday, unless `fullTime` is true.
- * For older messages, it shows the date and time.
- *
- * @param {number} timestamp - The timestamp to display.
- * @param {boolean} [fullTime=false] - If true, always show the full date and time.
- * @param {boolean} hour24Clock - Whether to use 24-hour time format.
- * @param {string} dateFormatString - Format string for the date part.
- * @returns {JSX.Element} A <time> element with the formatted date/time.
- */
-function Time({ timestamp, fullTime, hour24Clock, dateFormatString }) {
- const date = new Date(timestamp);
-
- const formattedFullTime = dateFormat(
- date,
- hour24Clock ? 'dd mmmm yyyy, HH:MM' : 'dd mmmm yyyy, hh:MM TT'
- );
- let formattedDate = formattedFullTime;
-
- if (!fullTime) {
- const compareDate = new Date();
- const isToday = isInSameDay(date, compareDate);
- compareDate.setDate(compareDate.getDate() - 1);
- const isYesterday = isInSameDay(date, compareDate);
-
- const timeFormat = hour24Clock ? 'HH:MM' : 'hh:MM TT';
-
- formattedDate = dateFormat(
- date,
- isToday || isYesterday ? timeFormat : dateFormatString.toLowerCase()
- );
- if (isYesterday) {
- formattedDate = `Yesterday, ${formattedDate}`;
- }
- }
-
- return (
- <time dateTime={date.toISOString()} title={formattedFullTime}>
- {formattedDate}
- </time>
- );
-}
-
-Time.defaultProps = {
- fullTime: false,
-};
-
-Time.propTypes = {
- timestamp: PropTypes.number.isRequired,
- fullTime: PropTypes.bool,
- hour24Clock: PropTypes.bool.isRequired,
- dateFormatString: PropTypes.string.isRequired,
-};
-
-export default Time;
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './Tooltip.scss';
-import Tippy from '@tippyjs/react';
-
-function Tooltip({
- className, placement, content, delay, children,
-}) {
- return (
- <Tippy
- content={content}
- className={`tooltip ${className}`}
- touch="hold"
- arrow={false}
- maxWidth={250}
- placement={placement}
- delay={delay}
- duration={[100, 0]}
- >
- {children}
- </Tippy>
- );
-}
-
-Tooltip.defaultProps = {
- placement: 'top',
- className: '',
- delay: [200, 0],
-};
-
-Tooltip.propTypes = {
- className: PropTypes.string,
- placement: PropTypes.string,
- content: PropTypes.node.isRequired,
- delay: PropTypes.arrayOf(PropTypes.number),
- children: PropTypes.node.isRequired,
-};
-
-export default Tooltip;
+++ /dev/null
-.tooltip {
- padding: var(--sp-extra-tight) var(--sp-normal);
- background-color: var(--bg-tooltip);
- border-radius: var(--bo-radius);
- box-shadow: var(--bs-popup);
-
- .text {
- color: var(--tc-tooltip);
- }
-}
\ No newline at end of file
import { ContainerColor } from '../styles/ContainerColor.css';
import { copyToClipboard } from '../utils/dom';
import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback';
-import { clearSecretStorageKeys } from '../../client/state/secretStorageKeys';
+import { clearSecretStorageKeys } from '../../client/secretStorageKeys';
import { ActionUIA, ActionUIAFlowsLoader } from './ActionUIA';
import { useMatrixClient } from '../hooks/useMatrixClient';
import { useAlive } from '../hooks/useAlive';
import { SecretStorageRecoveryKey, SecretStorageRecoveryPassphrase } from './SecretStorage';
import { useMatrixClient } from '../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback';
-import { storePrivateKey } from '../../client/state/secretStorageKeys';
+import { storePrivateKey } from '../../client/secretStorageKeys';
export enum ManualVerificationMethod {
RecoveryPassphrase = 'passphrase',
import * as css from './style.css';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
-import { bytesToSize } from '../../../../util/common';
-import { millisecondsToMinutesAndSeconds } from '../../../utils/common';
+import { bytesToSize, millisecondsToMinutesAndSeconds } from '../../../utils/common';
import {
decryptFile,
downloadEncryptedMedia,
} from '../../../hooks/useRoomAliases';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { CutoutCard } from '../../../components/cutout-card';
-import { getIdServer } from '../../../../util/matrixUtil';
import { replaceSpaceWithDash } from '../../../utils/common';
import { useAlive } from '../../../hooks/useAlive';
import { StateEvent } from '../../../../types/matrix/room';
import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions';
+import { getMxIdServer } from '../../../utils/matrix';
type RoomPublishedAddressesProps = {
permissions: RoomPermissionsAPI;
function LocalAddressInput({ addLocalAlias }: { addLocalAlias: (alias: string) => Promise<void> }) {
const mx = useMatrixClient();
const userId = mx.getSafeUserId();
- const server = getIdServer(userId);
+ const server = getMxIdServer(userId);
const alive = useAlive();
const [addState, addAlias] = useAsyncCallback(addLocalAlias);
import { roomToUnreadAtom } from '../../state/room/roomToUnread';
import { usePowerLevels } from '../../hooks/usePowerLevels';
import { copyToClipboard } from '../../utils/dom';
-import { markAsRead } from '../../../client/action/notifications';
+import { markAsRead } from '../../utils/notifications';
import { UseStateProvider } from '../../components/UseStateProvider';
import { LeaveRoomPrompt } from '../../components/leave-room-prompt';
import { useRoomTypingMember } from '../../hooks/useRoomTypingMembers';
import { PowerLevelsContextProvider, usePowerLevels } from '../../hooks/usePowerLevels';
import { useRoom } from '../../hooks/useRoom';
import { useKeyDown } from '../../hooks/useKeyDown';
-import { markAsRead } from '../../../client/action/notifications';
+import { markAsRead } from '../../utils/notifications';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRoomMembers } from '../../hooks/useRoomMembers';
getIntersectionObserverEntry,
useIntersectionObserver,
} from '../../hooks/useIntersectionObserver';
-import { markAsRead } from '../../../client/action/notifications';
+import { markAsRead } from '../../utils/notifications';
import { useDebounce } from '../../hooks/useDebounce';
import { getResizeObserverEntry, useResizeObserver } from '../../hooks/useResizeObserver';
import * as css from './RoomTimeline.css';
import { RoomViewHeader } from './RoomViewHeader';
import { useKeyDown } from '../../hooks/useKeyDown';
import { editableActiveElement } from '../../utils/dom';
-import navigation from '../../../client/state/navigation';
import { settingsAtom } from '../../state/settings';
import { useSetting } from '../../state/hooks/settings';
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
useCallback(
(evt) => {
if (editableActiveElement()) return;
- if (
- document.body.lastElementChild?.className !== 'ReactModalPortal' ||
- navigation.isRawModalVisible
- ) {
+ // means some menu or modal window is open
+ const lastNode = document.body.lastElementChild;
+ if (lastNode && !lastNode.hasAttribute('data-last-node')) {
return;
}
if (shouldFocusMessageField(evt) || isKeyHotkey('mod+v', evt)) {
import * as css from './RoomViewHeader.css';
import { useRoomUnread } from '../../state/hooks/unread';
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
-import { markAsRead } from '../../../client/action/notifications';
+import { markAsRead } from '../../utils/notifications';
import { roomToUnreadAtom } from '../../state/room/roomToUnread';
import { copyToClipboard } from '../../utils/dom';
import { LeaveRoomPrompt } from '../../components/leave-room-prompt';
import { UnreadBadge, UnreadBadgeCenter } from '../../components/unread-badge';
import { searchModalAtom } from '../../state/searchModal';
import { useKeyDown } from '../../hooks/useKeyDown';
-import navigation from '../../../client/state/navigation';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { KeySymbol } from '../../utils/key-symbol';
import { isMacOS } from '../../utils/user-agent';
}
// means some menu or modal window is open
- const { lastChild } = document.body;
- if (
- (lastChild && 'className' in lastChild && lastChild.className !== 'ReactModalPortal') ||
- navigation.isRawModalVisible
- ) {
+ const lastNode = document.body.lastElementChild;
+ if (lastNode && !lastNode.hasAttribute('data-last-node')) {
return;
}
setOpen(true);
import { SequenceCardStyle } from '../styles.css';
import { SettingTile } from '../../../components/setting-tile';
import CinnySVG from '../../../../../public/res/svg/cinny.svg';
-import cons from '../../../../client/state/cons';
import { clearCacheAndReload } from '../../../../client/initMatrix';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
<Box direction="Column" gap="100">
<Box gap="100" alignItems="End">
<Text size="H3">Cinny</Text>
- <Text size="T200">v{cons.version}</Text>
+ <Text size="T200">v4.9.1</Text>
</Box>
<Text>Yet another matrix client.</Text>
</Box>
import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../styles.css';
import { SettingTile } from '../../../components/setting-tile';
-import { copyToClipboard } from '../../../../util/common';
+import { copyToClipboard } from '../../../utils/dom';
export function MatrixId() {
const mx = useMatrixClient();
-import { Direction, IContextResponse, MatrixClient, Method, Room, RoomMember } from 'matrix-js-sdk';
+import {
+ Direction,
+ EventTimeline,
+ IContextResponse,
+ MatrixClient,
+ Method,
+ Preset,
+ Room,
+ RoomMember,
+ Visibility,
+} from 'matrix-js-sdk';
import { RoomServerAclEventContent } from 'matrix-js-sdk/lib/types';
import { useMemo } from 'react';
import {
rateLimitedActions,
removeRoomIdFromMDirect,
} from '../utils/matrix';
-import { hasDevices } from '../../util/matrixUtil';
-import * as roomActions from '../../client/action/room';
import { useRoomNavigate } from './useRoomNavigate';
import { Membership, StateEvent } from '../../types/matrix/room';
import { getStateEvent } from '../utils/room';
import { splitWithSpace } from '../utils/common';
+import { createRoomEncryptionState } from '../components/create-room';
export const SHRUG = '¯\\_(ツ)_/¯';
export const TABLEFLIP = '(╯°□°)╯︵ ┻━┻';
description: 'Start direct message with user. Example: /startdm userId1',
exe: async (payload) => {
const rawIds = splitWithSpace(payload);
- const userIds = rawIds.filter((id) => isUserId(id) && id !== mx.getUserId());
+ const userIds = rawIds.filter((id) => isUserId(id) && id !== mx.getSafeUserId());
if (userIds.length === 0) return;
if (userIds.length === 1) {
const dmRoomId = getDMRoomFor(mx, userIds[0])?.roomId;
return;
}
}
- const devices = await Promise.all(userIds.map((uid) => hasDevices(mx, uid)));
- const isEncrypt = devices.every((hasDevice) => hasDevice);
- const result = await roomActions.createDM(mx, userIds, isEncrypt);
+ const result = await mx.createRoom({
+ is_direct: true,
+ invite: userIds,
+ visibility: Visibility.Private,
+ preset: Preset.TrustedPrivateChat,
+ initial_state: [createRoomEncryptionState()],
+ });
+ addRoomIdToMDirect(mx, result.room_id, userIds[0]);
navigateRoom(result.room_id);
},
},
description: 'Join room with address. Example: /join address1 address2',
exe: async (payload) => {
const rawIds = splitWithSpace(payload);
- const roomIds = rawIds.filter(
+ const roomIdOrAliases = rawIds.filter(
(idOrAlias) => isRoomId(idOrAlias) || isRoomAlias(idOrAlias)
);
- roomIds.map((id) => roomActions.join(mx, id));
+ roomIdOrAliases.forEach(async (idOrAlias) => {
+ await mx.joinRoom(idOrAlias);
+ });
},
},
[Command.Leave]: {
exe: async (payload) => {
const rawIds = splitWithSpace(payload);
const userIds = rawIds.filter((id) => isUserId(id));
- if (userIds.length > 0) roomActions.ignore(mx, userIds);
+ if (userIds.length > 0) {
+ let ignoredUsers = mx.getIgnoredUsers().concat(userIds);
+ ignoredUsers = [...new Set(ignoredUsers)];
+ await mx.setIgnoredUsers(ignoredUsers);
+ }
},
},
[Command.UnIgnore]: {
exe: async (payload) => {
const rawIds = splitWithSpace(payload);
const userIds = rawIds.filter((id) => isUserId(id));
- if (userIds.length > 0) roomActions.unignore(mx, userIds);
+ if (userIds.length > 0) {
+ const ignoredUsers = mx.getIgnoredUsers();
+ await mx.setIgnoredUsers(ignoredUsers.filter((id) => !userIds.includes(id)));
+ }
},
},
[Command.MyRoomNick]: {
exe: async (payload) => {
const nick = payload.trim();
if (nick === '') return;
- roomActions.setMyRoomNick(mx, room.roomId, nick);
+ const mEvent = room
+ .getLiveTimeline()
+ .getState(EventTimeline.FORWARDS)
+ ?.getStateEvents(StateEvent.RoomMember, mx.getSafeUserId());
+ const content = mEvent?.getContent();
+ if (!content) return;
+ await mx.sendStateEvent(
+ room.roomId,
+ StateEvent.RoomMember as any,
+ {
+ ...content,
+ displayname: nick,
+ },
+ mx.getSafeUserId()
+ );
},
},
[Command.MyRoomAvatar]: {
description: 'Change profile picture in current room. Example /myroomavatar mxc://xyzabc',
exe: async (payload) => {
if (payload.match(/^mxc:\/\/\S+$/)) {
- roomActions.setMyRoomAvatar(mx, room.roomId, payload);
+ const mEvent = room
+ .getLiveTimeline()
+ .getState(EventTimeline.FORWARDS)
+ ?.getStateEvents(StateEvent.RoomMember, mx.getSafeUserId());
+ const content = mEvent?.getContent();
+ if (!content) return;
+ await mx.sendStateEvent(
+ room.roomId,
+ StateEvent.RoomMember as any,
+ {
+ ...content,
+ avatar_url: payload,
+ },
+ mx.getSafeUserId()
+ );
}
},
},
+++ /dev/null
-/* eslint-disable import/prefer-default-export */
-import { useEffect, useRef } from 'react';
-
-export function useStore(...args) {
- const itemRef = useRef(null);
-
- const getItem = () => itemRef.current;
-
- const setItem = (event) => {
- itemRef.current = event;
- return itemRef.current;
- };
-
- useEffect(() => {
- itemRef.current = null;
- return () => {
- itemRef.current = null;
- };
- }, args);
-
- return { getItem, setItem };
-}
+++ /dev/null
-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);
- },
- );
-});
+++ /dev/null
-.confirm-dialog {
- padding: var(--sp-normal);
-
- & > .text {
- padding-bottom: var(--sp-normal);
- }
- &__btn {
- display: flex;
- gap: var(--sp-normal);
- }
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './Dialog.scss';
-
-import Text from '../../atoms/text/Text';
-import Header, { TitleWrapper } from '../../atoms/header/Header';
-import ScrollView from '../../atoms/scroll/ScrollView';
-import RawModal from '../../atoms/modal/RawModal';
-
-function Dialog({
- className,
- isOpen,
- title,
- onAfterOpen,
- onAfterClose,
- contentOptions,
- onRequestClose,
- closeFromOutside,
- children,
- invisibleScroll,
-}) {
- return (
- <RawModal
- className={`${className === null ? '' : `${className} `}dialog-modal`}
- isOpen={isOpen}
- onAfterOpen={onAfterOpen}
- onAfterClose={onAfterClose}
- onRequestClose={onRequestClose}
- closeFromOutside={closeFromOutside}
- size="small"
- >
- <div className="dialog">
- <div className="dialog__content">
- <Header>
- <TitleWrapper>
- {typeof title === 'string' ? (
- <Text variant="h2" weight="medium" primary>
- {title}
- </Text>
- ) : (
- title
- )}
- </TitleWrapper>
- {contentOptions}
- </Header>
- <div className="dialog__content__wrapper">
- <ScrollView autoHide={!invisibleScroll} invisible={invisibleScroll}>
- <div className="dialog__content-container">{children}</div>
- </ScrollView>
- </div>
- </div>
- </div>
- </RawModal>
- );
-}
-
-Dialog.defaultProps = {
- className: null,
- contentOptions: null,
- onAfterOpen: null,
- onAfterClose: null,
- onRequestClose: null,
- closeFromOutside: true,
- invisibleScroll: false,
-};
-
-Dialog.propTypes = {
- className: PropTypes.string,
- isOpen: PropTypes.bool.isRequired,
- title: PropTypes.node.isRequired,
- contentOptions: PropTypes.node,
- onAfterOpen: PropTypes.func,
- onAfterClose: PropTypes.func,
- onRequestClose: PropTypes.func,
- closeFromOutside: PropTypes.bool,
- children: PropTypes.node.isRequired,
- invisibleScroll: PropTypes.bool,
-};
-
-export default Dialog;
+++ /dev/null
-.dialog-modal {
- --modal-height: 656px;
- max-height: min(100%, var(--modal-height));
- display: flex;
-}
-
-.dialog,
-.dialog__content,
-.dialog__content__wrapper {
- flex: 1;
- min-height: 0;
- min-width: 0;
-
- display: flex;
-}
-
-.dialog {
- background-color: var(--bg-surface);
-
- &__content {
- flex-direction: column;
- }
-}
+++ /dev/null
-import React, { useState, useEffect } from 'react';
-
-import cons from '../../../client/state/cons';
-
-import navigation from '../../../client/state/navigation';
-import IconButton from '../../atoms/button/IconButton';
-import Dialog from './Dialog';
-
-import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
-
-function ReusableDialog() {
- const [isOpen, setIsOpen] = useState(false);
- const [data, setData] = useState(null);
-
- useEffect(() => {
- const handleOpen = (title, render, afterClose) => {
- setIsOpen(true);
- setData({ title, render, afterClose });
- };
- navigation.on(cons.events.navigation.REUSABLE_DIALOG_OPENED, handleOpen);
- return () => {
- navigation.removeListener(cons.events.navigation.REUSABLE_DIALOG_OPENED, handleOpen);
- };
- }, []);
-
- const handleAfterClose = () => {
- data.afterClose?.();
- setData(null);
- };
-
- const handleRequestClose = () => {
- setIsOpen(false);
- };
-
- return (
- <Dialog
- isOpen={isOpen}
- title={data?.title || ''}
- onAfterClose={handleAfterClose}
- onRequestClose={handleRequestClose}
- contentOptions={<IconButton src={CrossIC} onClick={handleRequestClose} tooltip="Close" />}
- invisibleScroll
- >
- {data?.render(handleRequestClose) || <div />}
- </Dialog>
- );
-}
-
-export default ReusableDialog;
+++ /dev/null
-import React, { useState, useRef } from 'react';
-import PropTypes from 'prop-types';
-import './ImageUpload.scss';
-
-
-import Text from '../../atoms/text/Text';
-import Avatar from '../../atoms/avatar/Avatar';
-import Spinner from '../../atoms/spinner/Spinner';
-import RawIcon from '../../atoms/system-icons/RawIcon';
-
-import PlusIC from '../../../../public/res/ic/outlined/plus.svg';
-import { useMatrixClient } from '../../hooks/useMatrixClient';
-
-function ImageUpload({
- text, bgColor, imageSrc, onUpload, onRequestRemove,
- size,
-}) {
- const [uploadPromise, setUploadPromise] = useState(null);
- const uploadImageRef = useRef(null);
- const mx = useMatrixClient();
-
- async function uploadImage(e) {
- const file = e.target.files.item(0);
- if (file === null) return;
- try {
- const uPromise = mx.uploadContent(file);
- setUploadPromise(uPromise);
-
- const res = await uPromise;
- if (typeof res?.content_uri === 'string') onUpload(res.content_uri);
- setUploadPromise(null);
- } catch {
- setUploadPromise(null);
- }
- uploadImageRef.current.value = null;
- }
-
- function cancelUpload() {
- mx.cancelUpload(uploadPromise);
- setUploadPromise(null);
- uploadImageRef.current.value = null;
- }
-
- return (
- <div className="img-upload__wrapper">
- <button
- type="button"
- className="img-upload"
- onClick={() => {
- if (uploadPromise !== null) return;
- uploadImageRef.current.click();
- }}
- >
- <Avatar
- imageSrc={imageSrc}
- text={text}
- bgColor={bgColor}
- size={size}
- />
- <div className={`img-upload__process ${uploadPromise === null ? ' img-upload__process--stopped' : ''}`}>
- {uploadPromise === null && (
- size === 'large'
- ? <Text variant="b3" weight="bold">Upload</Text>
- : <RawIcon src={PlusIC} color="white" />
- )}
- {uploadPromise !== null && <Spinner size="small" />}
- </div>
- </button>
- { (typeof imageSrc === 'string' || uploadPromise !== null) && (
- <button
- className="img-upload__btn-cancel"
- type="button"
- onClick={uploadPromise === null ? onRequestRemove : cancelUpload}
- >
- <Text variant="b3">{uploadPromise ? 'Cancel' : 'Remove'}</Text>
- </button>
- )}
- <input onChange={uploadImage} style={{ display: 'none' }} ref={uploadImageRef} type="file" accept="image/*" />
- </div>
- );
-}
-
-ImageUpload.defaultProps = {
- text: null,
- bgColor: 'transparent',
- imageSrc: null,
- size: 'large',
-};
-
-ImageUpload.propTypes = {
- text: PropTypes.string,
- bgColor: PropTypes.string,
- imageSrc: PropTypes.string,
- onUpload: PropTypes.func.isRequired,
- onRequestRemove: PropTypes.func.isRequired,
- size: PropTypes.oneOf(['large', 'normal']),
-};
-
-export default ImageUpload;
+++ /dev/null
-.img-upload__wrapper {
- display: flex;
- flex-direction: column;
- align-items: center;
-}
-
-.img-upload {
- display: flex;
- cursor: pointer;
- position: relative;
-
- &__process {
- width: 100%;
- height: 100%;
- border-radius: var(--bo-radius);
- display: flex;
- justify-content: center;
- align-items: center;
- background-color: rgba(0, 0, 0, .6);
-
- position: absolute;
- left: 0;
- right: 0;
- z-index: 1;
- & .text {
- text-transform: uppercase;
- color: white;
- }
- &--stopped {
- display: none;
- }
- & .donut-spinner {
- border-color: rgb(255, 255, 255, .3);
- border-left-color: white;
- }
- }
- &:hover .img-upload__process--stopped {
- display: flex;
- }
-
-
- &__btn-cancel {
- margin-top: var(--sp-extra-tight);
- cursor: pointer;
- & .text {
- color: var(--tc-danger-normal)
- }
- }
-}
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './PeopleSelector.scss';
-
-import { blurOnBubbling } from '../../atoms/button/script';
-
-import Text from '../../atoms/text/Text';
-import Avatar from '../../atoms/avatar/Avatar';
-
-function PeopleSelector({ avatarSrc, name, color, peopleRole, onClick }) {
- return (
- <div className="people-selector__container">
- <button
- className="people-selector"
- onMouseUp={(e) => blurOnBubbling(e, '.people-selector')}
- onClick={onClick}
- type="button"
- >
- <Avatar imageSrc={avatarSrc} text={name} bgColor={color} size="extra-small" />
- <Text className="people-selector__name" variant="b1">
- {name}
- </Text>
- {peopleRole !== null && (
- <Text className="people-selector__role" variant="b3">
- {peopleRole}
- </Text>
- )}
- </button>
- </div>
- );
-}
-
-PeopleSelector.defaultProps = {
- avatarSrc: null,
- peopleRole: null,
-};
-
-PeopleSelector.propTypes = {
- avatarSrc: PropTypes.string,
- name: PropTypes.string.isRequired,
- color: PropTypes.string.isRequired,
- peopleRole: PropTypes.string,
- onClick: PropTypes.func.isRequired,
-};
-
-export default PeopleSelector;
+++ /dev/null
-@use '../../partials/text';
-
-.people-selector {
- width: 100%;
- padding: var(--sp-extra-tight) var(--sp-normal);
- display: flex;
- align-items: center;
- cursor: pointer;
-
- &__container {
- display: flex;
- }
-
- @media (hover: hover) {
- &:hover {
- background-color: var(--bg-surface-hover);
- }
- }
- &:focus {
- outline: none;
- background-color: var(--bg-surface-hover);
- }
- &:active {
- background-color: var(--bg-surface-active);
- }
-
- &__name {
- @extend .cp-txt__ellipsis;
- flex: 1;
- min-width: 0;
- margin: 0 var(--sp-tight);
- color: var(--tc-surface-normal);
- }
- &__role {
- color: var(--tc-surface-low);
- }
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './PopupWindow.scss';
-
-import Text from '../../atoms/text/Text';
-import IconButton from '../../atoms/button/IconButton';
-import { MenuItem } from '../../atoms/context-menu/ContextMenu';
-import Header, { TitleWrapper } from '../../atoms/header/Header';
-import ScrollView from '../../atoms/scroll/ScrollView';
-import RawModal from '../../atoms/modal/RawModal';
-
-import ChevronLeftIC from '../../../../public/res/ic/outlined/chevron-left.svg';
-
-function PWContentSelector({ selected, variant, iconSrc, type, onClick, children }) {
- const pwcsClass = selected ? ' pw-content-selector--selected' : '';
- return (
- <div className={`pw-content-selector${pwcsClass}`}>
- <MenuItem variant={variant} iconSrc={iconSrc} type={type} onClick={onClick}>
- {children}
- </MenuItem>
- </div>
- );
-}
-
-PWContentSelector.defaultProps = {
- selected: false,
- variant: 'surface',
- iconSrc: 'none',
- type: 'button',
-};
-
-PWContentSelector.propTypes = {
- selected: PropTypes.bool,
- variant: PropTypes.oneOf(['surface', 'caution', 'danger']),
- iconSrc: PropTypes.string,
- type: PropTypes.oneOf(['button', 'submit']),
- onClick: PropTypes.func.isRequired,
- children: PropTypes.string.isRequired,
-};
-
-function PopupWindow({
- className,
- isOpen,
- title,
- contentTitle,
- drawer,
- drawerOptions,
- contentOptions,
- onAfterClose,
- onRequestClose,
- children,
-}) {
- const haveDrawer = drawer !== null;
- const cTitle = contentTitle !== null ? contentTitle : title;
-
- return (
- <RawModal
- className={`${className === null ? '' : `${className} `}pw-modal`}
- overlayClassName="pw-modal__overlay"
- isOpen={isOpen}
- onAfterClose={onAfterClose}
- onRequestClose={onRequestClose}
- size={haveDrawer ? 'large' : 'medium'}
- >
- <div className="pw">
- {haveDrawer && (
- <div className="pw__drawer">
- <Header>
- <IconButton
- size="small"
- src={ChevronLeftIC}
- onClick={onRequestClose}
- tooltip="Back"
- />
- <TitleWrapper>
- {typeof title === 'string' ? (
- <Text variant="s1" weight="medium" primary>
- {title}
- </Text>
- ) : (
- title
- )}
- </TitleWrapper>
- {drawerOptions}
- </Header>
- <div className="pw__drawer__content__wrapper">
- <ScrollView invisible>
- <div className="pw__drawer__content">{drawer}</div>
- </ScrollView>
- </div>
- </div>
- )}
- <div className="pw__content">
- <Header>
- <TitleWrapper>
- {typeof cTitle === 'string' ? (
- <Text variant="h2" weight="medium" primary>
- {cTitle}
- </Text>
- ) : (
- cTitle
- )}
- </TitleWrapper>
- {contentOptions}
- </Header>
- <div className="pw__content__wrapper">
- <ScrollView autoHide>
- <div className="pw__content-container">{children}</div>
- </ScrollView>
- </div>
- </div>
- </div>
- </RawModal>
- );
-}
-
-PopupWindow.defaultProps = {
- className: null,
- drawer: null,
- contentTitle: null,
- drawerOptions: null,
- contentOptions: null,
- onAfterClose: null,
- onRequestClose: null,
-};
-
-PopupWindow.propTypes = {
- className: PropTypes.string,
- isOpen: PropTypes.bool.isRequired,
- title: PropTypes.node.isRequired,
- contentTitle: PropTypes.node,
- drawer: PropTypes.node,
- drawerOptions: PropTypes.node,
- contentOptions: PropTypes.node,
- onAfterClose: PropTypes.func,
- onRequestClose: PropTypes.func,
- children: PropTypes.node.isRequired,
-};
-
-export { PopupWindow as default, PWContentSelector };
+++ /dev/null
-@use '../../partials/dir';
-@use '../../partials/screen';
-
-.pw-modal {
- --modal-height: 774px;
- max-height: var(--modal-height) !important;
- height: 100%;
-
- @include screen.smallerThan(mobileBreakpoint) {
- --modal-height: 100%;
- border-radius: 0 !important;
- &__overlay {
- padding: 0 !important;
- }
- }
-}
-
-.pw {
- width: 100%;
- height: 100%;
- background-color: var(--bg-surface);
-
- display: flex;
-
- &__drawer {
- width: var(--popup-window-drawer-width);
- background-color: var(--bg-surface-low);
- @include dir.side(border, none, 1px solid var(--bg-surface-border));
- }
- &__content {
- flex: 1;
- min-width: 0;
- }
-
- &__drawer,
- &__content {
- display: flex;
- flex-direction: column;
- }
-}
-
-
-.pw__drawer__content,
-.pw__content-container {
- padding-top: var(--sp-extra-tight);
- padding-bottom: var(--sp-extra-loose);
-}
-.pw__drawer__content__wrapper,
-.pw__content__wrapper {
- flex: 1;
- min-height: 0;
-}
-
-.pw__drawer {
- & .header {
- padding-left: var(--sp-tight);
- @include dir.side(padding, var(--sp-tight), var(--sp-tight));
- & .header__title-wrapper {
- @include dir.side(margin, var(--sp-ultra-tight), var(--sp-extra-tight));
- }
- }
-}
-
-.pw-content-selector {
- margin: 0 var(--sp-extra-tight);
- border-radius: var(--bo-radius);
- &--selected {
- box-shadow: var(--bs-surface-border);
- background-color: var(--bg-surface);
-
- & .context-menu__item > button {
- &:hover {
- background-color: transparent;
- }
- }
- }
-
- & .context-menu__item > button {
- border-radius: var(--bo-radius);
- & .ic-raw {
- @include dir.side(margin, 0, var(--sp-tight));
- }
- }
-}
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './PowerLevelSelector.scss';
-
-import IconButton from '../../atoms/button/IconButton';
-import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
-
-import CheckIC from '../../../../public/res/ic/outlined/check.svg';
-
-function PowerLevelSelector({
- value, max, onSelect,
-}) {
- const handleSubmit = (e) => {
- const powerLevel = e.target.elements['power-level']?.value;
- if (!powerLevel) return;
- onSelect(Number(powerLevel));
- };
-
- return (
- <div className="power-level-selector">
- <MenuHeader>Power level selector</MenuHeader>
- <form onSubmit={(e) => { e.preventDefault(); handleSubmit(e); }}>
- <input
- className="input"
- defaultValue={value}
- type="number"
- name="power-level"
- placeholder="Power level"
- max={max}
- autoComplete="off"
- required
- />
- <IconButton variant="primary" src={CheckIC} type="submit" />
- </form>
- {max >= 0 && <MenuHeader>Presets</MenuHeader>}
- {max >= 100 && <MenuItem variant={value === 100 ? 'positive' : 'surface'} onClick={() => onSelect(100)}>Admin - 100</MenuItem>}
- {max >= 50 && <MenuItem variant={value === 50 ? 'positive' : 'surface'} onClick={() => onSelect(50)}>Mod - 50</MenuItem>}
- {max >= 0 && <MenuItem variant={value === 0 ? 'positive' : 'surface'} onClick={() => onSelect(0)}>Member - 0</MenuItem>}
- </div>
- );
-}
-
-PowerLevelSelector.propTypes = {
- value: PropTypes.number.isRequired,
- max: PropTypes.number.isRequired,
- onSelect: PropTypes.func.isRequired,
-};
-
-export default PowerLevelSelector;
+++ /dev/null
-@use '../../partials/flex';
-@use '../../partials/dir';
-
-.power-level-selector {
- & .context-menu__item .text {
- margin: 0 !important;
- }
-
- & form {
- margin: var(--sp-normal);
- display: flex;
-
- & input {
- @extend .cp-fx__item-one;
- @include dir.side(margin, 0, var(--sp-tight));
- width: 148px;
- padding: 9px var(--sp-tight);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './RoomSelector.scss';
-
-import colorMXID from '../../../util/colorMXID';
-
-import Text from '../../atoms/text/Text';
-import Avatar from '../../atoms/avatar/Avatar';
-import NotificationBadge from '../../atoms/badge/NotificationBadge';
-import { blurOnBubbling } from '../../atoms/button/script';
-
-function RoomSelectorWrapper({
- isSelected,
- isMuted,
- isUnread,
- onClick,
- content,
- options,
- onContextMenu,
-}) {
- const classes = ['room-selector'];
- if (isMuted) classes.push('room-selector--muted');
- if (isUnread) classes.push('room-selector--unread');
- if (isSelected) classes.push('room-selector--selected');
-
- return (
- <div className={classes.join(' ')}>
- <button
- className="room-selector__content"
- type="button"
- onClick={onClick}
- onMouseUp={(e) => blurOnBubbling(e, '.room-selector__content')}
- onContextMenu={onContextMenu}
- >
- {content}
- </button>
- <div className="room-selector__options">{options}</div>
- </div>
- );
-}
-RoomSelectorWrapper.defaultProps = {
- isMuted: false,
- options: null,
- onContextMenu: null,
-};
-RoomSelectorWrapper.propTypes = {
- isSelected: PropTypes.bool.isRequired,
- isMuted: PropTypes.bool,
- isUnread: PropTypes.bool.isRequired,
- onClick: PropTypes.func.isRequired,
- content: PropTypes.node.isRequired,
- options: PropTypes.node,
- onContextMenu: PropTypes.func,
-};
-
-function RoomSelector({
- name,
- parentName,
- roomId,
- imageSrc,
- iconSrc,
- isSelected,
- isMuted,
- isUnread,
- notificationCount,
- isAlert,
- options,
- onClick,
- onContextMenu,
-}) {
- return (
- <RoomSelectorWrapper
- isSelected={isSelected}
- isMuted={isMuted}
- isUnread={isUnread}
- content={
- <>
- <Avatar
- text={name}
- bgColor={colorMXID(roomId)}
- imageSrc={imageSrc}
- iconColor="var(--ic-surface-low)"
- iconSrc={iconSrc}
- size="extra-small"
- />
- <Text variant="b1" weight={isUnread ? 'medium' : 'normal'}>
- {name}
- {parentName && (
- <Text variant="b3" span>
- {' — '}
- {parentName}
- </Text>
- )}
- </Text>
- {isUnread && (
- <NotificationBadge
- alert={isAlert}
- content={notificationCount !== 0 ? notificationCount : null}
- />
- )}
- </>
- }
- options={options}
- onClick={onClick}
- onContextMenu={onContextMenu}
- />
- );
-}
-RoomSelector.defaultProps = {
- parentName: null,
- isSelected: false,
- imageSrc: null,
- iconSrc: null,
- isMuted: false,
- options: null,
- onContextMenu: null,
-};
-RoomSelector.propTypes = {
- name: PropTypes.string.isRequired,
- parentName: PropTypes.string,
- roomId: PropTypes.string.isRequired,
- imageSrc: PropTypes.string,
- iconSrc: PropTypes.string,
- isSelected: PropTypes.bool,
- isMuted: PropTypes.bool,
- isUnread: PropTypes.bool.isRequired,
- notificationCount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
- isAlert: PropTypes.bool.isRequired,
- options: PropTypes.node,
- onClick: PropTypes.func.isRequired,
- onContextMenu: PropTypes.func,
-};
-
-export default RoomSelector;
+++ /dev/null
-@use '../../partials/flex';
-@use '../../partials/text';
-@use '../../partials/dir';
-
-.room-selector {
- @extend .cp-fx__row--s-c;
-
- border: 1px solid transparent;
- border-radius: var(--bo-radius);
- cursor: pointer;
-
- &--muted {
- opacity: 0.6;
- }
-
- &--unread {
- .room-selector__content > .text {
- color: var(--tc-surface-high);
- }
- }
-
- &--selected {
- background-color: var(--bg-surface);
- border-color: var(--bg-surface-border);
-
- & .room-selector__options {
- display: flex;
- }
- }
-
- @media (hover: hover) {
- &:hover {
- background-color: var(--bg-surface-hover);
- & .room-selector__options {
- display: flex;
- }
- }
- }
- &:focus-within {
- background-color: var(--bg-surface-hover);
- & button {
- outline: none;
- }
- }
- &:active {
- background-color: var(--bg-surface-active);
- }
- &--selected:hover,
- &--selected:focus,
- &--selected:active {
- background-color: var(--bg-surface);
- }
-}
-
-.room-selector__content {
- @extend .cp-fx__item-one;
- @extend .cp-fx__row--s-c;
- padding: 0 var(--sp-extra-tight);
- min-height: 40px;
- cursor: inherit;
-
- & > .avatar-container .avatar__border--active {
- box-shadow: none;
- }
-
- & > .text {
- @extend .cp-fx__item-one;
- @extend .cp-txt__ellipsis;
- margin: 0 var(--sp-extra-tight);
-
- color: var(--tc-surface-normal-low);
- }
-}
-.room-selector__options {
- @extend .cp-fx__row--s-c;
- @include dir.side(margin, 0, var(--sp-ultra-tight));
- display: none;
-
- &:empty {
- margin: 0 !important;
- }
-
- & .ic-btn {
- padding: 6px;
- border-radius: calc(var(--bo-radius) / 2);
- }
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './RoomTile.scss';
-
-import colorMXID from '../../../util/colorMXID';
-
-import Text from '../../atoms/text/Text';
-import Avatar from '../../atoms/avatar/Avatar';
-
-function RoomTile({ avatarSrc, name, id, inviterName, memberCount, desc, options }) {
- return (
- <div className="room-tile">
- <div className="room-tile__avatar">
- <Avatar imageSrc={avatarSrc} bgColor={colorMXID(id)} text={name} />
- </div>
- <div className="room-tile__content">
- <Text variant="s1">{name}</Text>
- <Text variant="b3">
- {inviterName !== null
- ? `Invited by ${inviterName} to ${id}${
- memberCount === null ? '' : ` • ${memberCount} members`
- }`
- : id + (memberCount === null ? '' : ` • ${memberCount} members`)}
- </Text>
- {desc !== null && typeof desc === 'string' ? (
- <Text className="room-tile__content__desc" variant="b2">
- {desc}
- </Text>
- ) : (
- desc
- )}
- </div>
- {options !== null && <div className="room-tile__options">{options}</div>}
- </div>
- );
-}
-
-RoomTile.defaultProps = {
- avatarSrc: null,
- inviterName: null,
- options: null,
- desc: null,
- memberCount: null,
-};
-RoomTile.propTypes = {
- avatarSrc: PropTypes.string,
- name: PropTypes.string.isRequired,
- id: PropTypes.string.isRequired,
- inviterName: PropTypes.string,
- memberCount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
- desc: PropTypes.node,
- options: PropTypes.node,
-};
-
-export default RoomTile;
+++ /dev/null
-.room-tile {
- display: flex;
-
- &__content {
- flex: 1;
- min-width: 0;
-
- margin: 0 var(--sp-normal);
-
- &__desc {
- white-space: pre-wrap;
- & a {
- white-space: wrap;
- }
- }
-
- & .text:not(:first-child) {
- margin-top: var(--sp-ultra-tight);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-import PropTypes from 'prop-types';
-import './SettingTile.scss';
-
-import Text from '../../atoms/text/Text';
-
-function SettingTile({ title, options, content }) {
- return (
- <div className="setting-tile">
- <div className="setting-tile__content">
- <div className="setting-tile__title">
- {
- typeof title === 'string'
- ? <Text variant="b1">{title}</Text>
- : title
- }
- </div>
- {content}
- </div>
- {options !== null && <div className="setting-tile__options">{options}</div>}
- </div>
- );
-}
-
-SettingTile.defaultProps = {
- options: null,
- content: null,
-};
-
-SettingTile.propTypes = {
- title: PropTypes.node.isRequired,
- options: PropTypes.node,
- content: PropTypes.node,
-};
-
-export default SettingTile;
+++ /dev/null
-@use '../../partials/dir';
-
-.setting-tile {
- display: flex;
- &__content {
- flex: 1;
- min-width: 0;
- }
- &__title {
- margin-bottom: var(--sp-ultra-tight);
- }
- &__options {
- @include dir.side(margin, var(--sp-tight), 0);
- }
-}
\ No newline at end of file
+++ /dev/null
-import React, { useState, useEffect } from 'react';
-import PropTypes from 'prop-types';
-import { useAtomValue } from 'jotai';
-import './SpaceAddExisting.scss';
-
-import cons from '../../../client/state/cons';
-import navigation from '../../../client/state/navigation';
-import { joinRuleToIconSrc } from '../../../util/matrixUtil';
-import { Debounce } from '../../../util/common';
-
-import Text from '../../atoms/text/Text';
-import RawIcon from '../../atoms/system-icons/RawIcon';
-import Button from '../../atoms/button/Button';
-import IconButton from '../../atoms/button/IconButton';
-import Checkbox from '../../atoms/button/Checkbox';
-import Input from '../../atoms/input/Input';
-import Spinner from '../../atoms/spinner/Spinner';
-import RoomSelector from '../room-selector/RoomSelector';
-import Dialog from '../dialog/Dialog';
-
-import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
-import SearchIC from '../../../../public/res/ic/outlined/search.svg';
-
-import { roomToParentsAtom } from '../../state/room/roomToParents';
-import { useDirects, useRooms, useSpaces } from '../../state/hooks/roomList';
-import { allRoomsAtom } from '../../state/room-list/roomList';
-import { mDirectAtom } from '../../state/mDirectList';
-import { useMatrixClient } from '../../hooks/useMatrixClient';
-import { getViaServers } from '../../plugins/via-servers';
-import { rateLimitedActions } from '../../utils/matrix';
-import { useAlive } from '../../hooks/useAlive';
-
-function SpaceAddExistingContent({ roomId, spaces: onlySpaces }) {
- const alive = useAlive();
- const [debounce] = useState(new Debounce());
- const [process, setProcess] = useState(null);
- const [allRoomIds, setAllRoomIds] = useState([]);
- const [selected, setSelected] = useState([]);
- const [searchIds, setSearchIds] = useState(null);
- const mx = useMatrixClient();
- const roomIdToParents = useAtomValue(roomToParentsAtom);
- const mDirects = useAtomValue(mDirectAtom);
- const spaces = useSpaces(mx, allRoomsAtom);
- const rooms = useRooms(mx, allRoomsAtom, mDirects);
- const directs = useDirects(mx, allRoomsAtom, mDirects);
-
- useEffect(() => {
- const roomIds = onlySpaces ? [...spaces] : [...rooms, ...directs];
- const allIds = roomIds.filter(
- (rId) => rId !== roomId && !roomIdToParents.get(rId)?.has(roomId)
- );
- setAllRoomIds(allIds);
- }, [spaces, rooms, directs, roomIdToParents, roomId, onlySpaces]);
-
- const toggleSelection = (rId) => {
- if (process !== null) return;
- const newSelected = [...selected];
- const selectedIndex = newSelected.indexOf(rId);
-
- if (selectedIndex > -1) {
- newSelected.splice(selectedIndex, 1);
- setSelected(newSelected);
- return;
- }
- newSelected.push(rId);
- setSelected(newSelected);
- };
-
- const handleAdd = async () => {
- setProcess(`Adding ${selected.length} items...`);
-
- await rateLimitedActions(selected, async (rId) => {
- const room = mx.getRoom(rId);
- const via = getViaServers(room);
-
- await mx.sendStateEvent(
- roomId,
- 'm.space.child',
- {
- auto_join: false,
- suggested: false,
- via,
- },
- rId
- );
- });
-
- if (!alive()) return;
-
- const roomIds = onlySpaces ? [...spaces] : [...rooms, ...directs];
- const allIds = roomIds.filter(
- (rId) => rId !== roomId && !roomIdToParents.get(rId)?.has(roomId) && !selected.includes(rId)
- );
- setAllRoomIds(allIds);
- setProcess(null);
- setSelected([]);
- };
-
- const handleSearch = (ev) => {
- const term = ev.target.value.toLocaleLowerCase().replace(/\s/g, '');
- if (term === '') {
- setSearchIds(null);
- return;
- }
-
- debounce._(() => {
- const searchedIds = allRoomIds.filter((rId) => {
- let name = mx.getRoom(rId)?.name;
- if (!name) return false;
- name = name.normalize('NFKC').toLocaleLowerCase().replace(/\s/g, '');
- return name.includes(term);
- });
- setSearchIds(searchedIds);
- }, 200)();
- };
- const handleSearchClear = (ev) => {
- const btn = ev.currentTarget;
- btn.parentElement.searchInput.value = '';
- setSearchIds(null);
- };
-
- return (
- <>
- <form
- onSubmit={(ev) => {
- ev.preventDefault();
- }}
- >
- <RawIcon size="small" src={SearchIC} />
- <Input name="searchInput" onChange={handleSearch} placeholder="Search room" autoFocus />
- <IconButton size="small" type="button" onClick={handleSearchClear} src={CrossIC} />
- </form>
- {searchIds?.length === 0 && <Text>No results found</Text>}
- {(searchIds || allRoomIds).map((rId) => {
- const room = mx.getRoom(rId);
- let imageSrc =
- room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
- if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
-
- const parentSet = roomIdToParents.get(rId);
- const parentNames = parentSet
- ? [...parentSet].map((parentId) => mx.getRoom(parentId).name)
- : undefined;
- const parents = parentNames ? parentNames.join(', ') : null;
-
- const handleSelect = () => toggleSelection(rId);
-
- return (
- <RoomSelector
- key={rId}
- name={room.name}
- parentName={parents}
- roomId={rId}
- imageSrc={mDirects.has(rId) ? imageSrc : null}
- iconSrc={
- mDirects.has(rId) ? null : joinRuleToIconSrc(room.getJoinRule(), room.isSpaceRoom())
- }
- isUnread={false}
- notificationCount={0}
- isAlert={false}
- onClick={handleSelect}
- options={
- <Checkbox
- isActive={selected.includes(rId)}
- variant="positive"
- onToggle={handleSelect}
- tabIndex={-1}
- disabled={process !== null}
- />
- }
- />
- );
- })}
- {selected.length !== 0 && (
- <div className="space-add-existing__footer">
- {process && <Spinner size="small" />}
- <Text weight="medium">{process || `${selected.length} item selected`}</Text>
- {!process && (
- <Button onClick={handleAdd} variant="primary">
- Add
- </Button>
- )}
- </div>
- )}
- </>
- );
-}
-SpaceAddExistingContent.propTypes = {
- roomId: PropTypes.string.isRequired,
- spaces: PropTypes.bool.isRequired,
-};
-
-function useVisibilityToggle() {
- const [data, setData] = useState(null);
-
- useEffect(() => {
- const handleOpen = (roomId, spaces) =>
- setData({
- roomId,
- spaces,
- });
- navigation.on(cons.events.navigation.SPACE_ADDEXISTING_OPENED, handleOpen);
- return () => {
- navigation.removeListener(cons.events.navigation.SPACE_ADDEXISTING_OPENED, handleOpen);
- };
- }, []);
-
- const requestClose = () => setData(null);
-
- return [data, requestClose];
-}
-
-function SpaceAddExisting() {
- const [data, requestClose] = useVisibilityToggle();
- const mx = useMatrixClient();
- const room = mx.getRoom(data?.roomId);
-
- return (
- <Dialog
- isOpen={!!room}
- className="space-add-existing"
- title={
- <Text variant="s1" weight="medium" primary>
- {room && room.name}
- <span style={{ color: 'var(--tc-surface-low)' }}>
- {' '}
- — add existing {data?.spaces ? 'spaces' : 'rooms'}
- </span>
- </Text>
- }
- contentOptions={<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />}
- onRequestClose={requestClose}
- >
- {room ? <SpaceAddExistingContent roomId={room.roomId} spaces={data.spaces} /> : <div />}
- </Dialog>
- );
-}
-
-export default SpaceAddExisting;
+++ /dev/null
-@use '../../partials/dir';
-@use '../../partials/flex';
-
-.space-add-existing {
- height: 100%;
-
- .dialog__content-container {
- padding: 0;
- padding-bottom: 80px;
- @include dir.side(padding, var(--sp-extra-tight), 0);
-
- & > .text {
- margin: var(--sp-loose) var(--sp-normal);
- text-align: center;
- }
- }
-
- & form {
- @extend .cp-fx__row--s-c;
- padding: var(--sp-extra-tight);
- padding-top: var(--sp-normal);
-
- position: sticky;
- top: 0;
- z-index: 999;
- background-color: var(--bg-surface);
-
- & > .ic-raw,
- & > .ic-btn {
- position: absolute;
- }
- & > .ic-raw {
- margin: 0 var(--sp-tight);
- }
- & > .ic-btn {
- border-radius: calc(var(--bo-radius) / 2);
- @include dir.prop(right, var(--sp-tight), unset);
- @include dir.prop(left, unset, var(--sp-tight));
- }
- & input {
- padding: var(--sp-tight) 40px;
- }
- }
-
- .input-container {
- @extend .cp-fx__item-one;
- }
-
- .room-selector {
- margin: 0 var(--sp-extra-tight);
- }
- .room-selector__options {
- display: flex;
- margin: 0 10px;
- }
-}
-
-.space-add-existing__footer {
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- padding: var(--sp-normal);
- background-color: var(--bg-surface);
- border-top: 1px solid var(--bg-surface-border);
- display: flex;
- align-items: center;
-
- & > .text {
- @extend .cp-fx__item-one;
- padding: 0 var(--sp-tight);
- }
-
- & > button {
- @include dir.side(margin, var(--sp-normal), 0);
- }
-}
\ No newline at end of file
+++ /dev/null
-import React, { useState, useEffect, useRef } from 'react';
-import PropTypes from 'prop-types';
-import './CreateRoom.scss';
-
-import cons from '../../../client/state/cons';
-import navigation from '../../../client/state/navigation';
-import { openReusableContextMenu } from '../../../client/action/navigation';
-import * as roomActions from '../../../client/action/room';
-import { isRoomAliasAvailable, getIdServer } from '../../../util/matrixUtil';
-import { getEventCords } from '../../../util/common';
-
-import Text from '../../atoms/text/Text';
-import Button from '../../atoms/button/Button';
-import Toggle from '../../atoms/button/Toggle';
-import IconButton from '../../atoms/button/IconButton';
-import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
-import Input from '../../atoms/input/Input';
-import Spinner from '../../atoms/spinner/Spinner';
-import SegmentControl from '../../atoms/segmented-controls/SegmentedControls';
-import Dialog from '../../molecules/dialog/Dialog';
-import SettingTile from '../../molecules/setting-tile/SettingTile';
-
-import HashPlusIC from '../../../../public/res/ic/outlined/hash-plus.svg';
-import SpacePlusIC from '../../../../public/res/ic/outlined/space-plus.svg';
-import HashIC from '../../../../public/res/ic/outlined/hash.svg';
-import HashLockIC from '../../../../public/res/ic/outlined/hash-lock.svg';
-import HashGlobeIC from '../../../../public/res/ic/outlined/hash-globe.svg';
-import SpaceIC from '../../../../public/res/ic/outlined/space.svg';
-import SpaceLockIC from '../../../../public/res/ic/outlined/space-lock.svg';
-import SpaceGlobeIC from '../../../../public/res/ic/outlined/space-globe.svg';
-import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
-import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
-import { useRoomNavigate } from '../../hooks/useRoomNavigate';
-import { useMatrixClient } from '../../hooks/useMatrixClient';
-
-function CreateRoomContent({ isSpace, parentId, onRequestClose }) {
- const [joinRule, setJoinRule] = useState(parentId ? 'restricted' : 'invite');
- const [isEncrypted, setIsEncrypted] = useState(true);
- const [isCreatingRoom, setIsCreatingRoom] = useState(false);
- const [creatingError, setCreatingError] = useState(null);
- const { navigateRoom, navigateSpace } = useRoomNavigate();
-
- const [isValidAddress, setIsValidAddress] = useState(null);
- const [addressValue, setAddressValue] = useState(undefined);
- const [roleIndex, setRoleIndex] = useState(0);
-
- const addressRef = useRef(null);
-
- const mx = useMatrixClient();
- const userHs = getIdServer(mx.getUserId());
-
- const handleSubmit = async (evt) => {
- evt.preventDefault();
- const { target } = evt;
-
- if (isCreatingRoom) return;
- setIsCreatingRoom(true);
- setCreatingError(null);
-
- const name = target.name.value;
- let topic = target.topic.value;
- if (topic.trim() === '') topic = undefined;
- let roomAlias;
- if (joinRule === 'public') {
- roomAlias = addressRef?.current?.value;
- if (roomAlias.trim() === '') roomAlias = undefined;
- }
-
- const powerLevel = roleIndex === 1 ? 101 : undefined;
-
- try {
- const data = await roomActions.createRoom(mx, {
- name,
- topic,
- joinRule,
- alias: roomAlias,
- isEncrypted: isSpace || joinRule === 'public' ? false : isEncrypted,
- powerLevel,
- isSpace,
- parentId,
- });
- setIsCreatingRoom(false);
- setCreatingError(null);
- setIsValidAddress(null);
- setAddressValue(undefined);
- onRequestClose();
- if (isSpace) {
- navigateSpace(data.room_id);
- } else {
- navigateRoom(data.room_id);
- }
- } catch (e) {
- if (e.message === 'M_UNKNOWN: Invalid characters in room alias') {
- setCreatingError('ERROR: Invalid characters in address');
- setIsValidAddress(false);
- } else if (e.message === 'M_ROOM_IN_USE: Room alias already taken') {
- setCreatingError('ERROR: This address is already in use');
- setIsValidAddress(false);
- } else setCreatingError(e.message);
- setIsCreatingRoom(false);
- }
- };
-
- const validateAddress = (e) => {
- const myAddress = e.target.value;
- setIsValidAddress(null);
- setAddressValue(e.target.value);
- setCreatingError(null);
-
- setTimeout(async () => {
- if (myAddress !== addressRef.current.value) return;
- const roomAlias = addressRef.current.value;
- if (roomAlias === '') return;
- const roomAddress = `#${roomAlias}:${userHs}`;
-
- if (await isRoomAliasAvailable(mx, roomAddress)) {
- setIsValidAddress(true);
- } else {
- setIsValidAddress(false);
- }
- }, 1000);
- };
-
- const joinRules = ['invite', 'restricted', 'public'];
- const joinRuleShortText = ['Private', 'Restricted', 'Public'];
- const joinRuleText = [
- 'Private (invite only)',
- 'Restricted (space member can join)',
- 'Public (anyone can join)',
- ];
- const jrRoomIC = [HashLockIC, HashIC, HashGlobeIC];
- const jrSpaceIC = [SpaceLockIC, SpaceIC, SpaceGlobeIC];
- const handleJoinRule = (evt) => {
- openReusableContextMenu('bottom', getEventCords(evt, '.btn-surface'), (closeMenu) => (
- <>
- <MenuHeader>Visibility (who can join)</MenuHeader>
- {joinRules.map((rule) => (
- <MenuItem
- key={rule}
- variant={rule === joinRule ? 'positive' : 'surface'}
- iconSrc={
- isSpace ? jrSpaceIC[joinRules.indexOf(rule)] : jrRoomIC[joinRules.indexOf(rule)]
- }
- onClick={() => {
- closeMenu();
- setJoinRule(rule);
- }}
- disabled={!parentId && rule === 'restricted'}
- >
- {joinRuleText[joinRules.indexOf(rule)]}
- </MenuItem>
- ))}
- </>
- ));
- };
-
- return (
- <div className="create-room">
- <form className="create-room__form" onSubmit={handleSubmit}>
- <SettingTile
- title="Visibility"
- options={
- <Button onClick={handleJoinRule} iconSrc={ChevronBottomIC}>
- {joinRuleShortText[joinRules.indexOf(joinRule)]}
- </Button>
- }
- content={
- <Text variant="b3">{`Select who can join this ${isSpace ? 'space' : 'room'}.`}</Text>
- }
- />
- {joinRule === 'public' && (
- <div>
- <Text className="create-room__address__label" variant="b2">
- {isSpace ? 'Space address' : 'Room address'}
- </Text>
- <div className="create-room__address">
- <Text variant="b1">#</Text>
- <Input
- value={addressValue}
- onChange={validateAddress}
- state={isValidAddress === false ? 'error' : 'normal'}
- forwardRef={addressRef}
- placeholder="my_address"
- required
- />
- <Text variant="b1">{`:${userHs}`}</Text>
- </div>
- {isValidAddress === false && (
- <Text className="create-room__address__tip" variant="b3">
- <span
- style={{ color: 'var(--bg-danger)' }}
- >{`#${addressValue}:${userHs} is already in use`}</span>
- </Text>
- )}
- </div>
- )}
- {!isSpace && joinRule !== 'public' && (
- <SettingTile
- title="Enable end-to-end encryption"
- options={<Toggle isActive={isEncrypted} onToggle={setIsEncrypted} />}
- content={
- <Text variant="b3">
- You can’t disable this later. Bridges & most bots won’t work yet.
- </Text>
- }
- />
- )}
- <SettingTile
- title="Select your role"
- options={
- <SegmentControl
- selected={roleIndex}
- segments={[{ text: 'Admin' }, { text: 'Founder' }]}
- onSelect={setRoleIndex}
- />
- }
- content={
- <Text variant="b3">Selecting Admin sets 100 power level whereas Founder sets 101.</Text>
- }
- />
- <Input name="topic" minHeight={174} resizable label="Topic (optional)" />
- <div className="create-room__name-wrapper">
- <Input name="name" label={`${isSpace ? 'Space' : 'Room'} name`} required />
- <Button
- disabled={isValidAddress === false || isCreatingRoom}
- iconSrc={isSpace ? SpacePlusIC : HashPlusIC}
- type="submit"
- variant="primary"
- >
- Create
- </Button>
- </div>
- {isCreatingRoom && (
- <div className="create-room__loading">
- <Spinner size="small" />
- <Text>{`Creating ${isSpace ? 'space' : 'room'}...`}</Text>
- </div>
- )}
- {typeof creatingError === 'string' && (
- <Text className="create-room__error" variant="b3">
- {creatingError}
- </Text>
- )}
- </form>
- </div>
- );
-}
-CreateRoomContent.defaultProps = {
- parentId: null,
-};
-CreateRoomContent.propTypes = {
- isSpace: PropTypes.bool.isRequired,
- parentId: PropTypes.string,
- onRequestClose: PropTypes.func.isRequired,
-};
-
-function useWindowToggle() {
- const [create, setCreate] = useState(null);
-
- useEffect(() => {
- const handleOpen = (isSpace, parentId) => {
- setCreate({
- isSpace,
- parentId,
- });
- };
- navigation.on(cons.events.navigation.CREATE_ROOM_OPENED, handleOpen);
- return () => {
- navigation.removeListener(cons.events.navigation.CREATE_ROOM_OPENED, handleOpen);
- };
- }, []);
-
- const onRequestClose = () => setCreate(null);
-
- return [create, onRequestClose];
-}
-
-function CreateRoom() {
- const [create, onRequestClose] = useWindowToggle();
- const { isSpace, parentId } = create ?? {};
- const mx = useMatrixClient();
- const room = mx.getRoom(parentId);
-
- return (
- <Dialog
- isOpen={create !== null}
- title={
- <Text variant="s1" weight="medium" primary>
- {parentId ? room.name : 'Home'}
- <span style={{ color: 'var(--tc-surface-low)' }}>
- {` — create ${isSpace ? 'space' : 'room'}`}
- </span>
- </Text>
- }
- contentOptions={<IconButton src={CrossIC} onClick={onRequestClose} tooltip="Close" />}
- onRequestClose={onRequestClose}
- >
- {create ? (
- <CreateRoomContent isSpace={isSpace} parentId={parentId} onRequestClose={onRequestClose} />
- ) : (
- <div />
- )}
- </Dialog>
- );
-}
-
-export default CreateRoom;
+++ /dev/null
-@use '../../partials/flex';
-@use '../../partials/dir';
-
-.create-room {
- margin: var(--sp-normal);
- @include dir.side(margin, var(--sp-normal), var(--sp-extra-tight));
-
- &__form > * {
- margin-top: var(--sp-normal);
- &:first-child {
- margin-top: var(--sp-extra-tight);
- }
- }
-
- & .segment-btn {
- padding: var(--sp-ultra-tight) 0;
- &__base {
- padding: 0 var(--sp-tight);
- }
- }
-
- &__address {
- display: flex;
- &__label {
- color: var(--tc-surface-low);
- margin-bottom: var(--sp-ultra-tight);
- }
- &__tip {
- margin-top: var(--sp-ultra-tight);
- @include dir.side(margin, 46px, 0);
- }
- & .text {
- display: flex;
- align-items: center;
- padding: 0 var(--sp-normal);
- border: 1px solid var(--bg-surface-border);
- border-radius: var(--bo-radius);
- color: var(--tc-surface-low);
- }
- & *:nth-child(2) {
- flex: 1;
- min-width: 0;
- & .input {
- border-radius: 0;
- }
- }
- & .text:first-child {
- @include dir.prop(border-width, 1px 0 1px 1px, 1px 1px 1px 0);
- @include dir.prop(
- border-radius,
- var(--bo-radius) 0 0 var(--bo-radius),
- 0 var(--bo-radius) var(--bo-radius) 0,
- );
- }
- & .text:last-child {
- @include dir.prop(border-width, 1px 1px 1px 0, 1px 0 1px 1px);
- @include dir.prop(
- border-radius,
- 0 var(--bo-radius) var(--bo-radius) 0,
- var(--bo-radius) 0 0 var(--bo-radius),
- );
- }
- }
-
- &__name-wrapper {
- display: flex;
- align-items: flex-end;
-
- & .input-container {
- flex: 1;
- min-width: 0;
- @include dir.side(margin, 0, var(--sp-normal));
- }
- & .btn-primary {
- padding-top: 11px;
- padding-bottom: 11px;
- }
- }
-
- &__loading {
- @extend .cp-fx__row--c-c;
- & .text {
- @include dir.side(margin, var(--sp-normal), 0);
- }
- }
- &__error {
- text-align: center;
- color: var(--bg-danger) !important;
- }
-}
\ No newline at end of file
+++ /dev/null
-// https://github.com/Sorunome/matrix-doc/blob/soru/emotes/proposals/2545-emotes.md
-
-export class ImagePack {
- static parsePack(eventId, packContent) {
- if (!eventId || typeof packContent?.images !== 'object') {
- return null;
- }
-
- return new ImagePack(eventId, packContent);
- }
-
- constructor(eventId, content) {
- this.id = eventId;
- this.content = JSON.parse(JSON.stringify(content));
-
- this.applyPack(content);
- this.applyImages(content);
- }
-
- applyPack(content) {
- const pack = content.pack ?? {};
-
- this.displayName = pack.display_name;
- this.avatarUrl = pack.avatar_url;
- this.usage = pack.usage ?? ['emoticon', 'sticker'];
- this.attribution = pack.attribution;
- }
-
- applyImages(content) {
- this.images = new Map();
- this.emoticons = [];
- this.stickers = [];
-
- Object.entries(content.images).forEach(([shortcode, data]) => {
- const mxc = data.url;
- const body = data.body ?? shortcode;
- const usage = data.usage ?? this.usage;
- const { info } = data;
-
- if (!mxc) return;
- const image = {
- shortcode, mxc, body, usage, info,
- };
-
- this.images.set(shortcode, image);
- if (usage.includes('emoticon')) {
- this.emoticons.push(image);
- }
- if (usage.includes('sticker')) {
- this.stickers.push(image);
- }
- });
- }
-
- getImages() {
- return this.images;
- }
-
- getEmojis() {
- return this.emoticons;
- }
-
- getStickers() {
- return this.stickers;
- }
-
- getContent() {
- return this.content;
- }
-
- _updatePackProperty(property, value) {
- if (this.content.pack === undefined) {
- this.content.pack = {};
- }
- this.content.pack[property] = value;
- this.applyPack(this.content);
- }
-
- setAvatarUrl(avatarUrl) {
- this._updatePackProperty('avatar_url', avatarUrl);
- }
-
- setDisplayName(displayName) {
- this._updatePackProperty('display_name', displayName);
- }
-
- setAttribution(attribution) {
- this._updatePackProperty('attribution', attribution);
- }
-
- setUsage(usage) {
- this._updatePackProperty('usage', usage);
- }
-
- addImage(key, imgContent) {
- this.content.images = {
- [key]: imgContent,
- ...this.content.images,
- };
- this.applyImages(this.content);
- }
-
- removeImage(key) {
- if (this.content.images[key] === undefined) return;
- delete this.content.images[key];
- this.applyImages(this.content);
- }
-
- updateImageKey(key, newKey) {
- if (this.content.images[key] === undefined) return;
- const copyImages = {};
- Object.keys(this.content.images).forEach((imgKey) => {
- copyImages[imgKey === key ? newKey : imgKey] = this.content.images[imgKey];
- });
- this.content.images = copyImages;
- this.applyImages(this.content);
- }
-
- _updateImageProperty(key, property, value) {
- if (this.content.images[key] === undefined) return;
- this.content.images[key][property] = value;
- this.applyImages(this.content);
- }
-
- setImageUrl(key, url) {
- this._updateImageProperty(key, 'url', url);
- }
-
- setImageBody(key, body) {
- this._updateImageProperty(key, 'body', body);
- }
-
- setImageInfo(key, info) {
- this._updateImageProperty(key, 'info', info);
- }
-
- setImageUsage(key, usage) {
- this._updateImageProperty(key, 'usage', usage);
- }
-}
-
-
+++ /dev/null
-import React, { useState, useEffect, useRef } from 'react';
-import PropTypes from 'prop-types';
-import './InviteUser.scss';
-
-import * as roomActions from '../../../client/action/room';
-import { hasDevices } from '../../../util/matrixUtil';
-
-import Text from '../../atoms/text/Text';
-import Button from '../../atoms/button/Button';
-import IconButton from '../../atoms/button/IconButton';
-import Spinner from '../../atoms/spinner/Spinner';
-import Input from '../../atoms/input/Input';
-import PopupWindow from '../../molecules/popup-window/PopupWindow';
-import RoomTile from '../../molecules/room-tile/RoomTile';
-
-import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
-import UserIC from '../../../../public/res/ic/outlined/user.svg';
-import { useRoomNavigate } from '../../hooks/useRoomNavigate';
-import { getDMRoomFor } from '../../utils/matrix';
-import { useMatrixClient } from '../../hooks/useMatrixClient';
-import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
-
-function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) {
- const [isSearching, updateIsSearching] = useState(false);
- const [searchQuery, updateSearchQuery] = useState({});
- const [users, updateUsers] = useState([]);
- const useAuthentication = useMediaAuthentication();
-
- const [procUsers, updateProcUsers] = useState(new Set()); // proc stands for processing.
- const [procUserError, updateUserProcError] = useState(new Map());
-
- const [createdDM, updateCreatedDM] = useState(new Map());
- const [roomIdToUserId, updateRoomIdToUserId] = useState(new Map());
-
- const [invitedUserIds, updateInvitedUserIds] = useState(new Set());
-
- const usernameRef = useRef(null);
-
- const mx = useMatrixClient();
- const { navigateRoom } = useRoomNavigate();
-
- function getMapCopy(myMap) {
- const newMap = new Map();
- myMap.forEach((data, key) => {
- newMap.set(key, data);
- });
- return newMap;
- }
- function addUserToProc(userId) {
- procUsers.add(userId);
- updateProcUsers(new Set(Array.from(procUsers)));
- }
- function deleteUserFromProc(userId) {
- procUsers.delete(userId);
- updateProcUsers(new Set(Array.from(procUsers)));
- }
-
- function onDMCreated(newRoomId) {
- const myDMPartnerId = roomIdToUserId.get(newRoomId);
- if (typeof myDMPartnerId === 'undefined') return;
-
- createdDM.set(myDMPartnerId, newRoomId);
- roomIdToUserId.delete(newRoomId);
-
- deleteUserFromProc(myDMPartnerId);
- updateCreatedDM(getMapCopy(createdDM));
- updateRoomIdToUserId(getMapCopy(roomIdToUserId));
- }
-
- async function searchUser(username) {
- const inputUsername = username.trim();
- if (isSearching || inputUsername === '' || inputUsername === searchQuery.username) return;
- const isInputUserId = inputUsername[0] === '@' && inputUsername.indexOf(':') > 1;
- updateIsSearching(true);
- updateSearchQuery({ username: inputUsername });
-
- if (isInputUserId) {
- try {
- const result = await mx.getProfileInfo(inputUsername);
- updateUsers([
- {
- user_id: inputUsername,
- display_name: result.displayname,
- avatar_url: result.avatar_url,
- },
- ]);
- } catch (e) {
- updateSearchQuery({ error: `${inputUsername} not found!` });
- }
- } else {
- try {
- const result = await mx.searchUserDirectory({
- term: inputUsername,
- limit: 20,
- });
- if (result.results.length === 0) {
- updateSearchQuery({ error: `No matches found for "${inputUsername}"!` });
- updateIsSearching(false);
- return;
- }
- updateUsers(result.results);
- } catch (e) {
- updateSearchQuery({ error: 'Something went wrong!' });
- }
- }
- updateIsSearching(false);
- }
-
- async function createDM(userId) {
- if (mx.getUserId() === userId) return;
- const dmRoomId = getDMRoomFor(mx, userId)?.roomId;
- if (dmRoomId) {
- navigateRoom(dmRoomId);
- onRequestClose();
- return;
- }
-
- try {
- addUserToProc(userId);
- procUserError.delete(userId);
- updateUserProcError(getMapCopy(procUserError));
-
- const result = await roomActions.createDM(mx, userId, await hasDevices(mx, userId));
- roomIdToUserId.set(result.room_id, userId);
- updateRoomIdToUserId(getMapCopy(roomIdToUserId));
- onDMCreated(result.room_id);
- } catch (e) {
- deleteUserFromProc(userId);
- if (typeof e.message === 'string') procUserError.set(userId, e.message);
- else procUserError.set(userId, 'Something went wrong!');
- updateUserProcError(getMapCopy(procUserError));
- }
- }
-
- async function inviteToRoom(userId) {
- if (typeof roomId === 'undefined') return;
- try {
- addUserToProc(userId);
- procUserError.delete(userId);
- updateUserProcError(getMapCopy(procUserError));
-
- await mx.invite(roomId, userId);
-
- invitedUserIds.add(userId);
- updateInvitedUserIds(new Set(Array.from(invitedUserIds)));
- deleteUserFromProc(userId);
- } catch (e) {
- deleteUserFromProc(userId);
- if (typeof e.message === 'string') procUserError.set(userId, e.message);
- else procUserError.set(userId, 'Something went wrong!');
- updateUserProcError(getMapCopy(procUserError));
- }
- }
-
- function renderUserList() {
- const renderOptions = (userId) => {
- const messageJSX = (message, isPositive) => (
- <Text variant="b2">
- <span style={{ color: isPositive ? 'var(--bg-positive)' : 'var(--bg-negative)' }}>
- {message}
- </span>
- </Text>
- );
-
- if (mx.getUserId() === userId) return null;
- if (procUsers.has(userId)) {
- return <Spinner size="small" />;
- }
- if (createdDM.has(userId)) {
- // eslint-disable-next-line max-len
- return (
- <Button
- onClick={() => {
- navigateRoom(createdDM.get(userId));
- onRequestClose();
- }}
- >
- Open
- </Button>
- );
- }
- if (invitedUserIds.has(userId)) {
- return messageJSX('Invited', true);
- }
- if (typeof roomId === 'string') {
- const member = mx.getRoom(roomId).getMember(userId);
- if (member !== null) {
- const userMembership = member.membership;
- switch (userMembership) {
- case 'join':
- return messageJSX('Already joined', true);
- case 'invite':
- return messageJSX('Already Invited', true);
- case 'ban':
- return messageJSX('Banned', false);
- default:
- }
- }
- }
- return typeof roomId === 'string' ? (
- <Button onClick={() => inviteToRoom(userId)} variant="primary">
- Invite
- </Button>
- ) : (
- <Button onClick={() => createDM(userId)} variant="primary">
- Message
- </Button>
- );
- };
- const renderError = (userId) => {
- if (!procUserError.has(userId)) return null;
- return (
- <Text variant="b2">
- <span style={{ color: 'var(--bg-danger)' }}>{procUserError.get(userId)}</span>
- </Text>
- );
- };
-
- return users.map((user) => {
- const userId = user.user_id;
- const name = typeof user.display_name === 'string' ? user.display_name : userId;
- return (
- <RoomTile
- key={userId}
- avatarSrc={
- typeof user.avatar_url === 'string'
- ? mx.mxcUrlToHttp(
- user.avatar_url,
- 42,
- 42,
- 'crop',
- undefined,
- undefined,
- useAuthentication
- )
- : null
- }
- name={name}
- id={userId}
- options={renderOptions(userId)}
- desc={renderError(userId)}
- />
- );
- });
- }
-
- useEffect(() => {
- if (isOpen && typeof searchTerm === 'string') searchUser(searchTerm);
- return () => {
- updateIsSearching(false);
- updateSearchQuery({});
- updateUsers([]);
- updateProcUsers(new Set());
- updateUserProcError(new Map());
- updateCreatedDM(new Map());
- updateRoomIdToUserId(new Map());
- updateInvitedUserIds(new Set());
- };
- }, [isOpen, searchTerm]);
-
- return (
- <PopupWindow
- isOpen={isOpen}
- title={typeof roomId === 'string' ? `Invite to ${mx.getRoom(roomId).name}` : 'Direct message'}
- contentOptions={<IconButton src={CrossIC} onClick={onRequestClose} tooltip="Close" />}
- onRequestClose={onRequestClose}
- >
- <div className="invite-user">
- <form
- className="invite-user__form"
- onSubmit={(e) => {
- e.preventDefault();
- searchUser(usernameRef.current.value);
- }}
- >
- <Input value={searchTerm} forwardRef={usernameRef} label="Name or userId" autoFocus />
- <Button disabled={isSearching} iconSrc={UserIC} variant="primary" type="submit">
- Search
- </Button>
- </form>
- <div className="invite-user__search-status">
- {typeof searchQuery.username !== 'undefined' && isSearching && (
- <div className="flex--center">
- <Spinner size="small" />
- <Text variant="b2">{`Searching for user "${searchQuery.username}"...`}</Text>
- </div>
- )}
- {typeof searchQuery.username !== 'undefined' && !isSearching && (
- <Text variant="b2">{`Search result for user "${searchQuery.username}"`}</Text>
- )}
- {searchQuery.error && (
- <Text className="invite-user__search-error" variant="b2">
- {searchQuery.error}
- </Text>
- )}
- </div>
- {users.length !== 0 && <div className="invite-user__content">{renderUserList()}</div>}
- </div>
- </PopupWindow>
- );
-}
-
-InviteUser.defaultProps = {
- roomId: undefined,
- searchTerm: undefined,
-};
-
-InviteUser.propTypes = {
- isOpen: PropTypes.bool.isRequired,
- roomId: PropTypes.string,
- searchTerm: PropTypes.string,
- onRequestClose: PropTypes.func.isRequired,
-};
-
-export default InviteUser;
+++ /dev/null
-@use '../../partials/dir';
-
-.invite-user {
- margin-top: var(--sp-extra-tight);
- @include dir.side(margin, var(--sp-normal), var(--sp-extra-tight));
-
- &__form {
- display: flex;
- align-items: flex-end;
-
- & .input-container {
- flex: 1;
- min-width: 0;
- @include dir.side(margin, 0, var(--sp-normal));
- }
-
- & .btn-primary {
- padding: {
- top: 11px;
- bottom: 11px;
- }
- }
- }
-
- &__search-status {
- margin-top: var(--sp-extra-loose);
- margin-bottom: var(--sp-tight);
- & .donut-spinner {
- margin: 0 var(--sp-tight);
- }
- }
- &__search-error {
- color: var(--bg-danger);
- }
- &__content {
- border-top: 1px solid var(--bg-surface-border);
- }
-
- & .room-tile {
- margin-top: var(--sp-normal);
- &__options {
- align-self: flex-end;
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-import React, { useState, useEffect } from 'react';
-import PropTypes from 'prop-types';
-import './JoinAlias.scss';
-
-import cons from '../../../client/state/cons';
-import navigation from '../../../client/state/navigation';
-import { join } from '../../../client/action/room';
-
-import Text from '../../atoms/text/Text';
-import IconButton from '../../atoms/button/IconButton';
-import Button from '../../atoms/button/Button';
-import Input from '../../atoms/input/Input';
-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';
-import { useRoomNavigate } from '../../hooks/useRoomNavigate';
-import { useMatrixClient } from '../../hooks/useMatrixClient';
-
-const ALIAS_OR_ID_REG = /^[#|!].+:.+\..+$/;
-
-function JoinAliasContent({ term, requestClose }) {
- const [process, setProcess] = useState(false);
- const [error, setError] = useState(undefined);
-
- const mx = useMatrixClient();
- const mountStore = useStore();
-
- const { navigateRoom } = useRoomNavigate();
-
- const openRoom = (roomId) => {
- navigateRoom(roomId);
- requestClose();
- };
-
- const handleSubmit = async (e) => {
- e.preventDefault();
- mountStore.setItem(true);
- const alias = e.target.alias.value;
- if (alias?.trim() === '') return;
- if (alias.match(ALIAS_OR_ID_REG) === null) {
- setError('Invalid address.');
- return;
- }
- setProcess('Looking for address...');
- setError(undefined);
- let via;
- if (alias.startsWith('#')) {
- try {
- const aliasData = await mx.getRoomIdForAlias(alias);
- via = aliasData?.servers.slice(0, 3) || [];
- if (mountStore.getItem()) {
- setProcess(`Joining ${alias}...`);
- }
- } catch (err) {
- if (!mountStore.getItem()) return;
- setProcess(false);
- setError(
- `Unable to find room/space with ${alias}. Either room/space is private or doesn't exist.`
- );
- }
- }
- try {
- const roomId = await join(mx, alias, false, via);
- if (!mountStore.getItem()) return;
- openRoom(roomId);
- } catch {
- if (!mountStore.getItem()) return;
- setProcess(false);
- setError(`Unable to join ${alias}. Either room/space is private or doesn't exist.`);
- }
- };
-
- return (
- <form className="join-alias" onSubmit={handleSubmit}>
- <Input label="Address" value={term} name="alias" required autoFocus />
- {error && (
- <Text className="join-alias__error" variant="b3">
- {error}
- </Text>
- )}
- <div className="join-alias__btn">
- {process ? (
- <>
- <Spinner size="small" />
- <Text>{process}</Text>
- </>
- ) : (
- <Button variant="primary" type="submit">
- Join
- </Button>
- )}
- </div>
- </form>
- );
-}
-JoinAliasContent.defaultProps = {
- term: undefined,
-};
-JoinAliasContent.propTypes = {
- term: PropTypes.string,
- requestClose: PropTypes.func.isRequired,
-};
-
-function useWindowToggle() {
- const [data, setData] = useState(null);
-
- useEffect(() => {
- const handleOpen = (term) => {
- setData({ term });
- };
- navigation.on(cons.events.navigation.JOIN_ALIAS_OPENED, handleOpen);
- return () => {
- navigation.removeListener(cons.events.navigation.JOIN_ALIAS_OPENED, handleOpen);
- };
- }, []);
-
- const onRequestClose = () => setData(null);
-
- return [data, onRequestClose];
-}
-
-function JoinAlias() {
- const [data, requestClose] = useWindowToggle();
-
- return (
- <Dialog
- isOpen={data !== null}
- title={
- <Text variant="s1" weight="medium" primary>
- Join with address
- </Text>
- }
- contentOptions={<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />}
- onRequestClose={requestClose}
- >
- {data ? <JoinAliasContent term={data.term} requestClose={requestClose} /> : <div />}
- </Dialog>
- );
-}
-
-export default JoinAlias;
+++ /dev/null
-@use '../../partials/dir';
-
-.join-alias {
- padding: var(--sp-normal);
- @include dir.side(padding, var(--sp-normal), var(--sp-extra-tight));
-
- & > *:not(:first-child) {
- margin-top: var(--sp-normal);
- }
-
- &__error {
- color: var(--tc-danger-high);
- margin-top: var(--sp-extra-tight) !important;
- }
-
- &__btn {
- display: flex;
- gap: var(--sp-normal);
- }
-}
\ No newline at end of file
+++ /dev/null
-import React, { useState, useEffect, useRef } from 'react';
-import PropTypes from 'prop-types';
-import './ProfileViewer.scss';
-import { EventTimeline } from 'matrix-js-sdk';
-
-import cons from '../../../client/state/cons';
-import navigation from '../../../client/state/navigation';
-import { openReusableContextMenu } from '../../../client/action/navigation';
-import * as roomActions from '../../../client/action/room';
-
-import {
- getUsername,
- getUsernameOfRoomMember,
- getPowerLabel,
- hasDevices,
-} from '../../../util/matrixUtil';
-import { getEventCords } from '../../../util/common';
-import colorMXID from '../../../util/colorMXID';
-
-import Text from '../../atoms/text/Text';
-import Chip from '../../atoms/chip/Chip';
-import IconButton from '../../atoms/button/IconButton';
-import Input from '../../atoms/input/Input';
-import Avatar from '../../atoms/avatar/Avatar';
-import Button from '../../atoms/button/Button';
-import { MenuItem } from '../../atoms/context-menu/ContextMenu';
-import PowerLevelSelector from '../../molecules/power-level-selector/PowerLevelSelector';
-import Dialog from '../../molecules/dialog/Dialog';
-
-import ShieldEmptyIC from '../../../../public/res/ic/outlined/shield-empty.svg';
-import ChevronRightIC from '../../../../public/res/ic/outlined/chevron-right.svg';
-import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
-import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
-
-import { useForceUpdate } from '../../hooks/useForceUpdate';
-import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
-import { useRoomNavigate } from '../../hooks/useRoomNavigate';
-import { getDMRoomFor } from '../../utils/matrix';
-import { useMatrixClient } from '../../hooks/useMatrixClient';
-import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
-
-function ModerationTools({ roomId, userId }) {
- const mx = useMatrixClient();
- const room = mx.getRoom(roomId);
- const roomMember = room.getMember(userId);
-
- const myPowerLevel = room.getMember(mx.getUserId())?.powerLevel || 0;
- const powerLevel = roomMember?.powerLevel || 0;
- const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
- const canIKick =
- roomMember?.membership === 'join' &&
- roomState?.hasSufficientPowerLevelFor('kick', myPowerLevel) &&
- powerLevel < myPowerLevel;
- const canIBan =
- ['join', 'leave'].includes(roomMember?.membership) &&
- roomState?.hasSufficientPowerLevelFor('ban', myPowerLevel) &&
- powerLevel < myPowerLevel;
-
- const handleKick = (e) => {
- e.preventDefault();
- const kickReason = e.target.elements['kick-reason']?.value.trim();
- mx.kick(roomId, userId, kickReason !== '' ? kickReason : undefined);
- };
-
- const handleBan = (e) => {
- e.preventDefault();
- const banReason = e.target.elements['ban-reason']?.value.trim();
- mx.ban(roomId, userId, banReason !== '' ? banReason : undefined);
- };
-
- return (
- <div className="moderation-tools">
- {canIKick && (
- <form onSubmit={handleKick}>
- <Input label="Kick reason" name="kick-reason" />
- <Button type="submit">Kick</Button>
- </form>
- )}
- {canIBan && (
- <form onSubmit={handleBan}>
- <Input label="Ban reason" name="ban-reason" />
- <Button type="submit">Ban</Button>
- </form>
- )}
- </div>
- );
-}
-ModerationTools.propTypes = {
- roomId: PropTypes.string.isRequired,
- userId: PropTypes.string.isRequired,
-};
-
-function SessionInfo({ userId }) {
- const [devices, setDevices] = useState(null);
- const [isVisible, setIsVisible] = useState(false);
- const mx = useMatrixClient();
-
- useEffect(() => {
- let isUnmounted = false;
-
- async function loadDevices() {
- try {
- const crypto = mx.getCrypto();
- const userToDevices = await crypto.getUserDeviceInfo([userId], true);
- const myDevices = Array.from(userToDevices.get(userId).values());
-
- if (isUnmounted) return;
- setDevices(myDevices);
- } catch {
- setDevices([]);
- }
- }
- loadDevices();
-
- return () => {
- isUnmounted = true;
- };
- }, [mx, userId]);
-
- function renderSessionChips() {
- if (!isVisible) return null;
- return (
- <div className="session-info__chips">
- {devices === null && <Text variant="b2">Loading sessions...</Text>}
- {devices?.length === 0 && <Text variant="b2">No session found.</Text>}
- {devices !== null &&
- devices.map((device) => (
- <Chip
- key={device.deviceId}
- iconSrc={ShieldEmptyIC}
- text={device.displayName || device.deviceId}
- />
- ))}
- </div>
- );
- }
-
- return (
- <div className="session-info">
- <MenuItem
- onClick={() => setIsVisible(!isVisible)}
- iconSrc={isVisible ? ChevronBottomIC : ChevronRightIC}
- >
- <Text variant="b2">{`View ${
- devices?.length > 0
- ? `${devices.length} ${devices.length === 1 ? 'session' : 'sessions'}`
- : 'sessions'
- }`}</Text>
- </MenuItem>
- {renderSessionChips()}
- </div>
- );
-}
-
-SessionInfo.propTypes = {
- userId: PropTypes.string.isRequired,
-};
-
-function ProfileFooter({ roomId, userId, onRequestClose }) {
- const [isCreatingDM, setIsCreatingDM] = useState(false);
- const [isIgnoring, setIsIgnoring] = useState(false);
- const mx = useMatrixClient();
- const [isUserIgnored, setIsUserIgnored] = useState(mx.isUserIgnored(userId));
-
- const isMountedRef = useRef(true);
- const { navigateRoom } = useRoomNavigate();
- const room = mx.getRoom(roomId);
- const member = room.getMember(userId);
- const isInvitable = member?.membership !== 'join' && member?.membership !== 'ban';
-
- const [isInviting, setIsInviting] = useState(false);
- const [isInvited, setIsInvited] = useState(member?.membership === 'invite');
-
- const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel || 0;
- const userPL = room.getMember(userId)?.powerLevel || 0;
- const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
-
- const canIKick =
- roomState?.hasSufficientPowerLevelFor('kick', myPowerlevel) && userPL < myPowerlevel;
-
- const isBanned = member?.membership === 'ban';
-
- const onCreated = (dmRoomId) => {
- if (isMountedRef.current === false) return;
- setIsCreatingDM(false);
- navigateRoom(dmRoomId);
- onRequestClose();
- };
-
- useEffect(() => {
- setIsUserIgnored(mx.isUserIgnored(userId));
- setIsIgnoring(false);
- setIsInviting(false);
- }, [mx, userId]);
-
- const openDM = async () => {
- // Check and open if user already have a DM with userId.
- const dmRoomId = getDMRoomFor(mx, userId)?.roomId;
- if (dmRoomId) {
- navigateRoom(dmRoomId);
- onRequestClose();
- return;
- }
-
- // Create new DM
- try {
- setIsCreatingDM(true);
- const result = await roomActions.createDM(mx, userId, await hasDevices(mx, userId));
- onCreated(result.room_id);
- } catch {
- if (isMountedRef.current === false) return;
- setIsCreatingDM(false);
- }
- };
-
- const toggleIgnore = async () => {
- const isIgnored = mx.getIgnoredUsers().includes(userId);
-
- try {
- setIsIgnoring(true);
- if (isIgnored) {
- await roomActions.unignore(mx, [userId]);
- } else {
- await roomActions.ignore(mx, [userId]);
- }
-
- if (isMountedRef.current === false) return;
- setIsUserIgnored(!isIgnored);
- setIsIgnoring(false);
- } catch {
- setIsIgnoring(false);
- }
- };
-
- const toggleInvite = async () => {
- try {
- setIsInviting(true);
- let isInviteSent = false;
- if (isInvited) await mx.kick(roomId, userId);
- else {
- await mx.invite(roomId, userId);
- isInviteSent = true;
- }
- if (isMountedRef.current === false) return;
- setIsInvited(isInviteSent);
- setIsInviting(false);
- } catch {
- setIsInviting(false);
- }
- };
-
- return (
- <div className="profile-viewer__buttons">
- <Button variant="primary" onClick={openDM} disabled={isCreatingDM}>
- {isCreatingDM ? 'Creating room...' : 'Message'}
- </Button>
- {isBanned && canIKick && (
- <Button variant="positive" onClick={() => mx.unban(roomId, userId)}>
- Unban
- </Button>
- )}
- {(isInvited ? canIKick : room.canInvite(mx.getUserId())) && isInvitable && (
- <Button onClick={toggleInvite} disabled={isInviting}>
- {isInvited
- ? `${isInviting ? 'Disinviting...' : 'Disinvite'}`
- : `${isInviting ? 'Inviting...' : 'Invite'}`}
- </Button>
- )}
- <Button
- variant={isUserIgnored ? 'positive' : 'danger'}
- onClick={toggleIgnore}
- disabled={isIgnoring}
- >
- {isUserIgnored
- ? `${isIgnoring ? 'Unignoring...' : 'Unignore'}`
- : `${isIgnoring ? 'Ignoring...' : 'Ignore'}`}
- </Button>
- </div>
- );
-}
-ProfileFooter.propTypes = {
- roomId: PropTypes.string.isRequired,
- userId: PropTypes.string.isRequired,
- onRequestClose: PropTypes.func.isRequired,
-};
-
-function useToggleDialog() {
- const [isOpen, setIsOpen] = useState(false);
- const [roomId, setRoomId] = useState(null);
- const [userId, setUserId] = useState(null);
-
- useEffect(() => {
- const loadProfile = (uId, rId) => {
- setIsOpen(true);
- setUserId(uId);
- setRoomId(rId);
- };
- navigation.on(cons.events.navigation.PROFILE_VIEWER_OPENED, loadProfile);
- return () => {
- navigation.removeListener(cons.events.navigation.PROFILE_VIEWER_OPENED, loadProfile);
- };
- }, []);
-
- const closeDialog = () => setIsOpen(false);
-
- const afterClose = () => {
- setUserId(null);
- setRoomId(null);
- };
-
- return [isOpen, roomId, userId, closeDialog, afterClose];
-}
-
-function useRerenderOnProfileChange(roomId, userId) {
- const mx = useMatrixClient();
- const [, forceUpdate] = useForceUpdate();
- useEffect(() => {
- const handleProfileChange = (mEvent, member) => {
- if (
- mEvent.getRoomId() === roomId &&
- (member.userId === userId || member.userId === mx.getUserId())
- ) {
- forceUpdate();
- }
- };
- mx.on('RoomMember.powerLevel', handleProfileChange);
- mx.on('RoomMember.membership', handleProfileChange);
- return () => {
- mx.removeListener('RoomMember.powerLevel', handleProfileChange);
- mx.removeListener('RoomMember.membership', handleProfileChange);
- };
- }, [mx, roomId, userId]);
-}
-
-function ProfileViewer() {
- const [isOpen, roomId, userId, closeDialog, handleAfterClose] = useToggleDialog();
- useRerenderOnProfileChange(roomId, userId);
- const useAuthentication = useMediaAuthentication();
-
- const mx = useMatrixClient();
- const room = mx.getRoom(roomId);
-
- const renderProfile = () => {
- const roomMember = room.getMember(userId);
- const username = roomMember ? getUsernameOfRoomMember(roomMember) : getUsername(mx, userId);
- const avatarMxc = roomMember?.getMxcAvatarUrl?.() || mx.getUser(userId)?.avatarUrl;
- const avatarUrl =
- avatarMxc && avatarMxc !== 'null'
- ? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop', undefined, undefined, useAuthentication)
- : null;
-
- const powerLevel = roomMember?.powerLevel || 0;
- const myPowerLevel = room.getMember(mx.getUserId())?.powerLevel || 0;
-
- const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
- const canChangeRole =
- roomState?.maySendEvent('m.room.power_levels', mx.getUserId()) &&
- (powerLevel < myPowerLevel || userId === mx.getUserId());
-
- 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?';
-
- const isSharedPower = newPowerLevel === myPowerLevel;
- const isDemotingMyself = userId === mx.getUserId();
- if (isSharedPower || isDemotingMyself) {
- const isConfirmed = await confirmDialog(
- 'Change power level',
- isSharedPower ? SHARED_POWER_MSG : DEMOTING_MYSELF_MSG,
- 'Change',
- 'caution'
- );
- if (!isConfirmed) return;
- roomActions.setPowerLevel(mx, roomId, userId, newPowerLevel);
- } else {
- roomActions.setPowerLevel(mx, roomId, userId, newPowerLevel);
- }
- };
-
- const handlePowerSelector = (e) => {
- openReusableContextMenu('bottom', getEventCords(e, '.btn-surface'), (closeMenu) => (
- <PowerLevelSelector
- value={powerLevel}
- max={myPowerLevel}
- onSelect={(pl) => {
- closeMenu();
- handleChangePowerLevel(pl);
- }}
- />
- ));
- };
-
- return (
- <div className="profile-viewer">
- <div className="profile-viewer__user">
- <Avatar imageSrc={avatarUrl} text={username} bgColor={colorMXID(userId)} size="large" />
- <div className="profile-viewer__user__info">
- <Text variant="s1" weight="medium">
- {username}
- </Text>
- <Text variant="b2">{userId}</Text>
- </div>
- <div className="profile-viewer__user__role">
- <Text variant="b3">Role</Text>
- <Button
- onClick={canChangeRole ? handlePowerSelector : null}
- iconSrc={canChangeRole ? ChevronBottomIC : null}
- >
- {`${getPowerLabel(powerLevel) || 'Member'} - ${powerLevel}`}
- </Button>
- </div>
- </div>
- <ModerationTools roomId={roomId} userId={userId} />
- <SessionInfo userId={userId} />
- {userId !== mx.getUserId() && (
- <ProfileFooter roomId={roomId} userId={userId} onRequestClose={closeDialog} />
- )}
- </div>
- );
- };
-
- return (
- <Dialog
- className="profile-viewer__dialog"
- isOpen={isOpen}
- title={room?.name ?? ''}
- onAfterClose={handleAfterClose}
- onRequestClose={closeDialog}
- contentOptions={<IconButton src={CrossIC} onClick={closeDialog} tooltip="Close" />}
- >
- {roomId ? renderProfile() : <div />}
- </Dialog>
- );
-}
-
-export default ProfileViewer;
+++ /dev/null
-@use '../../partials/flex';
-@use '../../partials/dir';
-
-.profile-viewer__dialog {
- & .dialog__content__wrapper {
- position: relative;
- }
- & .dialog__content-container {
- padding-top: var(--sp-normal);
- padding-bottom: 89px;
- @include dir.side(padding, var(--sp-normal), var(--sp-extra-tight));
- }
-}
-
-.profile-viewer {
- &__user {
- display: flex;
- padding-bottom: var(--sp-normal);
-
- &__info {
- align-self: flex-end;
- flex: 1;
- min-width: 0;
-
- margin: 0 var(--sp-normal);
-
- & .text {
- white-space: pre-wrap;
- word-break: break-word;
- }
- }
- &__role {
- align-self: flex-end;
- & > .text {
- margin-bottom: var(--sp-ultra-tight);
- }
- }
- }
-
- & .session-info {
- margin-top: var(--sp-normal);
- }
-
- &__buttons {
- position: absolute;
- left: 0;
- bottom: 0;
-
- width: 100%;
- padding: var(--sp-normal);
- background-color: var(--bg-surface);
- border-top: 1px solid var(--bg-surface-border);
- display: flex;
-
- & > *:nth-child(2n) {
- margin: 0 var(--sp-normal)
- }
- & > *:last-child {
- @include dir.side(margin, auto, 0);
- }
- }
-}
-
-.profile-viewer__admin-tool {
- .setting-tile {
- margin-top: var(--sp-loose);
- }
-}
-
-.moderation-tools {
- & > form {
- margin: var(--sp-normal) 0;
- display: flex;
- align-items: flex-end;
- & .input-container {
- @extend .cp-fx__item-one;
- @include dir.side(margin, 0, var(--sp-tight));
- }
- & button {
- height: 46px;
- }
- }
-}
-
-.session-info {
- box-shadow: var(--bs-surface-border);
- border-radius: var(--bo-radius);
- overflow: hidden;
-
- & .context-menu__item button {
- padding: var(--sp-extra-tight);
- & .ic-raw {
- @include dir.side(margin, 0, var(--sp-extra-tight));
- }
- }
-
- &__chips {
- border-top: 1px solid var(--bg-surface-border);
- padding: var(--sp-tight);
- padding-top: var(--sp-ultra-tight);
-
- & > .text {
- margin-top: var(--sp-extra-tight);
- }
- & .chip {
- margin-top: var(--sp-extra-tight);
- @include dir.side(margin, 0, var(--sp-extra-tight));
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-import React from 'react';
-
-import ProfileViewer from '../profile-viewer/ProfileViewer';
-import SpaceAddExisting from '../../molecules/space-add-existing/SpaceAddExisting';
-import CreateRoom from '../create-room/CreateRoom';
-import JoinAlias from '../join-alias/JoinAlias';
-
-import ReusableDialog from '../../molecules/dialog/ReusableDialog';
-
-function Dialogs() {
- return (
- <>
- <ProfileViewer />
- <CreateRoom />
- <JoinAlias />
- <SpaceAddExisting />
-
- <ReusableDialog />
- </>
- );
-}
-
-export default Dialogs;
+++ /dev/null
-import React, { useState, useEffect } from 'react';
-
-import cons from '../../../client/state/cons';
-import navigation from '../../../client/state/navigation';
-
-import InviteUser from '../invite-user/InviteUser';
-
-function Windows() {
- const [inviteUser, changeInviteUser] = useState({
- isOpen: false,
- roomId: undefined,
- term: undefined,
- });
-
- function openInviteUser(roomId, searchTerm) {
- changeInviteUser({
- isOpen: true,
- roomId,
- searchTerm,
- });
- }
-
- useEffect(() => {
- navigation.on(cons.events.navigation.INVITE_USER_OPENED, openInviteUser);
- return () => {
- navigation.removeListener(cons.events.navigation.INVITE_USER_OPENED, openInviteUser);
- };
- }, []);
-
- return (
- <InviteUser
- isOpen={inviteUser.isOpen}
- roomId={inviteUser.roomId}
- searchTerm={inviteUser.searchTerm}
- onRequestClose={() => changeInviteUser({ isOpen: false, roomId: undefined })}
- />
- );
-}
-
-export default Windows;
+++ /dev/null
-import React, { useState, useEffect, useRef, useCallback } from 'react';
-import { useAtomValue } from 'jotai';
-import './Search.scss';
-
-import cons from '../../../client/state/cons';
-import navigation from '../../../client/state/navigation';
-import AsyncSearch from '../../../util/AsyncSearch';
-import { joinRuleToIconSrc } from '../../../util/matrixUtil';
-
-import Text from '../../atoms/text/Text';
-import RawIcon from '../../atoms/system-icons/RawIcon';
-import IconButton from '../../atoms/button/IconButton';
-import Input from '../../atoms/input/Input';
-import RawModal from '../../atoms/modal/RawModal';
-import ScrollView from '../../atoms/scroll/ScrollView';
-import RoomSelector from '../../molecules/room-selector/RoomSelector';
-
-import SearchIC from '../../../../public/res/ic/outlined/search.svg';
-import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
-import { useRoomNavigate } from '../../hooks/useRoomNavigate';
-import { useDirects, useRooms, useSpaces } from '../../state/hooks/roomList';
-import { roomToUnreadAtom } from '../../state/room/roomToUnread';
-import { roomToParentsAtom } from '../../state/room/roomToParents';
-import { allRoomsAtom } from '../../state/room-list/roomList';
-import { mDirectAtom } from '../../state/mDirectList';
-import { useKeyDown } from '../../hooks/useKeyDown';
-import { openSearch } from '../../../client/action/navigation';
-import { useMatrixClient } from '../../hooks/useMatrixClient';
-import { factoryRoomIdByActivity } from '../../utils/sort';
-
-function useVisiblityToggle(setResult) {
- const [isOpen, setIsOpen] = useState(false);
-
- useEffect(() => {
- const handleSearchOpen = (term) => {
- setResult({
- term,
- chunk: [],
- });
- setIsOpen(true);
- };
- navigation.on(cons.events.navigation.SEARCH_OPENED, handleSearchOpen);
- return () => {
- navigation.removeListener(cons.events.navigation.SEARCH_OPENED, handleSearchOpen);
- };
- }, []);
-
- useEffect(() => {
- if (isOpen === false) {
- setResult(undefined);
- }
- }, [isOpen]);
-
- useKeyDown(
- window,
- useCallback((event) => {
- // Ctrl/Cmd +
- if (event.ctrlKey || event.metaKey) {
- // open search modal
- if (event.key === 'k') {
- event.preventDefault();
- // means some menu or modal window is open
- if (
- document.body.lastChild.className !== 'ReactModalPortal' ||
- navigation.isRawModalVisible
- ) {
- return;
- }
- openSearch();
- }
- }
- }, [])
- );
-
- const requestClose = () => setIsOpen(false);
-
- return [isOpen, requestClose];
-}
-
-function mapRoomIds(mx, roomIds, directs, roomIdToParents) {
- return roomIds.map((roomId) => {
- const room = mx.getRoom(roomId);
- const parentSet = roomIdToParents.get(roomId);
- const parentNames = parentSet ? [] : undefined;
- parentSet?.forEach((parentId) => parentNames.push(mx.getRoom(parentId).name));
-
- const parents = parentNames ? parentNames.join(', ') : null;
-
- let type = 'room';
- if (room.isSpaceRoom()) type = 'space';
- else if (directs.includes(roomId)) type = 'direct';
-
- return {
- type,
- name: room.name,
- parents,
- roomId,
- room,
- };
- });
-}
-
-function Search() {
- const [result, setResult] = useState(null);
- const [asyncSearch] = useState(new AsyncSearch());
- const [isOpen, requestClose] = useVisiblityToggle(setResult);
- const searchRef = useRef(null);
- const mx = useMatrixClient();
- const { navigateRoom, navigateSpace } = useRoomNavigate();
- const mDirects = useAtomValue(mDirectAtom);
- const spaces = useSpaces(mx, allRoomsAtom);
- const rooms = useRooms(mx, allRoomsAtom, mDirects);
- const directs = useDirects(mx, allRoomsAtom, mDirects);
- const roomToUnread = useAtomValue(roomToUnreadAtom);
- const roomToParents = useAtomValue(roomToParentsAtom);
-
- const handleSearchResults = (chunk, term) => {
- setResult({
- term,
- chunk,
- });
- };
-
- const generateResults = (term) => {
- const prefix = term.match(/^[#@*]/)?.[0];
-
- if (term.length > 1) {
- asyncSearch.search(prefix ? term.slice(1) : term);
- return;
- }
-
- let ids = null;
-
- if (prefix) {
- if (prefix === '#') ids = [...rooms];
- else if (prefix === '@') ids = [...directs];
- else ids = [...spaces];
- } else {
- ids = [...rooms].concat([...directs], [...spaces]);
- }
-
- ids.sort(factoryRoomIdByActivity(mx));
- const mappedIds = mapRoomIds(mx, ids, directs, roomToParents);
- asyncSearch.setup(mappedIds, { keys: 'name', isContain: true, limit: 20 });
- if (prefix) handleSearchResults(mappedIds, prefix);
- else asyncSearch.search(term);
- };
-
- const loadRecentRooms = () => {
- const recentRooms = [];
- handleSearchResults(mapRoomIds(mx, recentRooms, directs, roomToParents).reverse());
- };
-
- const handleAfterOpen = () => {
- searchRef.current.focus();
- loadRecentRooms();
- asyncSearch.on(asyncSearch.RESULT_SENT, handleSearchResults);
-
- if (typeof result.term === 'string') {
- generateResults(result.term);
- searchRef.current.value = result.term;
- }
- };
-
- const handleAfterClose = () => {
- asyncSearch.removeListener(asyncSearch.RESULT_SENT, handleSearchResults);
- };
-
- const handleOnChange = () => {
- const { value } = searchRef.current;
- if (value.length === 0) {
- loadRecentRooms();
- return;
- }
- generateResults(value);
- };
-
- const handleCross = (e) => {
- e.preventDefault();
- const { value } = searchRef.current;
- if (value.length === 0) requestClose();
- else {
- searchRef.current.value = '';
- searchRef.current.focus();
- loadRecentRooms();
- }
- };
-
- const openItem = (roomId, type) => {
- if (type === 'space') navigateSpace(roomId);
- else navigateRoom(roomId);
- requestClose();
- };
-
- const openFirstResult = () => {
- const { chunk } = result;
- if (chunk?.length > 0) {
- const item = chunk[0];
- openItem(item.roomId, item.type);
- }
- };
-
- const renderRoomSelector = (item) => {
- let imageSrc = null;
- let iconSrc = null;
- if (item.type === 'direct') {
- imageSrc =
- item.room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
- } else {
- iconSrc = joinRuleToIconSrc(item.room.getJoinRule(), item.type === 'space');
- }
-
- return (
- <RoomSelector
- key={item.roomId}
- name={item.name}
- parentName={item.parents}
- roomId={item.roomId}
- imageSrc={imageSrc}
- iconSrc={iconSrc}
- isUnread={roomToUnread.has(item.roomId)}
- notificationCount={roomToUnread.get(item.roomId)?.total ?? 0}
- isAlert={roomToUnread.get(item.roomId)?.highlight > 0}
- onClick={() => openItem(item.roomId, item.type)}
- />
- );
- };
-
- return (
- <RawModal
- className="search-dialog__modal dialog-modal"
- isOpen={isOpen}
- onAfterOpen={handleAfterOpen}
- onAfterClose={handleAfterClose}
- onRequestClose={requestClose}
- size="small"
- >
- <div className="search-dialog">
- <form
- className="search-dialog__input"
- onSubmit={(e) => {
- e.preventDefault();
- openFirstResult();
- }}
- >
- <RawIcon src={SearchIC} size="small" />
- <Input onChange={handleOnChange} forwardRef={searchRef} placeholder="Search" />
- <IconButton size="small" src={CrossIC} type="reset" onClick={handleCross} tabIndex={-1} />
- </form>
- <div className="search-dialog__content-wrapper">
- <ScrollView autoHide>
- <div className="search-dialog__content">
- {Array.isArray(result?.chunk) && result.chunk.map(renderRoomSelector)}
- </div>
- </ScrollView>
- </div>
- <div className="search-dialog__footer">
- <Text variant="b3">Type # for rooms, @ for DMs and * for spaces. Hotkey: Ctrl + k</Text>
- </div>
- </div>
- </RawModal>
- );
-}
-
-export default Search;
+++ /dev/null
-@use '../../partials/dir';
-
-.search-dialog__modal {
- --modal-height: 380px;
- height: 100%;
- background-color: var(--bg-surface);
-}
-
-.search-dialog {
- display: flex;
- flex-direction: column;
- height: 100%;
- width: 100%;
-
- &__input {
- padding: var(--sp-normal);
- display: flex;
- align-items: center;
- position: relative;
-
- & > .ic-raw {
- position: absolute;
- --away: calc(var(--sp-normal) + var(--sp-tight));
- @include dir.prop(left, var(--away), unset);
- @include dir.prop(right, unset, var(--away));
- }
- & > .ic-btn {
- border-radius: calc(var(--bo-radius) / 2);
- position: absolute;
- --away: calc(var(--sp-normal) + var(--sp-extra-tight));
- @include dir.prop(right, var(--away), unset);
- @include dir.prop(left, unset, var(--away));
- }
- & .input-container {
- min-width: 0;
- flex: 1;
- }
-
- & input {
- padding-left: 40px;
- padding-right: 40px;
- font-size: var(--fs-s1);
- letter-spacing: var(--ls-s1);
- line-height: var(--lh-s1);
- color: var(--tc-surface-high);
- }
- }
- &__content-wrapper {
- min-height: 0;
- flex: 1;
- position: relative;
- &::before,
- &::after {
- position: absolute;
- top: 0;
- z-index: 99;
- content: "";
- display: inline-block;
- width: 100%;
- height: 8px;
- background-image: linear-gradient(to bottom, var(--bg-surface), var(--bg-surface-transparent));
- }
- &::after {
- top: unset;
- bottom: 0;
- background-image: linear-gradient(to bottom, var(--bg-surface-transparent), var(--bg-surface));
- }
- }
-
- &__content {
- padding: var(--sp-extra-tight);
- @include dir.side(padding, var(--sp-normal), var(--sp-extra-tight));
- }
-
- &__footer {
- padding: var(--sp-tight) var(--sp-normal);
- text-align: center;
- }
-
-}
\ No newline at end of file
-import React from 'react';
+import React, { useEffect } from 'react';
import { Provider as JotaiProvider } from 'jotai';
import { RouterProvider } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
+const useLastNodeToDetectReactPortalEntry = () => {
+ useEffect(() => {
+ const lastDiv = document.createElement('div');
+ lastDiv.setAttribute('data-last-node', 'true');
+ document.body.appendChild(lastDiv);
+ }, []);
+};
+
function App() {
const screenSize = useScreenSize();
+ useLastNodeToDetectReactPortalEntry();
+
return (
<ScreenSizeProvider value={screenSize}>
<FeatureCheck>
_SERVER_PATH,
CREATE_PATH,
} from './paths';
-import { isAuthenticated } from '../../client/state/auth';
import {
getAppPathFromHref,
getExploreFeaturedPath,
import { Create } from './client/create';
import { CreateSpaceModalRenderer } from '../features/create-space';
import { SearchModalRenderer } from '../features/search';
+import { getFallbackSession } from '../state/sessions';
export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => {
const { hashRouter } = clientConfig;
<Route
index
loader={() => {
- if (isAuthenticated()) return redirect(getHomePath());
+ if (getFallbackSession()) return redirect(getHomePath());
const afterLoginPath = getAppPathFromHref(getOriginBaseUrl(), window.location.href);
if (afterLoginPath) setAfterLoginRedirectPath(afterLoginPath);
return redirect(getLoginPath());
/>
<Route
loader={() => {
- if (isAuthenticated()) {
+ if (getFallbackSession()) {
return redirect(getHomePath());
}
<Route
loader={() => {
- if (!isAuthenticated()) {
+ if (!getFallbackSession()) {
const afterLoginPath = getAppPathFromHref(
getOriginBaseUrl(hashRouter),
window.location.href
import { useNavigate } from 'react-router-dom';
import { ClientConfig, clientAllowedServer } from '../../../hooks/useClientConfig';
import { autoDiscovery, specVersions } from '../../../cs-api';
-import { updateLocalStore } from '../../../../client/action/auth';
import { ErrorCode } from '../../../cs-errorcode';
import {
deleteAfterLoginRedirectPath,
getAfterLoginRedirectPath,
} from '../../afterLoginRedirectPath';
import { getHomePath } from '../../pathUtils';
+import { setFallbackSession } from '../../../state/sessions';
export enum GetBaseUrlError {
NotAllow = 'NotAllow',
useEffect(() => {
if (data) {
const { response: loginRes, baseUrl: loginBaseUrl } = data;
- updateLocalStore(loginRes.access_token, loginRes.device_id, loginRes.user_id, loginBaseUrl);
+ setFallbackSession(loginRes.access_token, loginRes.device_id, loginRes.user_id, loginBaseUrl);
const afterLoginRedirectUrl = getAfterLoginRedirectPath();
deleteAfterLoginRedirectPath();
navigate(afterLoginRedirectUrl ?? getHomePath(), { replace: true });
} from 'matrix-js-sdk';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
-import { updateLocalStore } from '../../../../client/action/auth';
import { LoginPathSearchParams } from '../../paths';
import { ErrorCode } from '../../../cs-errorcode';
import {
} from '../../afterLoginRedirectPath';
import { getHomePath, getLoginPath, withSearchParam } from '../../pathUtils';
import { getMxIdLocalPart, getMxIdServer } from '../../../utils/matrix';
+import { setFallbackSession } from '../../../state/sessions';
export enum RegisterError {
UserTaken = 'UserTaken',
const deviceId = response.device_id;
if (accessToken && deviceId) {
- updateLocalStore(accessToken, deviceId, userId, baseUrl);
+ setFallbackSession(accessToken, deviceId, userId, baseUrl);
const afterLoginRedirectPath = getAfterLoginRedirectPath();
deleteAfterLoginRedirectPath();
navigate(afterLoginRedirectPath ?? getHomePath(), { replace: true });
logoutClient,
startClient,
} from '../../../client/initMatrix';
-import { getSecret } from '../../../client/state/auth';
import { SplashScreen } from '../../components/splash-screen';
import { ServerConfigsLoader } from '../../components/ServerConfigsLoader';
import { CapabilitiesProvider } from '../../hooks/useCapabilities';
import { MediaConfigProvider } from '../../hooks/useMediaConfig';
import { MatrixClientProvider } from '../../hooks/useMatrixClient';
import { SpecVersions } from './SpecVersions';
-import Windows from '../../organisms/pw/Windows';
-import Dialogs from '../../organisms/pw/Dialogs';
-import ReusableContextMenu from '../../atoms/context-menu/ReusableContextMenu';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { useSyncState } from '../../hooks/useSyncState';
import { stopPropagation } from '../../utils/keyboard';
import { SyncStatus } from './SyncStatus';
import { AuthMetadataProvider } from '../../hooks/useAuthMetadata';
+import { getFallbackSession } from '../../state/sessions';
function ClientRootLoading() {
return (
};
export function ClientRoot({ children }: ClientRootProps) {
const [loading, setLoading] = useState(true);
- const { baseUrl } = getSecret();
+ const { baseUrl } = getFallbackSession() ?? {};
const [loadState, loadMatrix] = useAsyncCallback<MatrixClient, Error, []>(
- useCallback(() => initClient(getSecret() as any), [])
+ useCallback(() => {
+ const session = getFallbackSession();
+ if (!session) {
+ throw new Error('No session Found!');
+ }
+ return initClient(session);
+ }, [])
);
const mx = loadState.status === AsyncStatus.Success ? loadState.data : undefined;
const [startState, startMatrix] = useAsyncCallback<void, Error, [MatrixClient]>(
<MediaConfigProvider value={serverConfigs.mediaConfig ?? {}}>
<AuthMetadataProvider value={serverConfigs.authMetadata}>
{children}
- <Windows />
- <Dialogs />
- <ReusableContextMenu />
</AuthMetadataProvider>
</MediaConfigProvider>
</CapabilitiesProvider>
import { PageNav, PageNavContent, PageNavHeader } from '../../../components/page';
import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories';
import { useRoomsUnread } from '../../../state/hooks/unread';
-import { markAsRead } from '../../../../client/action/notifications';
+import { markAsRead } from '../../../utils/notifications';
import { stopPropagation } from '../../../utils/keyboard';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
import { useNavToActivePathMapper } from '../../../hooks/useNavToActivePathMapper';
import { PageNav, PageNavHeader, PageNavContent } from '../../../components/page';
import { useRoomsUnread } from '../../../state/hooks/unread';
-import { markAsRead } from '../../../../client/action/notifications';
+import { markAsRead } from '../../../utils/notifications';
import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories';
import { stopPropagation } from '../../../utils/keyboard';
import { useSetting } from '../../../state/hooks/settings';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
import { useRoomUnread } from '../../../state/hooks/unread';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
-import { markAsRead } from '../../../../client/action/notifications';
+import { markAsRead } from '../../../utils/notifications';
import { ContainerColor } from '../../../styles/ContainerColor.css';
import { VirtualTile } from '../../../components/virtualizer';
import { UserAvatar } from '../../../components/user-avatar';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { useNavToActivePathAtom } from '../../../state/hooks/navToActivePath';
import { useDirectRooms } from '../direct/useDirectRooms';
-import { markAsRead } from '../../../../client/action/notifications';
+import { markAsRead } from '../../../utils/notifications';
import { stopPropagation } from '../../../utils/keyboard';
import { settingsAtom } from '../../../state/settings';
import { useSetting } from '../../../state/hooks/settings';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { useNavToActivePathAtom } from '../../../state/hooks/navToActivePath';
import { useHomeRooms } from '../home/useHomeRooms';
-import { markAsRead } from '../../../../client/action/notifications';
+import { markAsRead } from '../../../utils/notifications';
import { stopPropagation } from '../../../utils/keyboard';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
import { usePowerLevels } from '../../../hooks/usePowerLevels';
import { useRoomsUnread } from '../../../state/hooks/unread';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
-import { markAsRead } from '../../../../client/action/notifications';
+import { markAsRead } from '../../../utils/notifications';
import { copyToClipboard } from '../../../utils/dom';
import { stopPropagation } from '../../../utils/keyboard';
import { getMatrixToRoom } from '../../../plugins/matrix-to';
import { usePowerLevels } from '../../../hooks/usePowerLevels';
import { useRecursiveChildScopeFactory, useSpaceChildren } from '../../../state/hooks/roomList';
import { roomToParentsAtom } from '../../../state/room/roomToParents';
-import { markAsRead } from '../../../../client/action/notifications';
+import { markAsRead } from '../../../utils/notifications';
import { useRoomsUnread } from '../../../state/hooks/unread';
import { UseStateProvider } from '../../../components/UseStateProvider';
import { LeaveSpacePrompt } from '../../../components/leave-space-prompt';
+++ /dev/null
-/*
-* NOTICE: only use this
-* when sides are un-even
-* if they are even just use $property: 0 $spacing;
-*/
-
-@mixin side($property, $left, $right) {
- #{$property}: {
- left: $left;
- right: $right;
- };
-
- [dir=rtl] & {
- #{$property}: {
- left: $right;
- right: $left;
- };
- }
-}
-
-@mixin prop($property, $ltr, $rtl) {
- #{$property}: $ltr;
- [dir=rtl] & {
- #{$property}: $rtl;
- }
-}
\ No newline at end of file
+++ /dev/null
-._s-c {
- justify-content: flex-start;
- align-items: center;
-}
-._c-c {
- justify-content: center;
- align-items: center;
-}
-._e-c {
- justify-content: flex-end;
- align-items: center;
-}
-
-.cp-fx__row {
- display: flex;
- flex-direction: row;
-}
-
-.cp-fx__column {
- display: flex;
- flex-direction: column;
-}
-
-.cp-fx__row--s-c {
- @extend .cp-fx__row;
- @extend ._s-c;
-}
-
-.cp-fx__row--c-c {
- @extend .cp-fx__row;
- @extend ._c-c;
-}
-
-.cp-fx__row--e-c {
- @extend .cp-fx__row;
- @extend ._e-c;
-}
-
-.cp-fx__column--s-c {
- @extend .cp-fx__column;
- @extend ._s-c;
-}
-
-.cp-fx__column--c-c {
- @extend .cp-fx__column;
- @extend ._c-c;
-}
-
-.cp-fx__column--e-c {
- @extend .cp-fx__column;
- @extend ._e-c;
-}
-
-.cp-fx__item-one {
- flex: 1;
- min-width: 0;
- min-height: 0;
-}
+++ /dev/null
-
-$breakpoint-tablet: 1124px;
-$breakpoint-mobile: 750px;
-
-@mixin smallerThan($deviceBreakpoint) {
- @if $deviceBreakpoint==mobileBreakpoint {
- @media screen and (max-width: $breakpoint-mobile) {
- @content;
- }
- }
- @else if $deviceBreakpoint==tabletBreakpoint {
- @media screen and (max-width: $breakpoint-tablet) {
- @content;
- }
- }
-}
-
-@mixin biggerThan($deviceBreakpoint) {
- @if $deviceBreakpoint==mobileBreakpoint {
- @media screen and (min-width: $breakpoint-mobile) {
- @content;
- }
- } @else if $deviceBreakpoint==tabletBreakpoint {
- @media screen and (min-width: $breakpoint-tablet) {
- @content;
- }
- }
-}
+++ /dev/null
-.cp-txt__ellipsis {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
\ No newline at end of file
-import { atom } from 'jotai';
-import {
- atomWithLocalStorage,
- getLocalStorageItem,
- setLocalStorageItem,
-} from './utils/atomWithLocalStorage';
+// import { atom } from 'jotai';
+// import {
+// atomWithLocalStorage,
+// getLocalStorageItem,
+// setLocalStorageItem,
+// } from './utils/atomWithLocalStorage';
export type Session = {
baseUrl: string;
/**
* Migration code for old session
*/
-const FALLBACK_STORE_NAME: SessionStoreName = {
- sync: 'web-sync-store',
- crypto: 'crypto-store',
-} as const;
+// const FALLBACK_STORE_NAME: SessionStoreName = {
+// sync: 'web-sync-store',
+// crypto: 'crypto-store',
+// } as const;
-const removeFallbackSession = () => {
+export function setFallbackSession(
+ accessToken: string,
+ deviceId: string,
+ userId: string,
+ baseUrl: string
+) {
+ localStorage.setItem('cinny_access_token', accessToken);
+ localStorage.setItem('cinny_device_id', deviceId);
+ localStorage.setItem('cinny_user_id', userId);
+ localStorage.setItem('cinny_hs_base_url', baseUrl);
+}
+export const removeFallbackSession = () => {
localStorage.removeItem('cinny_hs_base_url');
localStorage.removeItem('cinny_user_id');
localStorage.removeItem('cinny_device_id');
localStorage.removeItem('cinny_access_token');
};
-const getFallbackSession = (): Session | undefined => {
+export const getFallbackSession = (): Session | undefined => {
const baseUrl = localStorage.getItem('cinny_hs_base_url');
const userId = localStorage.getItem('cinny_user_id');
const deviceId = localStorage.getItem('cinny_device_id');
* End of migration code for old session
*/
-export const getSessionStoreName = (session: Session): SessionStoreName => {
- if (session.fallbackSdkStores) {
- return FALLBACK_STORE_NAME;
- }
+// export const getSessionStoreName = (session: Session): SessionStoreName => {
+// if (session.fallbackSdkStores) {
+// return FALLBACK_STORE_NAME;
+// }
- return {
- sync: `sync${session.userId}`,
- crypto: `crypto${session.userId}`,
- };
-};
+// return {
+// sync: `sync${session.userId}`,
+// crypto: `crypto${session.userId}`,
+// };
+// };
-export const MATRIX_SESSIONS_KEY = 'matrixSessions';
-const baseSessionsAtom = atomWithLocalStorage<Sessions>(
- MATRIX_SESSIONS_KEY,
- (key) => {
- const defaultSessions: Sessions = [];
- const sessions = getLocalStorageItem(key, defaultSessions);
+// export const MATRIX_SESSIONS_KEY = 'matrixSessions';
+// const baseSessionsAtom = atomWithLocalStorage<Sessions>(
+// MATRIX_SESSIONS_KEY,
+// (key) => {
+// const defaultSessions: Sessions = [];
+// const sessions = getLocalStorageItem(key, defaultSessions);
- // Before multi account support session was stored
- // as multiple item in local storage.
- // So we need these migration code.
- const fallbackSession = getFallbackSession();
- if (fallbackSession) {
- removeFallbackSession();
- sessions.push(fallbackSession);
- setLocalStorageItem(key, sessions);
- }
- return sessions;
- },
- (key, value) => {
- setLocalStorageItem(key, value);
- }
-);
+// // Before multi account support session was stored
+// // as multiple item in local storage.
+// // So we need these migration code.
+// const fallbackSession = getFallbackSession();
+// if (fallbackSession) {
+// removeFallbackSession();
+// sessions.push(fallbackSession);
+// setLocalStorageItem(key, sessions);
+// }
+// return sessions;
+// },
+// (key, value) => {
+// setLocalStorageItem(key, value);
+// }
+// );
-export type SessionsAction =
- | {
- type: 'PUT';
- session: Session;
- }
- | {
- type: 'DELETE';
- session: Session;
- };
+// export type SessionsAction =
+// | {
+// type: 'PUT';
+// session: Session;
+// }
+// | {
+// type: 'DELETE';
+// session: Session;
+// };
-export const sessionsAtom = atom<Sessions, [SessionsAction], undefined>(
- (get) => get(baseSessionsAtom),
- (get, set, action) => {
- if (action.type === 'PUT') {
- const sessions = [...get(baseSessionsAtom)];
- const sessionIndex = sessions.findIndex(
- (session) => session.userId === action.session.userId
- );
- if (sessionIndex === -1) {
- sessions.push(action.session);
- } else {
- sessions.splice(sessionIndex, 1, action.session);
- }
- set(baseSessionsAtom, sessions);
- return;
- }
- if (action.type === 'DELETE') {
- const sessions = get(baseSessionsAtom).filter(
- (session) => session.userId !== action.session.userId
- );
- set(baseSessionsAtom, sessions);
- }
- }
-);
+// export const sessionsAtom = atom<Sessions, [SessionsAction], undefined>(
+// (get) => get(baseSessionsAtom),
+// (get, set, action) => {
+// if (action.type === 'PUT') {
+// const sessions = [...get(baseSessionsAtom)];
+// const sessionIndex = sessions.findIndex(
+// (session) => session.userId === action.session.userId
+// );
+// if (sessionIndex === -1) {
+// sessions.push(action.session);
+// } else {
+// sessions.splice(sessionIndex, 1, action.session);
+// }
+// set(baseSessionsAtom, sessions);
+// return;
+// }
+// if (action.type === 'DELETE') {
+// const sessions = get(baseSessionsAtom).filter(
+// (session) => session.userId !== action.session.userId
+// );
+// set(baseSessionsAtom, sessions);
+// }
+// }
+// );
--- /dev/null
+import { MatrixClient, ReceiptType } from 'matrix-js-sdk';
+
+export async function markAsRead(mx: MatrixClient, roomId: string, privateReceipt: boolean) {
+ const room = mx.getRoom(roomId);
+ if (!room) return;
+
+ const timeline = room.getLiveTimeline().getEvents();
+ const readEventId = room.getEventReadUpTo(mx.getUserId()!);
+
+ const getLatestValidEvent = () => {
+ for (let i = timeline.length - 1; i >= 0; i -= 1) {
+ const latestEvent = timeline[i];
+ if (latestEvent.getId() === readEventId) return null;
+ if (!latestEvent.isSending()) return latestEvent;
+ }
+ return null;
+ };
+ if (timeline.length === 0) return;
+ const latestEvent = getLatestValidEvent();
+ if (latestEvent === null) return;
+
+ await mx.sendReadReceipt(
+ latestEvent,
+ privateReceipt ? ReceiptType.ReadPrivate : ReceiptType.Read
+ );
+}
+++ /dev/null
-import cons from '../state/cons';
-
-export function updateLocalStore(
- accessToken: string,
- deviceId: string,
- userId: string,
- baseUrl: string
-) {
- localStorage.setItem(cons.secretKey.ACCESS_TOKEN, accessToken);
- localStorage.setItem(cons.secretKey.DEVICE_ID, deviceId);
- localStorage.setItem(cons.secretKey.USER_ID, userId);
- localStorage.setItem(cons.secretKey.BASE_URL, baseUrl);
-}
+++ /dev/null
-import appDispatcher from '../dispatcher';
-import cons from '../state/cons';
-
-
-export function openSpaceAddExisting(roomId, spaces = false) {
- appDispatcher.dispatch({
- type: cons.actions.navigation.OPEN_SPACE_ADDEXISTING,
- roomId,
- spaces,
- });
-}
-
-
-export function openCreateRoom(isSpace = false, parentId = null) {
- appDispatcher.dispatch({
- type: cons.actions.navigation.OPEN_CREATE_ROOM,
- isSpace,
- parentId,
- });
-}
-
-export function openJoinAlias(term) {
- appDispatcher.dispatch({
- type: cons.actions.navigation.OPEN_JOIN_ALIAS,
- term,
- });
-}
-
-export function openInviteUser(roomId, searchTerm) {
- appDispatcher.dispatch({
- type: cons.actions.navigation.OPEN_INVITE_USER,
- roomId,
- searchTerm,
- });
-}
-
-export function openProfileViewer(userId, roomId) {
- appDispatcher.dispatch({
- type: cons.actions.navigation.OPEN_PROFILE_VIEWER,
- userId,
- roomId,
- });
-}
-
-export function openSearch(term) {
- appDispatcher.dispatch({
- type: cons.actions.navigation.OPEN_SEARCH,
- term,
- });
-}
-
-export function openReusableContextMenu(placement, cords, render, afterClose) {
- appDispatcher.dispatch({
- type: cons.actions.navigation.OPEN_REUSABLE_CONTEXT_MENU,
- placement,
- cords,
- render,
- afterClose,
- });
-}
-
-export function openReusableDialog(title, render, afterClose) {
- appDispatcher.dispatch({
- type: cons.actions.navigation.OPEN_REUSABLE_DIALOG,
- title,
- render,
- afterClose,
- });
-}
-
+++ /dev/null
-import { MatrixClient, ReceiptType } from 'matrix-js-sdk';
-
-export async function markAsRead(mx: MatrixClient, roomId: string, privateReceipt: boolean) {
- const room = mx.getRoom(roomId);
- if (!room) return;
-
- const timeline = room.getLiveTimeline().getEvents();
- const readEventId = room.getEventReadUpTo(mx.getUserId()!);
-
- const getLatestValidEvent = () => {
- for (let i = timeline.length - 1; i >= 0; i -= 1) {
- const latestEvent = timeline[i];
- if (latestEvent.getId() === readEventId) return null;
- if (!latestEvent.isSending()) return latestEvent;
- }
- return null;
- };
- if (timeline.length === 0) return;
- const latestEvent = getLatestValidEvent();
- if (latestEvent === null) return;
-
- await mx.sendReadReceipt(
- latestEvent,
- privateReceipt ? ReceiptType.ReadPrivate : ReceiptType.Read
- );
-}
+++ /dev/null
-import { EventTimeline } from 'matrix-js-sdk';
-import { getIdServer } from '../../util/matrixUtil';
-
-/**
- * https://github.com/matrix-org/matrix-react-sdk/blob/1e6c6e9d800890c732d60429449bc280de01a647/src/Rooms.js#L73
- * @param {MatrixClient} mx Matrix client
- * @param {string} roomId Id of room to add
- * @param {string} userId User id to which dm || undefined to remove
- * @returns {Promise} A promise
- */
-function addRoomToMDirect(mx, roomId, userId) {
- const mDirectsEvent = mx.getAccountData('m.direct');
- let userIdToRoomIds = {};
-
- if (typeof mDirectsEvent !== 'undefined') userIdToRoomIds = structuredClone(mDirectsEvent.getContent());
-
- // remove it from the lists of any others users
- // (it can only be a DM room for one person)
- Object.keys(userIdToRoomIds).forEach((thisUserId) => {
- const roomIds = userIdToRoomIds[thisUserId];
-
- if (thisUserId !== userId) {
- const indexOfRoomId = roomIds.indexOf(roomId);
- if (indexOfRoomId > -1) {
- roomIds.splice(indexOfRoomId, 1);
- }
- }
- });
-
- // now add it, if it's not already there
- if (userId) {
- const roomIds = userIdToRoomIds[userId] || [];
- if (roomIds.indexOf(roomId) === -1) {
- roomIds.push(roomId);
- }
- userIdToRoomIds[userId] = roomIds;
- }
-
- return mx.setAccountData('m.direct', userIdToRoomIds);
-}
-
-/**
- * Given a room, estimate which of its members is likely to
- * be the target if the room were a DM room and return that user.
- * https://github.com/matrix-org/matrix-react-sdk/blob/1e6c6e9d800890c732d60429449bc280de01a647/src/Rooms.js#L117
- *
- * @param {Object} room Target room
- * @param {string} myUserId User ID of the current user
- * @returns {string} User ID of the user that the room is probably a DM with
- */
-function guessDMRoomTargetId(room, myUserId) {
- let oldestMemberTs;
- let oldestMember;
-
- // Pick the joined user who's been here longest (and isn't us),
- room.getJoinedMembers().forEach((member) => {
- if (member.userId === myUserId) return;
-
- if (typeof oldestMemberTs === 'undefined' || (member.events.member && member.events.member.getTs() < oldestMemberTs)) {
- oldestMember = member;
- oldestMemberTs = member.events.member.getTs();
- }
- });
- if (oldestMember) return oldestMember.userId;
-
- // if there are no joined members other than us, use the oldest member
- room.getLiveTimeline().getState(EventTimeline.FORWARDS)?.getMembers().forEach((member) => {
- if (member.userId === myUserId) return;
-
- if (typeof oldestMemberTs === 'undefined' || (member.events.member && member.events.member.getTs() < oldestMemberTs)) {
- oldestMember = member;
- oldestMemberTs = member.events.member.getTs();
- }
- });
-
- if (typeof oldestMember === 'undefined') return myUserId;
- return oldestMember.userId;
-}
-
-function convertToDm(mx, roomId) {
- const room = mx.getRoom(roomId);
- return addRoomToMDirect(mx, roomId, guessDMRoomTargetId(room, mx.getUserId()));
-}
-
-function convertToRoom(mx, roomId) {
- return addRoomToMDirect(mx, roomId, undefined);
-}
-
-/**
- * @param {MatrixClient} mx
- * @param {string} roomId
- * @param {boolean} isDM
- * @param {string[]} via
- */
-async function join(mx, roomIdOrAlias, isDM = false, via = undefined) {
- try {
- const resultRoom = await mx.joinRoom(roomIdOrAlias, { viaServers: via });
-
- if (isDM) {
- const targetUserId = guessDMRoomTargetId(mx.getRoom(resultRoom.roomId), mx.getUserId());
- await addRoomToMDirect(mx, resultRoom.roomId, targetUserId);
- }
- return resultRoom.roomId;
- } catch (e) {
- throw new Error(e);
- }
-}
-
-async function create(mx, options, isDM = false) {
- try {
- const result = await mx.createRoom(options);
- if (isDM && typeof options.invite?.[0] === 'string') {
- await addRoomToMDirect(mx, result.room_id, options.invite[0]);
- }
- return result;
- } catch (e) {
- const errcodes = ['M_UNKNOWN', 'M_BAD_JSON', 'M_ROOM_IN_USE', 'M_INVALID_ROOM_STATE', 'M_UNSUPPORTED_ROOM_VERSION'];
- if (errcodes.includes(e.errcode)) {
- throw new Error(e);
- }
- throw new Error('Something went wrong!');
- }
-}
-
-async function createDM(mx, userIdOrIds, isEncrypted = true) {
- const options = {
- is_direct: true,
- invite: Array.isArray(userIdOrIds) ? userIdOrIds : [userIdOrIds],
- visibility: 'private',
- preset: 'trusted_private_chat',
- initial_state: [],
- };
- if (isEncrypted) {
- options.initial_state.push({
- type: 'm.room.encryption',
- state_key: '',
- content: {
- algorithm: 'm.megolm.v1.aes-sha2',
- },
- });
- }
-
- const result = await create(mx, options, true);
- return result;
-}
-
-async function createRoom(mx, opts) {
- // joinRule: 'public' | 'invite' | 'restricted'
- const { name, topic, joinRule } = opts;
- const alias = opts.alias ?? undefined;
- const parentId = opts.parentId ?? undefined;
- const isSpace = opts.isSpace ?? false;
- const isEncrypted = opts.isEncrypted ?? false;
- const powerLevel = opts.powerLevel ?? undefined;
- const blockFederation = opts.blockFederation ?? false;
-
- const visibility = joinRule === 'public' ? 'public' : 'private';
- const options = {
- creation_content: undefined,
- name,
- topic,
- visibility,
- room_alias_name: alias,
- initial_state: [],
- power_level_content_override: undefined,
- };
- if (isSpace) {
- options.creation_content = { type: 'm.space' };
- }
- if (blockFederation) {
- options.creation_content = { 'm.federate': false };
- }
- if (isEncrypted) {
- options.initial_state.push({
- type: 'm.room.encryption',
- state_key: '',
- content: {
- algorithm: 'm.megolm.v1.aes-sha2',
- },
- });
- }
- if (powerLevel) {
- options.power_level_content_override = {
- users: {
- [mx.getUserId()]: powerLevel,
- },
- };
- }
- if (parentId) {
- options.initial_state.push({
- type: 'm.space.parent',
- state_key: parentId,
- content: {
- canonical: true,
- via: [getIdServer(mx.getUserId())],
- },
- });
- }
- if (parentId && joinRule === 'restricted') {
- const caps = await mx.getCapabilities();
- if (caps['m.room_versions'].available?.['9'] !== 'stable') {
- throw new Error("ERROR: The server doesn't support restricted rooms");
- }
- if (Number(caps['m.room_versions'].default) < 9) {
- options.room_version = '9';
- }
- options.initial_state.push({
- type: 'm.room.join_rules',
- content: {
- join_rule: 'restricted',
- allow: [{
- type: 'm.room_membership',
- room_id: parentId,
- }],
- },
- });
- }
-
- const result = await create(mx, options);
-
- if (parentId) {
- await mx.sendStateEvent(parentId, 'm.space.child', {
- auto_join: false,
- suggested: false,
- via: [getIdServer(mx.getUserId())],
- }, result.room_id);
- }
-
- return result;
-}
-
-async function ignore(mx, userIds) {
-
- let ignoredUsers = mx.getIgnoredUsers().concat(userIds);
- ignoredUsers = [...new Set(ignoredUsers)];
- await mx.setIgnoredUsers(ignoredUsers);
-}
-
-async function unignore(mx, userIds) {
- const ignoredUsers = mx.getIgnoredUsers();
- await mx.setIgnoredUsers(ignoredUsers.filter((id) => !userIds.includes(id)));
-}
-
-async function setPowerLevel(mx, roomId, userId, powerLevel) {
- const result = await mx.setPowerLevel(roomId, userId, powerLevel);
- return result;
-}
-
-async function setMyRoomNick(mx, roomId, nick) {
- const room = mx.getRoom(roomId);
- const mEvent = room.getLiveTimeline().getState(EventTimeline.FORWARDS).getStateEvents('m.room.member', mx.getUserId());
- const content = mEvent?.getContent();
- if (!content) return;
- await mx.sendStateEvent(roomId, 'm.room.member', {
- ...content,
- displayname: nick,
- }, mx.getUserId());
-}
-
-async function setMyRoomAvatar(mx, roomId, mxc) {
- const room = mx.getRoom(roomId);
- const mEvent = room.getLiveTimeline().getState(EventTimeline.FORWARDS).getStateEvents('m.room.member', mx.getUserId());
- const content = mEvent?.getContent();
- if (!content) return;
- await mx.sendStateEvent(roomId, 'm.room.member', {
- ...content,
- avatar_url: mxc,
- }, mx.getUserId());
-}
-
-export {
- convertToDm,
- convertToRoom,
- join,
- createDM, createRoom,
- ignore, unignore,
- setPowerLevel,
- setMyRoomNick, setMyRoomAvatar,
-};
+++ /dev/null
-import { Dispatcher } from 'flux';
-
-const appDispatcher = new Dispatcher();
-export default appDispatcher;
import { createClient, MatrixClient, IndexedDBStore, IndexedDBCryptoStore } from 'matrix-js-sdk';
-import { cryptoCallbacks } from './state/secretStorageKeys';
+import { cryptoCallbacks } from './secretStorageKeys';
import { clearNavToActivePathStore } from '../app/state/navToActivePath';
type Session = {
--- /dev/null
+const secretStorageKeys = new Map();
+
+export function storePrivateKey(keyId, privateKey) {
+ if (privateKey instanceof Uint8Array === false) {
+ throw new Error('Unable to store, privateKey is invalid.');
+ }
+ secretStorageKeys.set(keyId, privateKey);
+}
+
+function hasPrivateKey(keyId) {
+ return secretStorageKeys.get(keyId) instanceof Uint8Array;
+}
+
+function getPrivateKey(keyId) {
+ return secretStorageKeys.get(keyId);
+}
+
+export function clearSecretStorageKeys() {
+ secretStorageKeys.clear();
+}
+
+async function getSecretStorageKey({ keys }) {
+ const keyIds = Object.keys(keys);
+ const keyId = keyIds.find(hasPrivateKey);
+ if (!keyId) return undefined;
+ const privateKey = getPrivateKey(keyId);
+ return [keyId, privateKey];
+}
+
+function cacheSecretStorageKey(keyId, keyInfo, privateKey) {
+ secretStorageKeys.set(keyId, privateKey);
+}
+
+export const cryptoCallbacks = {
+ getSecretStorageKey,
+ cacheSecretStorageKey,
+};
+++ /dev/null
-import cons from './cons';
-
-const isAuthenticated = () => localStorage.getItem(cons.secretKey.ACCESS_TOKEN) !== null;
-
-const getSecret = () => ({
- accessToken: localStorage.getItem(cons.secretKey.ACCESS_TOKEN),
- deviceId: localStorage.getItem(cons.secretKey.DEVICE_ID),
- userId: localStorage.getItem(cons.secretKey.USER_ID),
- baseUrl: localStorage.getItem(cons.secretKey.BASE_URL),
-});
-
-export { isAuthenticated, getSecret };
+++ /dev/null
-const cons = {
- version: '4.9.1',
- secretKey: {
- ACCESS_TOKEN: 'cinny_access_token',
- DEVICE_ID: 'cinny_device_id',
- USER_ID: 'cinny_user_id',
- BASE_URL: 'cinny_hs_base_url',
- },
- status: {
- PRE_FLIGHT: 'pre-flight',
- IN_FLIGHT: 'in-flight',
- SUCCESS: 'success',
- ERROR: 'error',
- },
- actions: {
- navigation: {
- OPEN_SPACE_ADDEXISTING: 'OPEN_SPACE_ADDEXISTING',
- OPEN_CREATE_ROOM: 'OPEN_CREATE_ROOM',
- OPEN_JOIN_ALIAS: 'OPEN_JOIN_ALIAS',
- OPEN_INVITE_USER: 'OPEN_INVITE_USER',
- OPEN_PROFILE_VIEWER: 'OPEN_PROFILE_VIEWER',
- OPEN_SEARCH: 'OPEN_SEARCH',
- OPEN_REUSABLE_CONTEXT_MENU: 'OPEN_REUSABLE_CONTEXT_MENU',
- OPEN_REUSABLE_DIALOG: 'OPEN_REUSABLE_DIALOG',
- },
- },
- events: {
- navigation: {
- SPACE_ADDEXISTING_OPENED: 'SPACE_ADDEXISTING_OPENED',
- CREATE_ROOM_OPENED: 'CREATE_ROOM_OPENED',
- JOIN_ALIAS_OPENED: 'JOIN_ALIAS_OPENED',
- INVITE_USER_OPENED: 'INVITE_USER_OPENED',
- SEARCH_OPENED: 'SEARCH_OPENED',
- REUSABLE_CONTEXT_MENU_OPENED: 'REUSABLE_CONTEXT_MENU_OPENED',
- REUSABLE_DIALOG_OPENED: 'REUSABLE_DIALOG_OPENED',
- },
- },
-};
-
-Object.freeze(cons);
-
-export default cons;
+++ /dev/null
-import EventEmitter from 'events';
-import appDispatcher from '../dispatcher';
-import cons from './cons';
-
-class Navigation extends EventEmitter {
- constructor() {
- super();
- this.rawModelStack = [];
- }
-
- get isRawModalVisible() {
- return this.rawModelStack.length > 0;
- }
-
- setIsRawModalVisible(visible) {
- if (visible) this.rawModelStack.push(true);
- else this.rawModelStack.pop();
- }
-
- navigate(action) {
- const actions = {
- [cons.actions.navigation.OPEN_SPACE_ADDEXISTING]: () => {
- this.emit(cons.events.navigation.SPACE_ADDEXISTING_OPENED, action.roomId, action.spaces);
- },
- [cons.actions.navigation.OPEN_CREATE_ROOM]: () => {
- this.emit(
- cons.events.navigation.CREATE_ROOM_OPENED,
- action.isSpace,
- action.parentId,
- );
- },
- [cons.actions.navigation.OPEN_JOIN_ALIAS]: () => {
- this.emit(
- cons.events.navigation.JOIN_ALIAS_OPENED,
- action.term,
- );
- },
- [cons.actions.navigation.OPEN_INVITE_USER]: () => {
- this.emit(cons.events.navigation.INVITE_USER_OPENED, action.roomId, action.searchTerm);
- },
- [cons.actions.navigation.OPEN_PROFILE_VIEWER]: () => {
- this.emit(cons.events.navigation.PROFILE_VIEWER_OPENED, action.userId, action.roomId);
- },
- [cons.actions.navigation.OPEN_SEARCH]: () => {
- this.emit(
- cons.events.navigation.SEARCH_OPENED,
- action.term,
- );
- },
- [cons.actions.navigation.OPEN_REUSABLE_CONTEXT_MENU]: () => {
- this.emit(
- cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED,
- action.placement,
- action.cords,
- action.render,
- action.afterClose,
- );
- },
- [cons.actions.navigation.OPEN_REUSABLE_DIALOG]: () => {
- this.emit(
- cons.events.navigation.REUSABLE_DIALOG_OPENED,
- action.title,
- action.render,
- action.afterClose,
- );
- },
- };
- actions[action.type]?.();
- }
-}
-
-const navigation = new Navigation();
-appDispatcher.register(navigation.navigate.bind(navigation));
-
-export default navigation;
+++ /dev/null
-const secretStorageKeys = new Map();
-
-export function storePrivateKey(keyId, privateKey) {
- if (privateKey instanceof Uint8Array === false) {
- throw new Error('Unable to store, privateKey is invalid.');
- }
- secretStorageKeys.set(keyId, privateKey);
-}
-
-export function hasPrivateKey(keyId) {
- return secretStorageKeys.get(keyId) instanceof Uint8Array;
-}
-
-export function getPrivateKey(keyId) {
- return secretStorageKeys.get(keyId);
-}
-
-export function deletePrivateKey(keyId) {
- delete secretStorageKeys.delete(keyId);
-}
-
-export function clearSecretStorageKeys() {
- secretStorageKeys.clear();
-}
-
-async function getSecretStorageKey({ keys }) {
- const keyIds = Object.keys(keys);
- const keyId = keyIds.find(hasPrivateKey);
- if (!keyId) return undefined;
- const privateKey = getPrivateKey(keyId);
- return [keyId, privateKey];
-}
-
-function cacheSecretStorageKey(keyId, keyInfo, privateKey) {
- secretStorageKeys.set(keyId, privateKey);
-}
-
-export const cryptoCallbacks = {
- getSecretStorageKey,
- cacheSecretStorageKey,
-};
--- /dev/null
+@font-face {
+ font-family: Twemoji;
+ src: url('../public/font/Twemoji.Mozilla.v15.1.0.woff2'),
+ url('../public/font/Twemoji.Mozilla.v15.1.0.ttf');
+ font-display: swap;
+}
+
+:root {
+ --tc-link: hsl(213deg 100% 45%);
+
+ /* user mxid colors */
+ --mx-uc-1: hsl(208, 100%, 45%);
+ --mx-uc-2: hsl(302, 100%, 30%);
+ --mx-uc-3: hsl(163, 100%, 30%);
+ --mx-uc-4: hsl(343, 100%, 45%);
+ --mx-uc-5: hsl(24, 100%, 45%);
+ --mx-uc-6: hsl(181, 100%, 30%);
+ --mx-uc-7: hsl(242, 100%, 45%);
+ --mx-uc-8: hsl(94, 100%, 35%);
+
+ --font-emoji: 'Twemoji_DISABLED';
+ --font-secondary: 'InterVariable', var(--font-emoji), sans-serif;
+}
+
+.dark-theme,
+.butter-theme {
+ --tc-link: hsl(213deg 100% 80%);
+
+ --mx-uc-1: hsl(208, 100%, 75%);
+ --mx-uc-2: hsl(301, 100%, 80%);
+ --mx-uc-3: hsl(163, 100%, 70%);
+ --mx-uc-4: hsl(343, 100%, 75%);
+ --mx-uc-5: hsl(24, 100%, 70%);
+ --mx-uc-6: hsl(181, 100%, 60%);
+ --mx-uc-7: hsl(243, 100%, 80%);
+ --mx-uc-8: hsl(94, 100%, 80%);
+
+ --font-secondary: 'InterVariable', var(--font-emoji), sans-serif;
+}
+
+html {
+ height: 100%;
+ overflow: hidden;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ font-family: var(--font-secondary);
+ font-size: 16px;
+ font-weight: 400;
+
+ /*Why font-variant-ligatures => https://github.com/rsms/inter/issues/222 */
+ font-variant-ligatures: no-contextual;
+}
+#root {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ -webkit-tap-highlight-color: transparent;
+}
+a {
+ color: var(--tc-link);
+ text-decoration: none;
+}
+a:hover {
+ text-decoration: underline;
+}
+
+[data-mx-spoiler][aria-pressed='true'] a {
+ color: transparent;
+ pointer-events: none;
+}
+
+b {
+ font-weight: 500;
+}
+label {
+ margin: 0;
+ padding: 0;
+}
+button,
+textarea {
+ margin: 0;
+ padding: 0;
+ background-color: transparent;
+ color: inherit;
+ font-family: inherit;
+ font-size: inherit;
+ font-weight: inherit;
+ line-height: inherit;
+ letter-spacing: inherit;
+ border: none;
+}
+button {
+ max-width: 100%;
+ text-transform: none;
+ text-align: inherit;
+ overflow: visible;
+ -webkit-appearance: button;
+}
+textarea,
+input,
+input[type],
+input[type='text'],
+input[type='username'],
+input[type='password'],
+input[type='email'],
+input[type='checkbox'] {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+textarea {
+ color: inherit;
+ word-spacing: inherit;
+}
+
+audio:not([controls]) {
+ display: none !important;
+}
+++ /dev/null
-@use './app/partials/screen';
-
-@font-face {
- font-family: Twemoji;
- src: url('../public/font/Twemoji.Mozilla.v15.1.0.woff2'),
- url('../public/font/Twemoji.Mozilla.v15.1.0.ttf');
- font-display: swap;
-}
-
-:root {
- /* background color | --bg-[background type]: value */
- --bg-surface: #ffffff;
- --bg-surface-transparent: #ffffff00;
- --bg-surface-low: #f6f6f6;
- --bg-surface-low-transparent: #f6f6f600;
- --bg-surface-extra-low: #f6f6f6;
- --bg-surface-extra-low-transparent: #f6f6f600;
- --bg-surface-hover: rgba(0, 0, 0, 3%);
- --bg-surface-active: rgba(0, 0, 0, 5%);
- --bg-surface-border: rgba(0, 0, 0, 6%);
-
- --bg-primary: rgb(18, 69, 168);
- --bg-primary-hover: rgba(18, 69, 168, 80%);
- --bg-primary-active: rgba(18, 69, 168, 70%);
- --bg-primary-border: rgba(18, 69, 168, 38%);
-
- --bg-positive: rgb(1, 115, 67);
- --bg-positive-hover: rgba(1, 115, 67, 8%);
- --bg-positive-active: rgba(1, 115, 67, 15%);
- --bg-positive-border: rgba(1, 115, 67, 40%);
-
- --bg-caution: rgb(134, 67, 0);
- --bg-caution-hover: rgba(134, 67, 0, 8%);
- --bg-caution-active: rgba(134, 67, 0, 15%);
- --bg-caution-border: rgba(134, 67, 0, 40%);
-
- --bg-danger: rgb(157, 15, 15);
- --bg-danger-hover: rgba(157, 15, 15, 5%);
- --bg-danger-active: rgba(157, 15, 15, 10%);
- --bg-danger-border: rgba(157, 15, 15, 20%);
-
- --bg-tooltip: #353535;
- --bg-badge: #989898;
- --bg-ping: hsla(137deg, 100%, 68%, 40%);
- --bg-ping-hover: hsla(137deg, 100%, 68%, 50%);
- --bg-divider: hsla(0, 0%, 0%, 0.1);
-
- /* text color | --tc-[background type]-[priority]: value */
- --tc-surface-high: #000000;
- --tc-surface-normal: rgba(0, 0, 0, 78%);
- --tc-surface-normal-low: rgba(0, 0, 0, 60%);
- --tc-surface-low: rgba(0, 0, 0, 48%);
-
- --tc-primary-high: #ffffff;
- --tc-primary-normal: rgba(255, 255, 255, 68%);
- --tc-primary-low: rgba(255, 255, 255, 40%);
-
- --tc-positive-high: var(--bg-positive);
- --tc-positive-normal: rgb(1, 115, 67, 80%);
- --tc-positive-low: rgb(1, 115, 67, 60%);
-
- --tc-caution-high: var(--bg-caution);
- --tc-caution-normal: rgb(134, 67, 0, 80%);
- --tc-caution-low: rgb(134, 67, 0, 60%);
-
- --tc-danger-high: var(--bg-danger);
- --tc-danger-normal: rgba(157, 15, 15, 88%);
- --tc-danger-low: rgba(157, 15, 15, 60%);
-
- --tc-code: #e62498;
- --tc-link: hsl(213deg 100% 45%);
- --tc-tooltip: white;
- --tc-badge: white;
-
- /* system icons | --ic-[background type]-[priority]: value */
- --ic-surface-high: #272727;
- --ic-surface-normal: #626262;
- --ic-surface-low: #7c7c7c;
- --ic-primary-high: #ffffff;
- --ic-primary-normal: #ffffff;
- --ic-positive-high: rgba(1, 115, 67);
- --ic-positive-normal: rgba(1, 115, 67, 80%);
- --ic-caution-high: rgba(134, 67, 0);
- --ic-caution-normal: rgba(134, 67, 0, 80%);
- --ic-danger-high: rgba(157, 15, 15);
- --ic-danger-normal: rgba(157, 15, 15, 0.7);
-
- /* user mxid colors */
- --mx-uc-1: hsl(208, 100%, 45%);
- --mx-uc-2: hsl(302, 100%, 30%);
- --mx-uc-3: hsl(163, 100%, 30%);
- --mx-uc-4: hsl(343, 100%, 45%);
- --mx-uc-5: hsl(24, 100%, 45%);
- --mx-uc-6: hsl(181, 100%, 30%);
- --mx-uc-7: hsl(242, 100%, 45%);
- --mx-uc-8: hsl(94, 100%, 35%);
-
- /* system icon size | -ic-[size]: value */
- --ic-large: 38px;
- --ic-normal: 24px;
- --ic-small: 20px;
- --ic-extra-small: 18px;
-
- /* avatar size */
- --av-large: 80px;
- --av-normal: 42px;
- --av-small: 36px;
- --av-extra-small: 24px;
-
- /* shadow and overlay */
- --bg-overlay: rgba(0, 0, 0, 20%);
- --bg-overlay-low: rgba(0, 0, 0, 50%);
-
- --bs-popup: 0 0 16px rgba(0, 0, 0, 10%);
-
- --bs-surface-border: inset 0 0 0 1px var(--bg-surface-border);
- --bs-surface-outline: 0 0 0 2px var(--bg-surface-border);
-
- --bs-primary-border: inset 0 0 0 1px var(--bg-primary-border);
- --bs-primary-outline: 0 0 0 2px var(--bg-primary-border);
-
- --bs-positive-border: inset 0 0 0 1px var(--bg-positive-border);
- --bs-positive-outline: 0 0 0 2px var(--bg-positive-border);
-
- --bs-caution-border: inset 0 0 0 1px var(--bg-caution-border);
- --bs-caution-outline: 0 0 0 2px var(--bg-caution-border);
-
- --bs-danger-border: inset 0 0 0 1px var(--bg-danger-border);
- --bs-danger-outline: 0 0 0 2px var(--bg-danger-border);
-
- /* border */
- --bo-radius: 8px;
-
- /* font styles: font-size, letter-spacing, line-hight */
- --fs-h1: 36px;
- --ls-h1: -1.5px;
- --lh-h1: 38px;
-
- --fs-h2: 24px;
- --ls-h2: -0.5px;
- --lh-h2: 30px;
-
- --fs-s1: 18px;
- --ls-s1: -0.2px;
- --lh-s1: 24px;
-
- --fs-b1: 16px;
- --ls-b1: 0.1px;
- --lh-b1: 24px;
-
- --fs-b2: 14px;
- --ls-b2: 0.2px;
- --lh-b2: 20px;
-
- --fs-b3: 12px;
- --ls-b3: 0px;
- --lh-b3: 16px;
-
- /* font-weight */
- --fw-light: 300;
- --fw-normal: 420;
- --fw-medium: 500;
- --fw-bold: 700;
-
- /* spacing | --sp-[space]: value */
- --sp-none: 0px;
- --sp-ultra-tight: 4px;
- --sp-extra-tight: 8px;
- --sp-tight: 12px;
- --sp-normal: 16px;
- --sp-loose: 20px;
- --sp-extra-loose: 32px;
-
- /* other */
- --border-width: 1px;
- --header-height: 54px;
- --navigation-sidebar-width: calc(64px + var(--border-width));
- --navigation-drawer-width: calc(280px + var(--border-width));
- --navigation-width: calc(var(--navigation-sidebar-width) + var(--navigation-drawer-width));
- --people-drawer-width: calc(268px - var(--border-width));
-
- --popup-window-drawer-width: 280px;
-
- @include screen.smallerThan(tabletBreakpoint) {
- --navigation-drawer-width: calc(240px + var(--border-width));
- --people-drawer-width: calc(256px - var(--border-width));
- --popup-window-drawer-width: 240px;
- }
-
- /* transition curves */
- --fluid-push: cubic-bezier(0, 0.8, 0.67, 0.97);
- --fluid-slide-down: cubic-bezier(0.02, 0.82, 0.4, 0.96);
- --fluid-slide-up: cubic-bezier(0.13, 0.56, 0.25, 0.99);
-
- --font-emoji: 'Twemoji_DISABLED';
- --font-primary: 'InterVariable', var(--font-emoji), sans-serif;
- --font-secondary: 'InterVariable', var(--font-emoji), sans-serif;
-}
-
-.silver-theme {
- /* background color | --bg-[background type]: value */
- --bg-surface: hsl(0, 0%, 95%);
- --bg-surface-transparent: hsla(0, 0%, 95%, 0);
- --bg-surface-low: hsl(0, 0%, 91%);
- --bg-surface-low-transparent: hsla(0, 0%, 91%, 0);
- --bg-surface-extra-low: hsl(0, 0%, 91%);
- --bg-surface-extra-low-transparent: hsla(0, 0%, 91%, 0);
-}
-
-.dark-theme,
-.butter-theme {
- /* background color | --bg-[background type]: value */
- --bg-surface: #262626;
- --bg-surface-transparent: #26262600;
- --bg-surface-low: #1a1a1a;
- --bg-surface-low-transparent: #1a1a1a00;
- --bg-surface-extra-low: #1a1a1a;
- --bg-surface-extra-low-transparent: #1a1a1a00;
- --bg-surface-hover: #333333;
- --bg-surface-active: #404040;
- --bg-surface-border: #404040;
-
- --bg-primary: rgb(189, 182, 236);
- --bg-primary-hover: rgba(189, 182, 236, 0.8);
- --bg-primary-active: rgba(189, 182, 236, 70%);
- --bg-primary-border: rgba(189, 182, 236, 38%);
-
- --bg-positive: rgb(133, 224, 186);
- --bg-positive-hover: rgba(133, 224, 186, 8%);
- --bg-positive-active: rgba(133, 224, 186, 15%);
- --bg-positive-border: rgba(133, 224, 186, 40%);
-
- --bg-caution: rgb(227, 186, 145);
- --bg-caution-hover: rgba(227, 186, 145, 8%);
- --bg-caution-active: rgba(227, 186, 145, 15%);
- --bg-caution-border: rgba(227, 186, 145, 40%);
-
- --bg-danger: rgb(230, 157, 157);
- --bg-danger-hover: rgba(230, 157, 157, 5%);
- --bg-danger-active: rgba(230, 157, 157, 10%);
- --bg-danger-border: rgba(230, 157, 157, 20%);
-
- --bg-tooltip: #000;
- --bg-badge: hsl(0, 0%, 75%);
- --bg-ping: hsla(137deg, 100%, 38%, 40%);
- --bg-ping-hover: hsla(137deg, 100%, 38%, 50%);
- --bg-divider: hsla(0, 0%, 100%, 0.1);
-
- /* text color | --tc-[background type]-[priority]: value */
- --tc-surface-high: rgba(255, 255, 255, 98%);
- --tc-surface-normal: rgba(255, 255, 255, 94%);
- --tc-surface-normal-low: rgba(255, 255, 255, 60%);
- --tc-surface-low: rgba(255, 255, 255, 58%);
-
- --tc-primary-high: rgb(44, 40, 67);
- --tc-primary-normal: rgba(44, 40, 67, 0.68);
- --tc-primary-low: rgba(44, 40, 67, 0.4);
-
- --tc-positive-high: var(--bg-positive);
- --tc-positive-normal: rgb(133, 224, 186, 80%);
- --tc-positive-low: rgb(133, 224, 186, 60%);
-
- --tc-caution-high: var(--bg-caution);
- --tc-caution-normal: rgb(227, 186, 145, 80%);
- --tc-caution-low: rgb(227, 186, 145, 60%);
-
- --tc-danger-high: var(--bg-danger);
- --tc-danger-normal: rgba(230, 157, 157, 88%);
- --tc-danger-low: rgba(230, 157, 157, 60%);
-
- --tc-code: #e565b1;
- --tc-link: hsl(213deg 100% 80%);
- --tc-badge: black;
-
- /* system icons | --ic-[background type]-[priority]: value */
- --ic-surface-high: rgb(255, 255, 255);
- --ic-surface-normal: rgba(255, 255, 255, 84%);
- --ic-surface-low: rgba(255, 255, 255, 64%);
- --ic-primary-high: var(--tc-primary-high);
- --ic-primary-normal: var(--tc-primary-high);
- --ic-primary-low: var(--tc-primary-high);
- --ic-positive-high: rgba(133, 224, 186);
- --ic-positive-normal: rgba(133, 224, 186, 80%);
- --ic-caution-high: rgba(227, 186, 145);
- --ic-caution-normal: rgba(227, 186, 145, 80%);
- --ic-danger-high: rgba(230, 157, 157);
- --ic-danger-normal: rgba(230, 157, 157, 0.7);
-
- --mx-uc-1: hsl(208, 100%, 75%);
- --mx-uc-2: hsl(301, 100%, 80%);
- --mx-uc-3: hsl(163, 100%, 70%);
- --mx-uc-4: hsl(343, 100%, 75%);
- --mx-uc-5: hsl(24, 100%, 70%);
- --mx-uc-6: hsl(181, 100%, 60%);
- --mx-uc-7: hsl(243, 100%, 80%);
- --mx-uc-8: hsl(94, 100%, 80%);
-
- /* shadow and overlay */
- --bg-overlay: rgba(0, 0, 0, 60%);
- --bg-overlay-low: rgba(0, 0, 0, 80%);
-
- --bs-popup: 0 0 16px rgba(0, 0, 0, 25%);
-
- --bs-surface-border: inset 0 0 0 1px var(--bg-surface-border);
- --bs-surface-outline: 0 0 0 2px var(--bg-surface-border);
-
- --bs-primary-border: inset 0 0 0 1px var(--bg-primary-border);
- --bs-primary-outline: 0 0 0 2px var(--bg-primary-border);
-
- /* font styles: font-size, letter-spacing, line-hight */
- --fs-h1: 35.6px;
-
- --fs-h2: 23.6px;
-
- --fs-s1: 17.6px;
-
- --fs-b1: 14.6px;
- --ls-b1: 0.14px;
-
- --fs-b2: 13.2px;
-
- --fs-b3: 11.2px;
-
- /* override normal font weight for dark mode */
- --fw-normal: 350;
- --fw-medium: 450;
- --fw-bold: 550;
-
- --font-secondary: 'InterVariable', var(--font-emoji), sans-serif;
-}
-
-.butter-theme {
- /* background color | --bg-[background type]: value */
- --bg-surface: #262621;
- --bg-surface-transparent: #26262100;
- --bg-surface-low: #1a1916;
- --bg-surface-low-transparent: #1a191600;
- --bg-surface-extra-low: #1a1916;
- --bg-surface-extra-low-transparent: #1a1916;
- --bg-surface-hover: #33322c;
- --bg-surface-active: #403f38;
- --bg-surface-border: #403f38;
-
- --bg-badge: #c4c1ab;
-
- /* text color | --tc-[background type]-[priority]: value */
- --tc-surface-high: rgb(255, 251, 222);
- --tc-surface-normal: rgba(255, 251, 222, 94%);
- --tc-surface-normal-low: rgba(255, 251, 222, 60%);
- --tc-surface-low: rgba(255, 251, 222, 58%);
-
- /* system icons | --ic-[background type]-[priority]: value */
- --ic-surface-high: rgb(255, 251, 222);
- --ic-surface-normal: rgba(255, 251, 222, 84%);
- --ic-surface-low: rgba(255, 251, 222, 64%);
-}
-
-.font-primary {
- font-family: var(--font-primary);
-
- /* override font styles for primary font */
- --fs-h1: 36px;
- --ls-h1: -1.5px;
- --lh-h1: 38px;
-
- --fs-h2: 24px;
- --ls-h2: -0.5px;
- --lh-h2: 30px;
-
- --fs-s1: 18px;
- --ls-s1: -0.2px;
- --lh-s1: 24px;
-
- --fs-b1: 16px;
- --ls-b1: 0.1px;
- --lh-b1: 24px;
-
- --fs-b2: 14px;
- --ls-b2: 0.2px;
- --lh-b2: 20px;
-
- --fs-b3: 12px;
- --ls-b3: 0px;
- --lh-b3: 16px;
-
- --fw-light: 300;
- --fw-normal: 400;
- --fw-medium: 500;
- --fw-bold: 600;
-}
-
-html {
- height: 100%;
- overflow: hidden;
-}
-
-body {
- margin: 0;
- padding: 0;
- height: 100%;
- font-family: var(--font-secondary);
- font-size: 16px;
- font-weight: var(--fw-normal);
- background-color: var(--bg-surface-low);
-
- /*Why font-variant-ligatures => https://github.com/rsms/inter/issues/222 */
- font-variant-ligatures: no-contextual;
-}
-#root {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
-}
-
-*,
-*::before,
-*::after {
- box-sizing: border-box;
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
- -webkit-tap-highlight-color: transparent;
-}
-a {
- color: var(--tc-link);
- text-decoration: none;
- &:hover {
- text-decoration: underline;
- }
-}
-
-[data-mx-spoiler][aria-pressed='true'] a {
- color: transparent;
- pointer-events: none;
-}
-
-b {
- font-weight: var(--fw-medium);
-}
-label {
- margin: 0;
- padding: 0;
-}
-button,
-textarea {
- margin: 0;
- padding: 0;
- background-color: transparent;
- color: inherit;
- font-family: inherit;
- font-size: inherit;
- font-weight: inherit;
- line-height: inherit;
- letter-spacing: inherit;
- border: none;
-}
-button {
- max-width: 100%;
- text-transform: none;
- text-align: inherit;
- overflow: visible;
- -webkit-appearance: button;
-}
-textarea,
-input,
-input[type],
-input[type='text'],
-input[type='username'],
-input[type='password'],
-input[type='email'],
-input[type='checkbox'] {
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
-}
-input[type='checkbox'] {
- margin: 0;
- padding: 0;
- width: 20px;
- height: 20px;
- border-radius: calc(var(--bo-radius) / 2);
- box-shadow: var(--bs-primary-border);
- background-color: var(--bg-surface);
- cursor: pointer;
- @extend .flex--center;
-
- &:checked {
- background-color: var(--bg-primary);
- &::before {
- content: '';
- display: inline-block;
- width: 12px;
- height: 6px;
- border: 6px solid white;
- border-width: 0 0 3px 3px;
- transform: rotateZ(-45deg) translate(1px, -1px);
- }
- }
-}
-
-textarea {
- color: inherit;
- word-spacing: inherit;
-}
-.noselect {
- -webkit-touch-callout: none; /* iOS Safari */
- -webkit-user-select: none; /* Safari */
- -khtml-user-select: none; /* Konqueror HTML */
- -moz-user-select: none; /* Old versions of Firefox */
- -ms-user-select: none; /* Internet Explorer/Edge */
- user-select: none; /* Non-prefixed version, currently
- supported by Chrome, Edge, Opera and Firefox */
-}
-
-audio:not([controls]) {
- display: none !important;
-}
-
-.flex--center {
- display: flex;
- justify-content: center;
- align-items: center;
-}
enableMapSet();
-import './index.scss';
+import './index.css';
import { trimTrailingSlash } from './app/utils/common';
import App from './app/pages/App';
+++ /dev/null
-import EventEmitter from 'events';
-
-class AsyncSearch extends EventEmitter {
- constructor() {
- super();
-
- this._reset();
-
- this.RESULT_SENT = 'RESULT_SENT';
- }
-
- _reset() {
- this.dataList = null;
- this.term = null;
- this.searchKeys = null;
- this.isContain = false;
- this.isCaseSensitive = false;
- this.normalizeUnicode = true;
- this.ignoreWhitespace = true;
- this.limit = null;
- this.findingList = [];
-
- this.searchUptoIndex = 0;
- this.sessionStartTimestamp = 0;
- }
-
- _softReset() {
- this.term = null;
- this.findingList = [];
- this.searchUptoIndex = 0;
- this.sessionStartTimestamp = 0;
- }
-
- /**
- * Setup the search.
- * opts.keys are required when dataList items are object.
- *
- * @param {[string | object]} dataList - A list to search in
- * @param {object} opts - Options
- * @param {string | [string]} [opts.keys=null]
- * @param {boolean} [opts.isContain=false] - Add finding to result if it contain search term
- * @param {boolean} [opts.isCaseSensitive=false]
- * @param {boolean} [opts.normalizeUnicode=true]
- * @param {boolean} [opts.ignoreWhitespace=true]
- * @param {number} [opts.limit=null] - Stop search after limit
- */
- setup(dataList, opts) {
- this._reset();
- this.dataList = dataList;
- this.searchKeys = opts?.keys || null;
- this.isContain = opts?.isContain || false;
- this.isCaseSensitive = opts?.isCaseSensitive || false;
- this.normalizeUnicode = opts?.normalizeUnicode || true;
- this.ignoreWhitespace = opts?.ignoreWhitespace || true;
- this.limit = opts?.limit || null;
- }
-
- search(term) {
- this._softReset();
-
- this.term = this._normalize(term);
- if (this.term === '') {
- this._sendFindings();
- return;
- }
-
- this._find(this.sessionStartTimestamp, 0);
- }
-
- _find(sessionTimestamp, lastFindingCount) {
- if (sessionTimestamp !== this.sessionStartTimestamp) return;
- this.sessionStartTimestamp = window.performance.now();
-
- for (
- let searchIndex = this.searchUptoIndex;
- searchIndex < this.dataList.length;
- searchIndex += 1
- ) {
- if (this._match(this.dataList[searchIndex])) {
- this.findingList.push(this.dataList[searchIndex]);
- if (typeof this.limit === 'number' && this.findingList.length >= this.limit) break;
- }
-
- const calcFinishTime = window.performance.now();
- if (calcFinishTime - this.sessionStartTimestamp > 8) {
- const thisFindingCount = this.findingList.length;
- const thisSessionTimestamp = this.sessionStartTimestamp;
- if (lastFindingCount !== thisFindingCount) this._sendFindings();
-
- this.searchUptoIndex = searchIndex + 1;
- setTimeout(() => this._find(thisSessionTimestamp, thisFindingCount));
- return;
- }
- }
-
- if (lastFindingCount !== this.findingList.length
- || lastFindingCount === 0) this._sendFindings();
- this._softReset();
- }
-
- _match(item) {
- if (typeof item === 'string') {
- return this._compare(item);
- }
- if (typeof item === 'object') {
- if (Array.isArray(this.searchKeys)) {
- return !!this.searchKeys.find((key) => this._compare(item[key]));
- }
- if (typeof this.searchKeys === 'string') {
- return this._compare(item[this.searchKeys]);
- }
- }
- return false;
- }
-
- _compare(item) {
- if (typeof item !== 'string') return false;
- const myItem = this._normalize(item);
- if (this.isContain) return myItem.indexOf(this.term) !== -1;
- return myItem.startsWith(this.term);
- }
-
- _normalize(item) {
- let myItem = item.normalize(this.normalizeUnicode ? 'NFKC' : 'NFC');
- if (!this.isCaseSensitive) myItem = myItem.toLocaleLowerCase();
- if (this.ignoreWhitespace) myItem = myItem.replace(/\s/g, '');
- return myItem;
- }
-
- _sendFindings() {
- this.emit(this.RESULT_SENT, this.findingList, this.term);
- }
-}
-
-export default AsyncSearch;
+++ /dev/null
-/* eslint-disable max-classes-per-file */
-export function bytesToSize(bytes) {
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
- if (bytes === 0) return 'n/a';
- const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10);
- if (i === 0) return `${bytes} ${sizes[i]}`;
- return `${(bytes / (1024 ** i)).toFixed(1)} ${sizes[i]}`;
-}
-
-export function diffMinutes(dt2, dt1) {
- let diff = (dt2.getTime() - dt1.getTime()) / 1000;
- diff /= 60;
- return Math.abs(Math.round(diff));
-}
-
-export function isInSameDay(dt2, dt1) {
- return (
- dt2.getFullYear() === dt1.getFullYear()
- && dt2.getMonth() === dt1.getMonth()
- && dt2.getDate() === dt1.getDate()
- );
-}
-
-/**
- * @param {Event} ev
- * @param {string} [targetSelector] element selector for Element.matches([selector])
- */
-export function getEventCords(ev, targetSelector) {
- let boxInfo;
-
- const path = ev.nativeEvent.composedPath();
- const target = targetSelector
- ? path.find((element) => element.matches?.(targetSelector))
- : null;
- if (target) {
- boxInfo = target.getBoundingClientRect();
- } else {
- boxInfo = ev.target.getBoundingClientRect();
- }
-
- return {
- x: boxInfo.x,
- y: boxInfo.y,
- width: boxInfo.width,
- height: boxInfo.height,
- detail: ev.detail,
- };
-}
-
-export function abbreviateNumber(number) {
- if (number > 99) return '99+';
- return number;
-}
-
-export class Debounce {
- constructor() {
- this.timeoutId = null;
- }
-
- /**
- * @param {function} func - callback function
- * @param {number} wait - wait in milliseconds to call func
- * @returns {func} debounceCallback - to pass arguments to func callback
- */
- _(func, wait) {
- const that = this;
- return function debounceCallback(...args) {
- clearTimeout(that.timeoutId);
- that.timeoutId = setTimeout(() => {
- func.apply(this, args);
- that.timeoutId = null;
- }, wait);
- };
- }
-}
-
-export class Throttle {
- constructor() {
- this.timeoutId = null;
- }
-
- /**
- * @param {function} func - callback function
- * @param {number} wait - wait in milliseconds to call func
- * @returns {function} throttleCallback - to pass arguments to func callback
- */
- _(func, wait) {
- const that = this;
- return function throttleCallback(...args) {
- if (that.timeoutId !== null) return;
- that.timeoutId = setTimeout(() => {
- func.apply(this, args);
- that.timeoutId = null;
- }, wait);
- };
- }
-}
-
-export function getUrlPrams(paramName) {
- const queryString = window.location.search;
- const urlParams = new URLSearchParams(queryString);
- return urlParams.get(paramName);
-}
-
-export function getScrollInfo(target) {
- const scroll = {};
- scroll.top = Math.round(target.scrollTop);
- scroll.height = Math.round(target.scrollHeight);
- scroll.viewHeight = Math.round(target.offsetHeight);
- scroll.isScrollable = scroll.height > scroll.viewHeight;
- return scroll;
-}
-
-export function avatarInitials(text) {
- return [...text][0];
-}
-
-export function cssVar(name) {
- return getComputedStyle(document.body).getPropertyValue(name);
-}
-
-export function setFavicon(url) {
- const favicon = document.querySelector('#favicon');
- if (!favicon) return;
- favicon.setAttribute('href', url);
-}
-
-export function copyToClipboard(text) {
- if (navigator.clipboard) {
- navigator.clipboard.writeText(text);
- } else {
- const host = document.body;
- const copyInput = document.createElement('input');
- copyInput.style.position = 'fixed';
- copyInput.style.opacity = '0';
- copyInput.value = text;
- host.append(copyInput);
-
- copyInput.select();
- copyInput.setSelectionRange(0, 99999);
- document.execCommand('Copy');
- copyInput.remove();
- }
-}
-
-export function suffixRename(name, validator) {
- let suffix = 2;
- let newName = name;
- do {
- newName = name + suffix;
- suffix += 1;
- } while (validator(newName));
-
- return newName;
-}
-
-export function getImageDimension(file) {
- return new Promise((resolve) => {
- const img = new Image();
- img.onload = async () => {
- resolve({
- w: img.width,
- h: img.height,
- });
- URL.revokeObjectURL(img.src);
- };
- img.src = URL.createObjectURL(file);
- });
-}
-
-export function scaleDownImage(imageFile, width, height) {
- return new Promise((resolve) => {
- const imgURL = URL.createObjectURL(imageFile);
- const img = new Image();
-
- img.onload = () => {
- let newWidth = img.width;
- let newHeight = img.height;
- if (newHeight <= height && newWidth <= width) {
- resolve(imageFile);
- }
-
- if (newHeight > height) {
- newWidth = Math.floor(newWidth * (height / newHeight));
- newHeight = height;
- }
- if (newWidth > width) {
- newHeight = Math.floor(newHeight * (width / newWidth));
- newWidth = width;
- }
-
- const canvas = document.createElement('canvas');
- canvas.width = newWidth;
- canvas.height = newHeight;
- const ctx = canvas.getContext('2d');
- ctx.drawImage(img, 0, 0, newWidth, newHeight);
-
- canvas.toBlob((thumbnail) => {
- URL.revokeObjectURL(imgURL);
- resolve(thumbnail);
- }, imageFile.type);
- };
-
- img.src = imgURL;
- });
-}
-
-/**
- * @param {sigil} string sigil to search for (for example '@', '#' or '$')
- * @param {flags} string regex flags
- * @param {prefix} string prefix appended at the beginning of the regex
- * @returns {RegExp}
- */
-export function idRegex(sigil, flags, prefix) {
- const servername = '(?:[a-zA-Z0-9-.]*[a-zA-Z0-9]+|\\[\\S+?\\])(?::\\d+)?';
- return new RegExp(`${prefix}(${sigil}\\S+:${servername})`, flags);
-}
-
-const matrixToRegex = /^https?:\/\/matrix.to\/#\/(\S+:\S+)/;
-/**
- * Parses a matrix.to URL into an matrix id.
- * This function can later be extended to support matrix: URIs
- * @param {string} uri The URI to parse
- * @returns {string|null} The id or null if the URI does not match
- */
-export function parseIdUri(uri) {
- const res = decodeURIComponent(uri).match(matrixToRegex);
- if (!res) return null;
- return res[1];
-}
+++ /dev/null
-import HashIC from '../../public/res/ic/outlined/hash.svg';
-import HashGlobeIC from '../../public/res/ic/outlined/hash-globe.svg';
-import HashLockIC from '../../public/res/ic/outlined/hash-lock.svg';
-import SpaceIC from '../../public/res/ic/outlined/space.svg';
-import SpaceGlobeIC from '../../public/res/ic/outlined/space-globe.svg';
-import SpaceLockIC from '../../public/res/ic/outlined/space-lock.svg';
-
-const WELL_KNOWN_URI = '/.well-known/matrix/client';
-
-export async function getBaseUrl(servername) {
- let protocol = 'https://';
- if (servername.match(/^https?:\/\//) !== null) protocol = '';
- const serverDiscoveryUrl = `${protocol}${servername}${WELL_KNOWN_URI}`;
- try {
- const result = await (await fetch(serverDiscoveryUrl, { method: 'GET' })).json();
-
- const baseUrl = result?.['m.homeserver']?.base_url;
- if (baseUrl === undefined) throw new Error();
- return baseUrl;
- } catch (e) {
- return `${protocol}${servername}`;
- }
-}
-
-export function getUsername(mx, userId) {
- const user = mx.getUser(userId);
- if (user === null) return userId;
- let username = user.displayName;
- if (typeof username === 'undefined') {
- username = userId;
- }
- return username;
-}
-
-export function getUsernameOfRoomMember(roomMember) {
- return roomMember.name || roomMember.userId;
-}
-
-export async function isRoomAliasAvailable(mx, alias) {
- try {
- const result = await mx.getRoomIdForAlias(alias);
- if (result.room_id) return false;
- return false;
- } catch (e) {
- if (e.errcode === 'M_NOT_FOUND') return true;
- return false;
- }
-}
-
-export function getPowerLabel(powerLevel) {
- if (powerLevel > 9000) return 'Goku';
- if (powerLevel > 100) return 'Founder';
- if (powerLevel === 100) return 'Admin';
- if (powerLevel >= 50) return 'Mod';
- return null;
-}
-
-export function parseReply(rawBody) {
- if (rawBody?.indexOf('>') !== 0) return null;
- let body = rawBody.slice(rawBody.indexOf('<') + 1);
- const user = body.slice(0, body.indexOf('>'));
-
- body = body.slice(body.indexOf('>') + 2);
- const replyBody = body.slice(0, body.indexOf('\n\n'));
- body = body.slice(body.indexOf('\n\n') + 2);
-
- if (user === '') return null;
-
- const isUserId = user.match(/^@.+:.+/);
-
- return {
- userId: isUserId ? user : null,
- displayName: isUserId ? null : user,
- replyBody,
- body,
- };
-}
-
-export function trimHTMLReply(html) {
- if (!html) return html;
- const suffix = '</mx-reply>';
- const i = html.indexOf(suffix);
- if (i < 0) {
- return html;
- }
- return html.slice(i + suffix.length);
-}
-
-export function joinRuleToIconSrc(joinRule, isSpace) {
- return ({
- restricted: () => (isSpace ? SpaceIC : HashIC),
- knock: () => (isSpace ? SpaceLockIC : HashLockIC),
- invite: () => (isSpace ? SpaceLockIC : HashLockIC),
- public: () => (isSpace ? SpaceGlobeIC : HashGlobeIC),
- }[joinRule]?.() || null);
-}
-
-export function getIdServer(userId) {
- const idParts = userId.split(':');
- return idParts[1];
-}
-
-export async function hasDevices(mx, userId) {
- try {
- const usersDeviceMap = await mx.getCrypto()?.getUserDeviceInfo([userId, mx.getUserId()], true);
-
- return Array.from(usersDeviceMap.values())
- .every((deviceIdToDevices) => deviceIdToDevices.size > 0);
- } catch (e) {
- console.error("Error determining if it's possible to encrypt to all users: ", e);
- return false;
- }
-}
+++ /dev/null
-export function memberByAtoZ(m1, m2) {
- const aName = m1.name;
- const bName = m2.name;
-
- if (aName.toLowerCase() < bName.toLowerCase()) {
- return -1;
- }
- if (aName.toLowerCase() > bName.toLowerCase()) {
- return 1;
- }
- return 0;
-}
-export function memberByPowerLevel(m1, m2) {
- const pl1 = m1.powerLevel;
- const pl2 = m2.powerLevel;
-
- if (pl1 > pl2) return -1;
- if (pl1 < pl2) return 1;
- return 0;
-}