forked from Archives/Athou_commafeed
update to mantine 7
This commit is contained in:
394
commafeed-client/package-lock.json
generated
394
commafeed-client/package-lock.json
generated
@@ -13,13 +13,12 @@
|
|||||||
"@lingui/core": "^4.6.0",
|
"@lingui/core": "^4.6.0",
|
||||||
"@lingui/macro": "^4.6.0",
|
"@lingui/macro": "^4.6.0",
|
||||||
"@lingui/react": "^4.6.0",
|
"@lingui/react": "^4.6.0",
|
||||||
"@mantine/core": "^6.0.21",
|
"@mantine/core": "^7.3.2",
|
||||||
"@mantine/form": "^6.0.21",
|
"@mantine/form": "^7.3.2",
|
||||||
"@mantine/hooks": "^6.0.21",
|
"@mantine/hooks": "^7.3.2",
|
||||||
"@mantine/modals": "^6.0.21",
|
"@mantine/modals": "^7.3.2",
|
||||||
"@mantine/notifications": "^6.0.21",
|
"@mantine/notifications": "^7.3.2",
|
||||||
"@mantine/spotlight": "^6.0.21",
|
"@mantine/spotlight": "^7.3.2",
|
||||||
"@mantine/styles": "^6.0.21",
|
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@reduxjs/toolkit": "^2.0.1",
|
"@reduxjs/toolkit": "^2.0.1",
|
||||||
"axios": "^1.6.3",
|
"axios": "^1.6.3",
|
||||||
@@ -28,11 +27,11 @@
|
|||||||
"interweave": "^13.1.0",
|
"interweave": "^13.1.0",
|
||||||
"monaco-editor": "^0.45.0",
|
"monaco-editor": "^0.45.0",
|
||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
"re-resizable": "^6.9.11",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-async-hook": "^4.0.0",
|
"react-async-hook": "^4.0.0",
|
||||||
"react-contexify": "^6.0.0",
|
"react-contexify": "^6.0.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-draggable": "^4.4.6",
|
||||||
"react-ga4": "^2.1.0",
|
"react-ga4": "^2.1.0",
|
||||||
"react-icons": "^4.12.0",
|
"react-icons": "^4.12.0",
|
||||||
"react-infinite-scroller": "^1.2.6",
|
"react-infinite-scroller": "^1.2.6",
|
||||||
@@ -1130,25 +1129,29 @@
|
|||||||
"integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw=="
|
"integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw=="
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/core": {
|
"node_modules/@floating-ui/core": {
|
||||||
"version": "1.2.6",
|
"version": "1.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.2.tgz",
|
||||||
"integrity": "sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg=="
|
"integrity": "sha512-Ii3MrfY/GAIN3OhXNzpCKaLxHQfJF9qvwq/kEJYdqDxeIHa01K8sldugal6TmeeXl+WMvhv9cnVzUTaFFJF09A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/utils": "^0.1.3"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/dom": {
|
"node_modules/@floating-ui/dom": {
|
||||||
"version": "1.2.7",
|
"version": "1.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz",
|
||||||
"integrity": "sha512-DyqylONj1ZaBnzj+uBnVfzdjjCkFCL2aA9ESHLyUOGSqb03RpbLMImP1ekIQXYs4KLk9jAjJfZAU8hXfWSahEg==",
|
"integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/core": "^1.2.6"
|
"@floating-ui/core": "^1.4.2",
|
||||||
|
"@floating-ui/utils": "^0.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/react": {
|
"node_modules/@floating-ui/react": {
|
||||||
"version": "0.19.2",
|
"version": "0.24.8",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.24.8.tgz",
|
||||||
"integrity": "sha512-JyNk4A0Ezirq8FlXECvRtQOX/iBe5Ize0W/pLkrZjfHW9GUV7Xnq6zm6fyZuQzaHHqEnVizmvlA96e1/CkZv+w==",
|
"integrity": "sha512-AuYeDoaR8jtUlUXtZ1IJ/6jtBkGnSpJXbGNzokBL87VDJ8opMq1Bgrc0szhK482ReQY6KZsMoZCVSb4xwalkBA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/react-dom": "^1.3.0",
|
"@floating-ui/react-dom": "^2.0.1",
|
||||||
"aria-hidden": "^1.1.3",
|
"aria-hidden": "^1.2.3",
|
||||||
"tabbable": "^6.0.1"
|
"tabbable": "^6.0.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -1157,17 +1160,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/react-dom": {
|
"node_modules/@floating-ui/react-dom": {
|
||||||
"version": "1.3.0",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz",
|
||||||
"integrity": "sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==",
|
"integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/dom": "^1.2.1"
|
"@floating-ui/dom": "^1.5.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0"
|
"react-dom": ">=16.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@floating-ui/utils": {
|
||||||
|
"version": "0.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz",
|
||||||
|
"integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
|
||||||
|
},
|
||||||
"node_modules/@fontsource/open-sans": {
|
"node_modules/@fontsource/open-sans": {
|
||||||
"version": "5.0.20",
|
"version": "5.0.20",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-5.0.20.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-5.0.20.tgz",
|
||||||
@@ -1490,111 +1498,108 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mantine/core": {
|
"node_modules/@mantine/core": {
|
||||||
"version": "6.0.21",
|
"version": "7.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-6.0.21.tgz",
|
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.3.2.tgz",
|
||||||
"integrity": "sha512-Kx4RrRfv0I+cOCIcsq/UA2aWcYLyXgW3aluAuW870OdXnbII6qg7RW28D+r9D76SHPxWFKwIKwmcucAG08Divg==",
|
"integrity": "sha512-CwAuQogVLcLR7O9e1eOgi3gtk4XX6cnaqevAxzJJpIOIyCnHiQ3cEGINVXyUUjUUipBlvK3sqz3NPGJ2ekLFDQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/react": "^0.19.1",
|
"@floating-ui/react": "^0.24.8",
|
||||||
"@mantine/styles": "6.0.21",
|
"clsx": "2.0.0",
|
||||||
"@mantine/utils": "6.0.21",
|
"react-number-format": "^5.3.1",
|
||||||
"@radix-ui/react-scroll-area": "1.0.2",
|
"react-remove-scroll": "^2.5.7",
|
||||||
"react-remove-scroll": "^2.5.5",
|
"react-textarea-autosize": "8.5.3",
|
||||||
"react-textarea-autosize": "8.3.4"
|
"type-fest": "^3.13.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@mantine/hooks": "6.0.21",
|
"@mantine/hooks": "7.3.2",
|
||||||
"react": ">=16.8.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": ">=16.8.0"
|
"react-dom": "^18.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mantine/core/node_modules/clsx": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mantine/core/node_modules/type-fest": {
|
||||||
|
"version": "3.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
|
||||||
|
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mantine/form": {
|
"node_modules/@mantine/form": {
|
||||||
"version": "6.0.21",
|
"version": "7.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@mantine/form/-/form-6.0.21.tgz",
|
"resolved": "https://registry.npmjs.org/@mantine/form/-/form-7.3.2.tgz",
|
||||||
"integrity": "sha512-d4tlxyZic7MSDnaPx/WliCX1sRFDkUd2nxx4MxxO2T4OSek0YDqTlSBCxeoveu60P+vrQQN5rbbsVsaOJBe4SQ==",
|
"integrity": "sha512-/qa1KQKVC46XWgIU190r3XM3Xld8Lsvz4L/an//TO67RnAGEdC5OCvr2JCb+fprZZi3YdxaKOkVNvP20W23qkg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"klona": "^2.0.5"
|
"klona": "^2.0.6"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=16.8.0"
|
"react": "^18.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mantine/hooks": {
|
"node_modules/@mantine/hooks": {
|
||||||
"version": "6.0.21",
|
"version": "7.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-6.0.21.tgz",
|
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.3.2.tgz",
|
||||||
"integrity": "sha512-sYwt5wai25W6VnqHbS5eamey30/HD5dNXaZuaVEAJ2i2bBv8C0cCiczygMDpAFiSYdXoSMRr/SZ2CrrPTzeNew==",
|
"integrity": "sha512-xgumuuI3PBWXff5N02HCI7PEy25mDEdyXDQklUYK93J6FKwpcosyZnGVitoUrV1gLtYYa9ZudeAWdhHuh/CpOg==",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=16.8.0"
|
"react": "^18.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mantine/modals": {
|
"node_modules/@mantine/modals": {
|
||||||
"version": "6.0.21",
|
"version": "7.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-6.0.21.tgz",
|
"resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-7.3.2.tgz",
|
||||||
"integrity": "sha512-Gx2D/ZHMUuYF197JKMWey4K9FeGP9rxYp4lmAEXUrjXiST2fEhLZOdiD75KuOHXd1/sYAU9NcNRo9wXrlF/gUA==",
|
"integrity": "sha512-vhpcp0Yqgm+K/vorDbuweTjzDO4pJaG2POc00cSTV3zJdsbeMAzVClovTuseJT+UO2lUdUP3RG1cInaZqSclhA==",
|
||||||
"dependencies": {
|
|
||||||
"@mantine/utils": "6.0.21"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@mantine/core": "6.0.21",
|
"@mantine/core": "7.3.2",
|
||||||
"@mantine/hooks": "6.0.21",
|
"@mantine/hooks": "7.3.2",
|
||||||
"react": ">=16.8.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": ">=16.8.0"
|
"react-dom": "^18.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mantine/notifications": {
|
"node_modules/@mantine/notifications": {
|
||||||
"version": "6.0.21",
|
"version": "7.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-6.0.21.tgz",
|
"resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.3.2.tgz",
|
||||||
"integrity": "sha512-qsrqxuJHK8b67sf9Pfk+xyhvpf9jMsivW8vchfnJfjv7yz1lLvezjytMFp4fMDoYhjHnDPOEc/YFockK4muhOw==",
|
"integrity": "sha512-XOzgm4pm4XszavVN0QUjN+IP0xiG2IochxJSz/FduTI0r3u1WxdpvDYlOvEJpHhtWvyqI8W8rx6cPJaD2HdAwQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/utils": "6.0.21",
|
"@mantine/store": "7.3.2",
|
||||||
"react-transition-group": "4.4.2"
|
"react-transition-group": "4.4.5"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@mantine/core": "6.0.21",
|
"@mantine/core": "7.3.2",
|
||||||
"@mantine/hooks": "6.0.21",
|
"@mantine/hooks": "7.3.2",
|
||||||
"react": ">=16.8.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": ">=16.8.0"
|
"react-dom": "^18.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mantine/spotlight": {
|
"node_modules/@mantine/spotlight": {
|
||||||
"version": "6.0.21",
|
"version": "7.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@mantine/spotlight/-/spotlight-6.0.21.tgz",
|
"resolved": "https://registry.npmjs.org/@mantine/spotlight/-/spotlight-7.3.2.tgz",
|
||||||
"integrity": "sha512-xJqF2Vpn8s6I4mSF+iCi7IzqL8iaqbvq0RcYlF1usLZYW2HrArX31s1r11DmzqM1PIuBQUhquW8jUXx/MZy3oA==",
|
"integrity": "sha512-wvrIj7ZZKoVwKFxgY+KvWilu1YYdkv8HDUzZzRxOlD9fjPyyMRgBxAdVkxA4sLbol4XoCpW83dNIiXDII4httw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/utils": "6.0.21"
|
"@mantine/store": "7.3.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@mantine/core": "6.0.21",
|
"@mantine/core": "7.3.2",
|
||||||
"@mantine/hooks": "6.0.21",
|
"@mantine/hooks": "7.3.2",
|
||||||
"react": ">=16.8.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": ">=16.8.0"
|
"react-dom": "^18.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mantine/styles": {
|
"node_modules/@mantine/store": {
|
||||||
"version": "6.0.21",
|
"version": "7.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@mantine/styles/-/styles-6.0.21.tgz",
|
"resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.3.2.tgz",
|
||||||
"integrity": "sha512-PVtL7XHUiD/B5/kZ/QvZOZZQQOj12QcRs3Q6nPoqaoPcOX5+S7bMZLMH0iLtcGq5OODYk0uxlvuJkOZGoPj8Mg==",
|
"integrity": "sha512-M1eWHzTRCeCFvrpFhXKIM9zblrlIT5/XrMue/fP2HrkA43dpkgq+ArnZkN3LhG9lWR/EKbRwQWDhDIvdLtfD7w==",
|
||||||
"dependencies": {
|
|
||||||
"clsx": "1.1.1",
|
|
||||||
"csstype": "3.0.9"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@emotion/react": ">=11.9.0",
|
"react": "^18.2.0"
|
||||||
"react": ">=16.8.0",
|
|
||||||
"react-dom": ">=16.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@mantine/styles/node_modules/csstype": {
|
|
||||||
"version": "3.0.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz",
|
|
||||||
"integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw=="
|
|
||||||
},
|
|
||||||
"node_modules/@mantine/utils": {
|
|
||||||
"version": "6.0.21",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mantine/utils/-/utils-6.0.21.tgz",
|
|
||||||
"integrity": "sha512-33RVDRop5jiWFao3HKd3Yp7A9mEq4HAJxJPTuYm1NkdqX6aTKOQK7wT8v8itVodBp+sb4cJK6ZVdD1UurK/txQ==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=16.8.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@messageformat/parser": {
|
"node_modules/@messageformat/parser": {
|
||||||
@@ -1676,137 +1681,6 @@
|
|||||||
"url": "https://opencollective.com/unts"
|
"url": "https://opencollective.com/unts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/number": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.13.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/primitive": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.13.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-compose-refs": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.13.10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-context": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.13.10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-direction": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.13.10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-presence": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.13.10",
|
|
||||||
"@radix-ui/react-compose-refs": "1.0.0",
|
|
||||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0",
|
|
||||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-primitive": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.13.10",
|
|
||||||
"@radix-ui/react-slot": "1.0.1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0",
|
|
||||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-scroll-area": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-k8VseTxI26kcKJaX0HPwkvlNBPTs56JRdYzcZ/vzrNUkDlvXBy8sMc7WvCpYzZkHgb+hd72VW9MqkqecGtuNgg==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.13.10",
|
|
||||||
"@radix-ui/number": "1.0.0",
|
|
||||||
"@radix-ui/primitive": "1.0.0",
|
|
||||||
"@radix-ui/react-compose-refs": "1.0.0",
|
|
||||||
"@radix-ui/react-context": "1.0.0",
|
|
||||||
"@radix-ui/react-direction": "1.0.0",
|
|
||||||
"@radix-ui/react-presence": "1.0.0",
|
|
||||||
"@radix-ui/react-primitive": "1.0.1",
|
|
||||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
|
||||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0",
|
|
||||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-slot": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.13.10",
|
|
||||||
"@radix-ui/react-compose-refs": "1.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-use-callback-ref": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.13.10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-use-layout-effect": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.13.10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@redocly/ajv": {
|
"node_modules/@redocly/ajv": {
|
||||||
"version": "8.11.0",
|
"version": "8.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.0.tgz",
|
||||||
@@ -6733,15 +6607,6 @@
|
|||||||
"integrity": "sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==",
|
"integrity": "sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/re-resizable": {
|
|
||||||
"version": "6.9.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.11.tgz",
|
|
||||||
"integrity": "sha512-a3hiLWck/NkmyLvGWUuvkAmN1VhwAz4yOhS6FdMTaxCUVN9joIWkT11wsO68coG/iEYuwn+p/7qAmfQzRhiPLQ==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
|
||||||
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||||
@@ -6797,6 +6662,19 @@
|
|||||||
"react": "^18.2.0"
|
"react": "^18.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-draggable": {
|
||||||
|
"version": "4.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz",
|
||||||
|
"integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==",
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^1.1.1",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.3.0",
|
||||||
|
"react-dom": ">= 16.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-ga4": {
|
"node_modules/react-ga4": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz",
|
||||||
@@ -6826,6 +6704,18 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/react-number-format": {
|
||||||
|
"version": "5.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.3.1.tgz",
|
||||||
|
"integrity": "sha512-qpYcQLauIeEhCZUZY9jXZnnroOtdy3jYaS1zQ3M1Sr6r/KMOBEIGNIb7eKT19g2N1wbYgFgvDzs19hw5TrB8XQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-redux": {
|
"node_modules/react-redux": {
|
||||||
"version": "9.0.4",
|
"version": "9.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.0.4.tgz",
|
||||||
@@ -6862,9 +6752,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-remove-scroll": {
|
"node_modules/react-remove-scroll": {
|
||||||
"version": "2.5.6",
|
"version": "2.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz",
|
||||||
"integrity": "sha512-bO856ad1uDYLefgArk559IzUNeQ6SWH4QnrevIUjH+GczV56giDfl3h0Idptf2oIKxQmd1p9BN25jleKodTALg==",
|
"integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react-remove-scroll-bar": "^2.3.4",
|
"react-remove-scroll-bar": "^2.3.4",
|
||||||
"react-style-singleton": "^2.2.1",
|
"react-style-singleton": "^2.2.1",
|
||||||
@@ -6979,11 +6869,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-textarea-autosize": {
|
"node_modules/react-textarea-autosize": {
|
||||||
"version": "8.3.4",
|
"version": "8.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz",
|
||||||
"integrity": "sha512-CdtmP8Dc19xL8/R6sWvtknD/eCXkQr30dtvC4VmGInhRsfF8X/ihXCq6+9l9qbxmKRiq407/7z5fxE7cVWQNgQ==",
|
"integrity": "sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.10.2",
|
"@babel/runtime": "^7.20.13",
|
||||||
"use-composed-ref": "^1.3.0",
|
"use-composed-ref": "^1.3.0",
|
||||||
"use-latest": "^1.2.1"
|
"use-latest": "^1.2.1"
|
||||||
},
|
},
|
||||||
@@ -6995,9 +6885,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-transition-group": {
|
"node_modules/react-transition-group": {
|
||||||
"version": "4.4.2",
|
"version": "4.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||||
"integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
|
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"dom-helpers": "^5.0.1",
|
"dom-helpers": "^5.0.1",
|
||||||
@@ -7875,9 +7765,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tabbable": {
|
"node_modules/tabbable": {
|
||||||
"version": "6.1.2",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
|
||||||
"integrity": "sha512-qCN98uP7i9z0fIS4amQ5zbGBOq+OSigYeGvPy7NDk8Y9yncqDZ9pRPgfsc2PJIVM9RrJj7GIfuRgmjoUU9zTHQ=="
|
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
|
||||||
},
|
},
|
||||||
"node_modules/text-table": {
|
"node_modules/text-table": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
@@ -8240,9 +8130,9 @@
|
|||||||
"integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="
|
"integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="
|
||||||
},
|
},
|
||||||
"node_modules/use-callback-ref": {
|
"node_modules/use-callback-ref": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz",
|
||||||
"integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==",
|
"integrity": "sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.0.0"
|
"tslib": "^2.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,13 +19,12 @@
|
|||||||
"@lingui/core": "^4.6.0",
|
"@lingui/core": "^4.6.0",
|
||||||
"@lingui/macro": "^4.6.0",
|
"@lingui/macro": "^4.6.0",
|
||||||
"@lingui/react": "^4.6.0",
|
"@lingui/react": "^4.6.0",
|
||||||
"@mantine/core": "^6.0.21",
|
"@mantine/core": "^7.3.2",
|
||||||
"@mantine/form": "^6.0.21",
|
"@mantine/form": "^7.3.2",
|
||||||
"@mantine/hooks": "^6.0.21",
|
"@mantine/hooks": "^7.3.2",
|
||||||
"@mantine/modals": "^6.0.21",
|
"@mantine/modals": "^7.3.2",
|
||||||
"@mantine/notifications": "^6.0.21",
|
"@mantine/notifications": "^7.3.2",
|
||||||
"@mantine/spotlight": "^6.0.21",
|
"@mantine/spotlight": "^7.3.2",
|
||||||
"@mantine/styles": "^6.0.21",
|
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@reduxjs/toolkit": "^2.0.1",
|
"@reduxjs/toolkit": "^2.0.1",
|
||||||
"axios": "^1.6.3",
|
"axios": "^1.6.3",
|
||||||
@@ -34,11 +33,11 @@
|
|||||||
"interweave": "^13.1.0",
|
"interweave": "^13.1.0",
|
||||||
"monaco-editor": "^0.45.0",
|
"monaco-editor": "^0.45.0",
|
||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
"re-resizable": "^6.9.11",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-async-hook": "^4.0.0",
|
"react-async-hook": "^4.0.0",
|
||||||
"react-contexify": "^6.0.0",
|
"react-contexify": "^6.0.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-draggable": "^4.4.6",
|
||||||
"react-ga4": "^2.1.0",
|
"react-ga4": "^2.1.0",
|
||||||
"react-icons": "^4.12.0",
|
"react-icons": "^4.12.0",
|
||||||
"react-infinite-scroller": "^1.2.6",
|
"react-infinite-scroller": "^1.2.6",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { i18n } from "@lingui/core"
|
import { i18n } from "@lingui/core"
|
||||||
import { I18nProvider } from "@lingui/react"
|
import { I18nProvider } from "@lingui/react"
|
||||||
import { type ColorScheme, ColorSchemeProvider, MantineProvider } from "@mantine/core"
|
import { MantineProvider } from "@mantine/core"
|
||||||
import { useColorScheme } from "@mantine/hooks"
|
|
||||||
import { ModalsProvider } from "@mantine/modals"
|
import { ModalsProvider } from "@mantine/modals"
|
||||||
import { Notifications } from "@mantine/notifications"
|
import { Notifications } from "@mantine/notifications"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
@@ -33,31 +32,37 @@ import React, { useEffect } from "react"
|
|||||||
import ReactGA from "react-ga4"
|
import ReactGA from "react-ga4"
|
||||||
import { HashRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom"
|
import { HashRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom"
|
||||||
import Tinycon from "tinycon"
|
import Tinycon from "tinycon"
|
||||||
import useLocalStorage from "use-local-storage"
|
|
||||||
|
|
||||||
function Providers(props: { children: React.ReactNode }) {
|
function Providers(props: { children: React.ReactNode }) {
|
||||||
const preferredColorScheme = useColorScheme()
|
|
||||||
const [colorScheme, setColorScheme] = useLocalStorage<ColorScheme>("color-scheme", preferredColorScheme)
|
|
||||||
const toggleColorScheme = (value?: ColorScheme) => setColorScheme(value ?? (colorScheme === "dark" ? "light" : "dark"))
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<I18nProvider i18n={i18n}>
|
<I18nProvider i18n={i18n}>
|
||||||
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}>
|
<MantineProvider
|
||||||
<MantineProvider
|
theme={{
|
||||||
withGlobalStyles
|
primaryColor: "orange",
|
||||||
withNormalizeCSS
|
fontFamily: "Open Sans",
|
||||||
theme={{
|
colors: {
|
||||||
primaryColor: "orange",
|
// keep using dark colors from mantine v6
|
||||||
colorScheme,
|
// https://v6.mantine.dev/theming/colors/#default-colors
|
||||||
fontFamily: "Open Sans",
|
dark: [
|
||||||
}}
|
"#C1C2C5",
|
||||||
>
|
"#A6A7AB",
|
||||||
<ModalsProvider>
|
"#909296",
|
||||||
<Notifications position="bottom-right" zIndex={9999} />
|
"#5c5f66",
|
||||||
<ErrorBoundary>{props.children}</ErrorBoundary>
|
"#373A40",
|
||||||
</ModalsProvider>
|
"#2C2E33",
|
||||||
</MantineProvider>
|
"#25262b",
|
||||||
</ColorSchemeProvider>
|
"#1A1B1E",
|
||||||
|
"#141517",
|
||||||
|
"#101113",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalsProvider>
|
||||||
|
<Notifications position="bottom-right" zIndex={9999} />
|
||||||
|
<ErrorBoundary>{props.children}</ErrorBoundary>
|
||||||
|
</ModalsProvider>
|
||||||
|
</MantineProvider>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -77,7 +82,10 @@ function AppRoutes() {
|
|||||||
<Route path="register" element={<RegistrationPage />} />
|
<Route path="register" element={<RegistrationPage />} />
|
||||||
<Route path="passwordRecovery" element={<PasswordRecoveryPage />} />
|
<Route path="passwordRecovery" element={<PasswordRecoveryPage />} />
|
||||||
<Route path="api" element={<ApiDocumentationPage />} />
|
<Route path="api" element={<ApiDocumentationPage />} />
|
||||||
<Route path="app" element={<Layout header={<Header />} sidebar={<Tree />} sidebarWidth={sidebarVisible ? sidebarWidth : 0} />}>
|
<Route
|
||||||
|
path="app"
|
||||||
|
element={<Layout header={<Header />} sidebar={<Tree />} sidebarWidth={sidebarWidth} sidebarVisible={sidebarVisible} />}
|
||||||
|
>
|
||||||
<Route path="category">
|
<Route path="category">
|
||||||
<Route path=":id" element={<FeedEntriesPage sourceType="category" />} />
|
<Route path=":id" element={<FeedEntriesPage sourceType="category" />} />
|
||||||
<Route path=":id/details" element={<CategoryDetailsPage />} />
|
<Route path=":id/details" element={<CategoryDetailsPage />} />
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { t } from "@lingui/macro"
|
import { t } from "@lingui/macro"
|
||||||
import { DEFAULT_THEME } from "@mantine/core"
|
|
||||||
import { type IconType } from "react-icons"
|
import { type IconType } from "react-icons"
|
||||||
import { FaAt } from "react-icons/fa"
|
import { FaAt } from "react-icons/fa"
|
||||||
import { SiBuffer, SiFacebook, SiGmail, SiInstapaper, SiPocket, SiTumblr, SiTwitter } from "react-icons/si"
|
import { SiBuffer, SiFacebook, SiGmail, SiInstapaper, SiPocket, SiTumblr, SiTwitter } from "react-icons/si"
|
||||||
@@ -86,7 +85,8 @@ export const Constants = {
|
|||||||
categories,
|
categories,
|
||||||
sharing,
|
sharing,
|
||||||
layout: {
|
layout: {
|
||||||
mobileBreakpoint: DEFAULT_THEME.breakpoints.md,
|
mobileBreakpoint: 992,
|
||||||
|
mobileBreakpointName: "md",
|
||||||
headerHeight: 60,
|
headerHeight: 60,
|
||||||
entryMaxWidth: 650,
|
entryMaxWidth: 650,
|
||||||
isTopVisible: (div: HTMLElement) => div.getBoundingClientRect().top >= Constants.layout.headerHeight,
|
isTopVisible: (div: HTMLElement) => div.getBoundingClientRect().top >= Constants.layout.headerHeight,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ActionIcon, Button, Tooltip, useMantineTheme } from "@mantine/core"
|
import { ActionIcon, Button, type ButtonVariant, Tooltip, useMantineTheme } from "@mantine/core"
|
||||||
import { type ActionIconProps } from "@mantine/core/lib/ActionIcon/ActionIcon"
|
import { type ActionIconVariant } from "@mantine/core/lib/components/ActionIcon/ActionIcon"
|
||||||
import { type ButtonProps } from "@mantine/core/lib/Button/Button"
|
|
||||||
import { useActionButton } from "hooks/useActionButton"
|
import { useActionButton } from "hooks/useActionButton"
|
||||||
import { forwardRef, type MouseEventHandler, type ReactNode } from "react"
|
import { forwardRef, type MouseEventHandler, type ReactNode } from "react"
|
||||||
|
|
||||||
@@ -9,7 +8,7 @@ interface ActionButtonProps {
|
|||||||
icon?: ReactNode
|
icon?: ReactNode
|
||||||
label: ReactNode
|
label: ReactNode
|
||||||
onClick?: MouseEventHandler
|
onClick?: MouseEventHandler
|
||||||
variant?: ActionIconProps["variant"] & ButtonProps["variant"]
|
variant?: ActionIconVariant & ButtonVariant
|
||||||
hideLabelOnDesktop?: boolean
|
hideLabelOnDesktop?: boolean
|
||||||
showLabelOnMobile?: boolean
|
showLabelOnMobile?: boolean
|
||||||
}
|
}
|
||||||
@@ -29,7 +28,7 @@ export const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>((pr
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<Button ref={ref} variant={variant} size="xs" className={props.className} leftIcon={props.icon} onClick={props.onClick}>
|
<Button ref={ref} variant={variant} size="xs" className={props.className} leftSection={props.icon} onClick={props.onClick}>
|
||||||
{props.label}
|
{props.label}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function AnnouncementDialog() {
|
|||||||
return (
|
return (
|
||||||
<Dialog opened={opened} withCloseButton onClose={onClosed} size="xl" radius="md">
|
<Dialog opened={opened} withCloseButton onClose={onClosed} size="xl" radius="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text weight="bold">
|
<Text fw="bold">
|
||||||
<Trans>Announcement</Trans>
|
<Trans>Announcement</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Box, Center, type MantineTheme, useMantineTheme } from "@mantine/core"
|
import { Box, Center, type MantineTheme, useMantineTheme } from "@mantine/core"
|
||||||
|
import { useColorScheme } from "hooks/useColorScheme"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { TbPhoto } from "react-icons/tb"
|
import { TbPhoto } from "react-icons/tb"
|
||||||
import { tss } from "tss"
|
import { tss } from "tss"
|
||||||
@@ -19,6 +20,7 @@ interface ImageWithPlaceholderWhileLoadingProps {
|
|||||||
const useStyles = tss
|
const useStyles = tss
|
||||||
.withParams<{
|
.withParams<{
|
||||||
theme: MantineTheme
|
theme: MantineTheme
|
||||||
|
colorScheme: "light" | "dark"
|
||||||
placeholderWidth?: number
|
placeholderWidth?: number
|
||||||
placeholderHeight?: number
|
placeholderHeight?: number
|
||||||
placeholderBackgroundColor?: string
|
placeholderBackgroundColor?: string
|
||||||
@@ -31,13 +33,14 @@ const useStyles = tss
|
|||||||
maxWidth: "100%",
|
maxWidth: "100%",
|
||||||
color:
|
color:
|
||||||
props.placeholderIconColor ??
|
props.placeholderIconColor ??
|
||||||
props.theme.fn.variant({
|
props.theme.variantColorResolver({
|
||||||
|
theme: props.theme,
|
||||||
color: props.theme.primaryColor,
|
color: props.theme.primaryColor,
|
||||||
variant: "subtle",
|
variant: "subtle",
|
||||||
}).color,
|
}).color,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
props.placeholderBackgroundColor ??
|
props.placeholderBackgroundColor ??
|
||||||
(props.theme.colorScheme === "dark" ? props.theme.colors.dark[5] : props.theme.colors.gray[1]),
|
(props.colorScheme === "dark" ? props.theme.colors.dark[5] : props.theme.colors.gray[1]),
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -54,8 +57,10 @@ export function ImageWithPlaceholderWhileLoading({
|
|||||||
width,
|
width,
|
||||||
}: ImageWithPlaceholderWhileLoadingProps) {
|
}: ImageWithPlaceholderWhileLoadingProps) {
|
||||||
const theme = useMantineTheme()
|
const theme = useMantineTheme()
|
||||||
|
const colorScheme = useColorScheme()
|
||||||
const { classes } = useStyles({
|
const { classes } = useStyles({
|
||||||
theme,
|
theme,
|
||||||
|
colorScheme,
|
||||||
placeholderWidth,
|
placeholderWidth,
|
||||||
placeholderHeight,
|
placeholderHeight,
|
||||||
placeholderBackgroundColor,
|
placeholderBackgroundColor,
|
||||||
|
|||||||
@@ -4,64 +4,64 @@ import { Constants } from "app/constants"
|
|||||||
|
|
||||||
export function KeyboardShortcutsHelp() {
|
export function KeyboardShortcutsHelp() {
|
||||||
return (
|
return (
|
||||||
<Stack spacing="xs">
|
<Stack gap="xs">
|
||||||
<Table striped highlightOnHover>
|
<Table striped highlightOnHover>
|
||||||
<tbody>
|
<Table.Tbody>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Refresh</Trans>
|
<Trans>Refresh</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>R</Kbd>
|
<Kbd>R</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Open next entry</Trans>
|
<Trans>Open next entry</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>J</Kbd>
|
<Kbd>J</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Open previous entry</Trans>
|
<Trans>Open previous entry</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>K</Kbd>
|
<Kbd>K</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Set focus on next entry without opening it</Trans>
|
<Trans>Set focus on next entry without opening it</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>N</Kbd>
|
<Kbd>N</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Set focus on previous entry without opening it</Trans>
|
<Trans>Set focus on previous entry without opening it</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>P</Kbd>
|
<Kbd>P</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Move the page down</Trans>
|
<Trans>Move the page down</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>
|
<Kbd>
|
||||||
<Trans>Space</Trans>
|
<Trans>Space</Trans>
|
||||||
</Kbd>
|
</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Move the page up</Trans>
|
<Trans>Move the page up</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>
|
<Kbd>
|
||||||
<Trans>Shift</Trans>
|
<Trans>Shift</Trans>
|
||||||
</Kbd>
|
</Kbd>
|
||||||
@@ -69,85 +69,85 @@ export function KeyboardShortcutsHelp() {
|
|||||||
<Kbd>
|
<Kbd>
|
||||||
<Trans>Space</Trans>
|
<Trans>Space</Trans>
|
||||||
</Kbd>
|
</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Open/close current entry</Trans>
|
<Trans>Open/close current entry</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>O</Kbd>
|
<Kbd>O</Kbd>
|
||||||
<span>, </span>
|
<span>, </span>
|
||||||
<Kbd>
|
<Kbd>
|
||||||
<Trans>Enter</Trans>
|
<Trans>Enter</Trans>
|
||||||
</Kbd>
|
</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Open current entry in a new tab</Trans>
|
<Trans>Open current entry in a new tab</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>V</Kbd>
|
<Kbd>V</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Open current entry in a new tab in the background</Trans>
|
<Trans>Open current entry in a new tab in the background</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>B</Kbd>
|
<Kbd>B</Kbd>
|
||||||
<span>*, </span>
|
<span>*, </span>
|
||||||
<Kbd>
|
<Kbd>
|
||||||
<Trans>Middle click</Trans>
|
<Trans>Middle click</Trans>
|
||||||
</Kbd>
|
</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Toggle read status of current entry</Trans>
|
<Trans>Toggle read status of current entry</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>M</Kbd>
|
<Kbd>M</Kbd>
|
||||||
<span>, </span>
|
<span>, </span>
|
||||||
<Trans>Swipe header to the right</Trans>
|
<Trans>Swipe header to the right</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Toggle starred status of current entry</Trans>
|
<Trans>Toggle starred status of current entry</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>S</Kbd>
|
<Kbd>S</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Mark all entries as read</Trans>
|
<Trans>Mark all entries as read</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>
|
<Kbd>
|
||||||
<Trans>Shift</Trans>
|
<Trans>Shift</Trans>
|
||||||
</Kbd>
|
</Kbd>
|
||||||
<span> + </span>
|
<span> + </span>
|
||||||
<Kbd>A</Kbd>
|
<Kbd>A</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Go to the All view</Trans>
|
<Trans>Go to the All view</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>G</Kbd>
|
<Kbd>G</Kbd>
|
||||||
<span> </span>
|
<span> </span>
|
||||||
<Kbd>A</Kbd>
|
<Kbd>A</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Navigate to a subscription by entering its name</Trans>
|
<Trans>Navigate to a subscription by entering its name</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>
|
<Kbd>
|
||||||
<Trans>Ctrl</Trans>
|
<Trans>Ctrl</Trans>
|
||||||
</Kbd>
|
</Kbd>
|
||||||
@@ -157,23 +157,23 @@ export function KeyboardShortcutsHelp() {
|
|||||||
<Kbd>G</Kbd>
|
<Kbd>G</Kbd>
|
||||||
<span> </span>
|
<span> </span>
|
||||||
<Kbd>U</Kbd>
|
<Kbd>U</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Show entry menu (desktop)</Trans>
|
<Trans>Show entry menu (desktop)</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>
|
<Kbd>
|
||||||
<Trans>Right click</Trans>
|
<Trans>Right click</Trans>
|
||||||
</Kbd>
|
</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Show native menu (desktop)</Trans>
|
<Trans>Show native menu (desktop)</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>
|
<Kbd>
|
||||||
<Trans>Shift</Trans>
|
<Trans>Shift</Trans>
|
||||||
</Kbd>
|
</Kbd>
|
||||||
@@ -181,35 +181,35 @@ export function KeyboardShortcutsHelp() {
|
|||||||
<Kbd>
|
<Kbd>
|
||||||
<Trans>Right click</Trans>
|
<Trans>Right click</Trans>
|
||||||
</Kbd>
|
</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Show entry menu (mobile)</Trans>
|
<Trans>Show entry menu (mobile)</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>
|
<Kbd>
|
||||||
<Trans>Long press</Trans>
|
<Trans>Long press</Trans>
|
||||||
</Kbd>
|
</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Toggle sidebar</Trans>
|
<Trans>Toggle sidebar</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>F</Kbd>
|
<Kbd>F</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Trans>Show keyboard shortcut help</Trans>
|
<Trans>Show keyboard shortcut help</Trans>
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Kbd>?</Kbd>
|
<Kbd>?</Kbd>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
</tbody>
|
</Table.Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
<Box>
|
<Box>
|
||||||
<span>* </span>
|
<span>* </span>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Center, Loader as MantineLoader } from "@mantine/core"
|
|||||||
export function Loader() {
|
export function Loader() {
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center>
|
||||||
<MantineLoader size="xl" variant="bars" />
|
<MantineLoader size="lg" type="bars" />
|
||||||
</Center>
|
</Center>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ export interface LogoProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Logo(props: LogoProps) {
|
export function Logo(props: LogoProps) {
|
||||||
return <Image src={logo} width={props.size} />
|
return <Image src={logo} w={props.size} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function UserEdit(props: UserEditProps) {
|
|||||||
<Button variant="default" onClick={props.onCancel}>
|
<Button variant="default" onClick={props.onCancel}>
|
||||||
<Trans>Cancel</Trans>
|
<Trans>Cancel</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveUser.loading}>
|
<Button type="submit" leftSection={<TbDeviceFloppy size={16} />} loading={saveUser.loading}>
|
||||||
<Trans>Save</Trans>
|
<Trans>Save</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useMantineTheme } from "@mantine/core"
|
|
||||||
import { Loader } from "components/Loader"
|
import { Loader } from "components/Loader"
|
||||||
|
import { useColorScheme } from "hooks/useColorScheme"
|
||||||
import { useAsync } from "react-async-hook"
|
import { useAsync } from "react-async-hook"
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
@@ -32,8 +32,8 @@ interface RichCodeEditorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function RichCodeEditor(props: RichCodeEditorProps) {
|
function RichCodeEditor(props: RichCodeEditorProps) {
|
||||||
const theme = useMantineTheme()
|
const colorScheme = useColorScheme()
|
||||||
const editorTheme = theme.colorScheme === "dark" ? "vs-dark" : "light"
|
const editorTheme = colorScheme === "dark" ? "vs-dark" : "light"
|
||||||
|
|
||||||
const { result: Editor } = useAsync(init, [])
|
const { result: Editor } = useAsync(init, [])
|
||||||
if (!Editor) return <Loader />
|
if (!Editor) return <Loader />
|
||||||
|
|||||||
11
commafeed-client/src/components/content/BasicHtmlStyles.tsx
Normal file
11
commafeed-client/src/components/content/BasicHtmlStyles.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { TypographyStylesProvider } from "@mantine/core"
|
||||||
|
import { type ReactNode } from "react"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component is used to provide basic styles to html typography elements.
|
||||||
|
*
|
||||||
|
* see https://mantine.dev/core/typography-styles-provider/
|
||||||
|
*/
|
||||||
|
export const BasicHtmlStyles = (props: { children: ReactNode }) => {
|
||||||
|
return <TypographyStylesProvider pl={0}>{props.children}</TypographyStylesProvider>
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Box, type MantineTheme, Mark, TypographyStylesProvider, useMantineTheme } from "@mantine/core"
|
import { Box, type MantineTheme, Mark, useMantineTheme } from "@mantine/core"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { calculatePlaceholderSize } from "app/utils"
|
import { calculatePlaceholderSize } from "app/utils"
|
||||||
|
import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
|
||||||
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
|
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
|
||||||
import escapeStringRegexp from "escape-string-regexp"
|
import escapeStringRegexp from "escape-string-regexp"
|
||||||
import { type ChildrenNode, Interweave, Matcher, type MatchResponse, type Node, type TransformCallback } from "interweave"
|
import { type ChildrenNode, Interweave, Matcher, type MatchResponse, type Node, type TransformCallback } from "interweave"
|
||||||
@@ -21,7 +22,11 @@ const useStyles = tss
|
|||||||
// break long links or long words
|
// break long links or long words
|
||||||
overflowWrap: "anywhere",
|
overflowWrap: "anywhere",
|
||||||
"& a": {
|
"& a": {
|
||||||
color: theme.fn.variant({ color: theme.primaryColor, variant: "subtle" }).color,
|
color: theme.variantColorResolver({
|
||||||
|
theme,
|
||||||
|
color: theme.primaryColor,
|
||||||
|
variant: "subtle",
|
||||||
|
}).color,
|
||||||
},
|
},
|
||||||
"& iframe": {
|
"& iframe": {
|
||||||
maxWidth: "100%",
|
maxWidth: "100%",
|
||||||
@@ -96,11 +101,11 @@ const Content = React.memo((props: ContentProps) => {
|
|||||||
const matchers = props.highlight ? [new HighlightMatcher(props.highlight)] : []
|
const matchers = props.highlight ? [new HighlightMatcher(props.highlight)] : []
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypographyStylesProvider>
|
<BasicHtmlStyles>
|
||||||
<Box className={classes.content}>
|
<Box className={classes.content}>
|
||||||
<Interweave content={props.content} transform={transform} matchers={matchers} />
|
<Interweave content={props.content} transform={transform} matchers={matchers} />
|
||||||
</Box>
|
</Box>
|
||||||
</TypographyStylesProvider>
|
</BasicHtmlStyles>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
Content.displayName = "Content"
|
Content.displayName = "Content"
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { TypographyStylesProvider } from "@mantine/core"
|
import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
|
||||||
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
|
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
|
||||||
|
|
||||||
export function Enclosure(props: { enclosureType: string; enclosureUrl: string }) {
|
export function Enclosure(props: { enclosureType: string; enclosureUrl: string }) {
|
||||||
const hasVideo = props.enclosureType && props.enclosureType.indexOf("video") === 0
|
const hasVideo = props.enclosureType?.startsWith("video")
|
||||||
const hasAudio = props.enclosureType && props.enclosureType.indexOf("audio") === 0
|
const hasAudio = props.enclosureType?.startsWith("audio")
|
||||||
const hasImage = props.enclosureType && props.enclosureType.indexOf("image") === 0
|
const hasImage = props.enclosureType?.startsWith("image")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypographyStylesProvider>
|
<BasicHtmlStyles>
|
||||||
{hasVideo && (
|
{hasVideo && (
|
||||||
<video controls>
|
<video controls>
|
||||||
<source src={props.enclosureUrl} type={props.enclosureType} />
|
<source src={props.enclosureUrl} type={props.enclosureType} />
|
||||||
@@ -19,6 +19,6 @@ export function Enclosure(props: { enclosureType: string; enclosureUrl: string }
|
|||||||
</audio>
|
</audio>
|
||||||
)}
|
)}
|
||||||
{hasImage && <ImageWithPlaceholderWhileLoading src={props.enclosureUrl} alt="enclosure" />}
|
{hasImage && <ImageWithPlaceholderWhileLoading src={props.enclosureUrl} alt="enclosure" />}
|
||||||
</TypographyStylesProvider>
|
</BasicHtmlStyles>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Box, Divider, type MantineTheme, Paper, useMantineTheme } from "@mantine/core"
|
import { Box, Divider, type MantineRadius, type MantineSpacing, type MantineTheme, Paper, useMantineTheme } from "@mantine/core"
|
||||||
import { type MantineNumberSize } from "@mantine/styles"
|
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { type Entry, type ViewMode } from "app/types"
|
import { type Entry, type ViewMode } from "app/types"
|
||||||
|
import { useColorScheme } from "hooks/useColorScheme"
|
||||||
import { useViewMode } from "hooks/useViewMode"
|
import { useViewMode } from "hooks/useViewMode"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { useSwipeable } from "react-swipeable"
|
import { useSwipeable } from "react-swipeable"
|
||||||
@@ -27,6 +27,7 @@ interface FeedEntryProps {
|
|||||||
const useStyles = tss
|
const useStyles = tss
|
||||||
.withParams<{
|
.withParams<{
|
||||||
theme: MantineTheme
|
theme: MantineTheme
|
||||||
|
colorScheme: "light" | "dark"
|
||||||
read: boolean
|
read: boolean
|
||||||
expanded: boolean
|
expanded: boolean
|
||||||
viewMode: ViewMode
|
viewMode: ViewMode
|
||||||
@@ -34,9 +35,9 @@ const useStyles = tss
|
|||||||
showSelectionIndicator: boolean
|
showSelectionIndicator: boolean
|
||||||
maxWidth?: number
|
maxWidth?: number
|
||||||
}>()
|
}>()
|
||||||
.create(({ theme, read, expanded, viewMode, rtl, showSelectionIndicator, maxWidth }) => {
|
.create(({ theme, colorScheme, read, expanded, viewMode, rtl, showSelectionIndicator, maxWidth }) => {
|
||||||
let backgroundColor
|
let backgroundColor
|
||||||
if (theme.colorScheme === "dark") {
|
if (colorScheme === "dark") {
|
||||||
backgroundColor = read ? "inherit" : theme.colors.dark[5]
|
backgroundColor = read ? "inherit" : theme.colors.dark[5]
|
||||||
} else {
|
} else {
|
||||||
backgroundColor = read && !expanded ? theme.colors.gray[0] : "inherit"
|
backgroundColor = read && !expanded ? theme.colors.gray[0] : "inherit"
|
||||||
@@ -58,12 +59,12 @@ const useStyles = tss
|
|||||||
|
|
||||||
let backgroundHoverColor = backgroundColor
|
let backgroundHoverColor = backgroundColor
|
||||||
if (!expanded && !read) {
|
if (!expanded && !read) {
|
||||||
backgroundHoverColor = theme.colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[1]
|
backgroundHoverColor = colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
let paperBorderLeftColor
|
let paperBorderLeftColor
|
||||||
if (showSelectionIndicator) {
|
if (showSelectionIndicator) {
|
||||||
const borderLeftColor = theme.colorScheme === "dark" ? theme.colors.orange[4] : theme.colors.orange[6]
|
const borderLeftColor = colorScheme === "dark" ? theme.colors[theme.primaryColor][4] : theme.colors[theme.primaryColor][6]
|
||||||
paperBorderLeftColor = `${borderLeftColor} !important`
|
paperBorderLeftColor = `${borderLeftColor} !important`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ const useStyles = tss
|
|||||||
borderLeftColor: paperBorderLeftColor,
|
borderLeftColor: paperBorderLeftColor,
|
||||||
marginTop: marginY,
|
marginTop: marginY,
|
||||||
marginBottom: marginY,
|
marginBottom: marginY,
|
||||||
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
|
[`@media (max-width: ${Constants.layout.mobileBreakpoint}px)`]: {
|
||||||
marginTop: mobileMarginY,
|
marginTop: mobileMarginY,
|
||||||
marginBottom: mobileMarginY,
|
marginBottom: mobileMarginY,
|
||||||
},
|
},
|
||||||
@@ -96,9 +97,11 @@ const useStyles = tss
|
|||||||
|
|
||||||
export function FeedEntry(props: FeedEntryProps) {
|
export function FeedEntry(props: FeedEntryProps) {
|
||||||
const theme = useMantineTheme()
|
const theme = useMantineTheme()
|
||||||
|
const colorScheme = useColorScheme()
|
||||||
const { viewMode } = useViewMode()
|
const { viewMode } = useViewMode()
|
||||||
const { classes, cx } = useStyles({
|
const { classes, cx } = useStyles({
|
||||||
theme,
|
theme,
|
||||||
|
colorScheme,
|
||||||
read: props.entry.read,
|
read: props.entry.read,
|
||||||
expanded: props.expanded,
|
expanded: props.expanded,
|
||||||
viewMode,
|
viewMode,
|
||||||
@@ -111,17 +114,17 @@ export function FeedEntry(props: FeedEntryProps) {
|
|||||||
onSwipedRight: props.onSwipedRight,
|
onSwipedRight: props.onSwipedRight,
|
||||||
})
|
})
|
||||||
|
|
||||||
let paddingX: MantineNumberSize = "xs"
|
let paddingX: MantineSpacing = "xs"
|
||||||
if (viewMode === "title" || viewMode === "cozy") paddingX = 6
|
if (viewMode === "title" || viewMode === "cozy") paddingX = 6
|
||||||
|
|
||||||
let paddingY: MantineNumberSize = "xs"
|
let paddingY: MantineSpacing = "xs"
|
||||||
if (viewMode === "title") {
|
if (viewMode === "title") {
|
||||||
paddingY = 4
|
paddingY = 4
|
||||||
} else if (viewMode === "cozy") {
|
} else if (viewMode === "cozy") {
|
||||||
paddingY = 8
|
paddingY = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
let borderRadius: MantineNumberSize = "sm"
|
let borderRadius: MantineRadius = "sm"
|
||||||
if (viewMode === "title") {
|
if (viewMode === "title") {
|
||||||
borderRadius = 0
|
borderRadius = 0
|
||||||
} else if (viewMode === "cozy") {
|
} else if (viewMode === "cozy") {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Box, type MantineTheme, Text, useMantineTheme } from "@mantine/core"
|
import { Box, Text } from "@mantine/core"
|
||||||
import { type Entry } from "app/types"
|
import { type Entry } from "app/types"
|
||||||
import { RelativeDate } from "components/RelativeDate"
|
import { RelativeDate } from "components/RelativeDate"
|
||||||
import { OnDesktop } from "components/responsive/OnDesktop"
|
import { OnDesktop } from "components/responsive/OnDesktop"
|
||||||
|
import { useColorScheme } from "hooks/useColorScheme"
|
||||||
import { tss } from "tss"
|
import { tss } from "tss"
|
||||||
import { FeedEntryTitle } from "./FeedEntryTitle"
|
import { FeedEntryTitle } from "./FeedEntryTitle"
|
||||||
import { FeedFavicon } from "./FeedFavicon"
|
import { FeedFavicon } from "./FeedFavicon"
|
||||||
@@ -12,10 +13,10 @@ export interface FeedEntryHeaderProps {
|
|||||||
|
|
||||||
const useStyles = tss
|
const useStyles = tss
|
||||||
.withParams<{
|
.withParams<{
|
||||||
theme: MantineTheme
|
colorScheme: "light" | "dark"
|
||||||
read: boolean
|
read: boolean
|
||||||
}>()
|
}>()
|
||||||
.create(({ read, theme }) => ({
|
.create(({ colorScheme, read }) => ({
|
||||||
wrapper: {
|
wrapper: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -23,7 +24,7 @@ const useStyles = tss
|
|||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
fontWeight: theme.colorScheme === "light" && !read ? "bold" : "inherit",
|
fontWeight: colorScheme === "light" && !read ? "bold" : "inherit",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
@@ -41,9 +42,9 @@ const useStyles = tss
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) {
|
export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) {
|
||||||
const theme = useMantineTheme()
|
const colorScheme = useColorScheme()
|
||||||
const { classes } = useStyles({
|
const { classes } = useStyles({
|
||||||
theme,
|
colorScheme,
|
||||||
read: props.entry.read,
|
read: props.entry.read,
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
@@ -52,7 +53,7 @@ export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) {
|
|||||||
<FeedFavicon url={props.entry.iconUrl} />
|
<FeedFavicon url={props.entry.iconUrl} />
|
||||||
</Box>
|
</Box>
|
||||||
<OnDesktop>
|
<OnDesktop>
|
||||||
<Text color="dimmed" className={classes.feedName}>
|
<Text c="dimmed" className={classes.feedName}>
|
||||||
{props.entry.feedName}
|
{props.entry.feedName}
|
||||||
</Text>
|
</Text>
|
||||||
</OnDesktop>
|
</OnDesktop>
|
||||||
@@ -60,7 +61,7 @@ export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) {
|
|||||||
<FeedEntryTitle entry={props.entry} />
|
<FeedEntryTitle entry={props.entry} />
|
||||||
</Box>
|
</Box>
|
||||||
<OnDesktop>
|
<OnDesktop>
|
||||||
<Text color="dimmed" className={classes.date}>
|
<Text c="dimmed" className={classes.date}>
|
||||||
<RelativeDate date={props.entry.date} />
|
<RelativeDate date={props.entry.date} />
|
||||||
</Text>
|
</Text>
|
||||||
</OnDesktop>
|
</OnDesktop>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useAppDispatch, useAppSelector } from "app/store"
|
|||||||
import { type Entry } from "app/types"
|
import { type Entry } from "app/types"
|
||||||
import { truncate } from "app/utils"
|
import { truncate } from "app/utils"
|
||||||
import { useBrowserExtension } from "hooks/useBrowserExtension"
|
import { useBrowserExtension } from "hooks/useBrowserExtension"
|
||||||
|
import { useColorScheme } from "hooks/useColorScheme"
|
||||||
import { Item, Menu, Separator } from "react-contexify"
|
import { Item, Menu, Separator } from "react-contexify"
|
||||||
import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbRss, TbStar, TbStarOff } from "react-icons/tb"
|
import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbRss, TbStar, TbStarOff } from "react-icons/tb"
|
||||||
import { tss } from "tss"
|
import { tss } from "tss"
|
||||||
@@ -19,30 +20,31 @@ const iconSize = 16
|
|||||||
const useStyles = tss
|
const useStyles = tss
|
||||||
.withParams<{
|
.withParams<{
|
||||||
theme: MantineTheme
|
theme: MantineTheme
|
||||||
|
colorScheme: "light" | "dark"
|
||||||
}>()
|
}>()
|
||||||
.create(({ theme }) => ({
|
.create(({ theme, colorScheme }) => ({
|
||||||
menu: {
|
menu: {
|
||||||
// apply mantine theme from MenuItem.styles.ts
|
// apply mantine theme from MenuItem.styles.ts
|
||||||
fontSize: theme.fontSizes.sm,
|
fontSize: theme.fontSizes.sm,
|
||||||
"--contexify-item-color": `${theme.colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
|
"--contexify-item-color": `${colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
|
||||||
"--contexify-activeItem-color": `${theme.colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
|
"--contexify-activeItem-color": `${colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
|
||||||
"--contexify-activeItem-bgColor": `${
|
"--contexify-activeItem-bgColor": `${colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[1]} !important`,
|
||||||
theme.colorScheme === "dark" ? theme.fn.rgba(theme.colors.dark[3], 0.35) : theme.colors.gray[1]
|
|
||||||
} !important`,
|
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
|
export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
|
||||||
const theme = useMantineTheme()
|
const theme = useMantineTheme()
|
||||||
|
const colorScheme = useColorScheme()
|
||||||
const { classes } = useStyles({
|
const { classes } = useStyles({
|
||||||
theme,
|
theme,
|
||||||
|
colorScheme,
|
||||||
})
|
})
|
||||||
const sourceType = useAppSelector(state => state.entries.source.type)
|
const sourceType = useAppSelector(state => state.entries.source.type)
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { openLinkInBackgroundTab } = useBrowserExtension()
|
const { openLinkInBackgroundTab } = useBrowserExtension()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu id={Constants.dom.entryContextMenuId(props.entry)} theme={theme.colorScheme} animation={false} className={classes.menu}>
|
<Menu id={Constants.dom.entryContextMenuId(props.entry)} theme={colorScheme} animation={false} className={classes.menu}>
|
||||||
<Item
|
<Item
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open(props.entry.url, "_blank", "noreferrer")
|
window.open(props.entry.url, "_blank", "noreferrer")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { t, Trans } from "@lingui/macro"
|
import { t, Trans } from "@lingui/macro"
|
||||||
import { Group, Indicator, MultiSelect, Popover } from "@mantine/core"
|
import { Group, Indicator, Popover, TagsInput } from "@mantine/core"
|
||||||
import { markEntriesUpToEntry, markEntry, starEntry, tagEntry } from "app/entries/thunks"
|
import { markEntriesUpToEntry, markEntry, starEntry, tagEntry } from "app/entries/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { type Entry } from "app/types"
|
import { type Entry } from "app/types"
|
||||||
@@ -38,8 +38,8 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Group spacing={spacing}>
|
<Group gap={spacing}>
|
||||||
{props.entry.markable && (
|
{props.entry.markable && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={props.entry.read ? <TbEyeOff size={18} /> : <TbEyeCheck size={18} />}
|
icon={props.entry.read ? <TbEyeOff size={18} /> : <TbEyeCheck size={18} />}
|
||||||
@@ -72,22 +72,21 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{tags && (
|
{tags && (
|
||||||
<Popover withArrow withinPortal shadow="md" closeOnClickOutside={!mobile}>
|
<Popover withArrow shadow="md" closeOnClickOutside={!mobile}>
|
||||||
<Popover.Target>
|
<Popover.Target>
|
||||||
<Indicator label={props.entry.tags.length} disabled={props.entry.tags.length === 0} inline size={16}>
|
<Indicator label={props.entry.tags.length} disabled={props.entry.tags.length === 0} inline size={16}>
|
||||||
<ActionButton icon={<TbTag size={18} />} label={<Trans>Tags</Trans>} />
|
<ActionButton icon={<TbTag size={18} />} label={<Trans>Tags</Trans>} />
|
||||||
</Indicator>
|
</Indicator>
|
||||||
</Popover.Target>
|
</Popover.Target>
|
||||||
<Popover.Dropdown>
|
<Popover.Dropdown>
|
||||||
<MultiSelect
|
<TagsInput
|
||||||
|
placeholder={t`Tags`}
|
||||||
data={tags}
|
data={tags}
|
||||||
placeholder="Tags"
|
|
||||||
searchable
|
|
||||||
creatable
|
|
||||||
autoFocus
|
|
||||||
getCreateLabel={query => t`Create tag: ${query}`}
|
|
||||||
value={props.entry.tags}
|
value={props.entry.tags}
|
||||||
onChange={onTagsChange}
|
onChange={onTagsChange}
|
||||||
|
comboboxProps={{
|
||||||
|
withinPortal: false,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Popover.Dropdown>
|
</Popover.Dropdown>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Box, type MantineTheme, Space, Text, useMantineTheme } from "@mantine/core"
|
import { Box, Space, Text } from "@mantine/core"
|
||||||
import { type Entry } from "app/types"
|
import { type Entry } from "app/types"
|
||||||
import { RelativeDate } from "components/RelativeDate"
|
import { RelativeDate } from "components/RelativeDate"
|
||||||
|
import { useColorScheme } from "hooks/useColorScheme"
|
||||||
import { tss } from "tss"
|
import { tss } from "tss"
|
||||||
import { FeedEntryTitle } from "./FeedEntryTitle"
|
import { FeedEntryTitle } from "./FeedEntryTitle"
|
||||||
import { FeedFavicon } from "./FeedFavicon"
|
import { FeedFavicon } from "./FeedFavicon"
|
||||||
@@ -12,12 +13,12 @@ export interface FeedEntryHeaderProps {
|
|||||||
|
|
||||||
const useStyles = tss
|
const useStyles = tss
|
||||||
.withParams<{
|
.withParams<{
|
||||||
theme: MantineTheme
|
colorScheme: "light" | "dark"
|
||||||
read: boolean
|
read: boolean
|
||||||
}>()
|
}>()
|
||||||
.create(({ theme, read }) => ({
|
.create(({ colorScheme, read }) => ({
|
||||||
headerText: {
|
headerText: {
|
||||||
fontWeight: theme.colorScheme === "light" && !read ? "bold" : "inherit",
|
fontWeight: colorScheme === "light" && !read ? "bold" : "inherit",
|
||||||
},
|
},
|
||||||
headerSubtext: {
|
headerSubtext: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -27,9 +28,9 @@ const useStyles = tss
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
export function FeedEntryHeader(props: FeedEntryHeaderProps) {
|
export function FeedEntryHeader(props: FeedEntryHeaderProps) {
|
||||||
const theme = useMantineTheme()
|
const colorScheme = useColorScheme()
|
||||||
const { classes } = useStyles({
|
const { classes } = useStyles({
|
||||||
theme,
|
colorScheme,
|
||||||
read: props.entry.read,
|
read: props.entry.read,
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
@@ -40,7 +41,7 @@ export function FeedEntryHeader(props: FeedEntryHeaderProps) {
|
|||||||
<Box className={classes.headerSubtext}>
|
<Box className={classes.headerSubtext}>
|
||||||
<FeedFavicon url={props.entry.iconUrl} />
|
<FeedFavicon url={props.entry.iconUrl} />
|
||||||
<Space w={6} />
|
<Space w={6} />
|
||||||
<Text color="dimmed">
|
<Text c="dimmed">
|
||||||
{props.entry.feedName}
|
{props.entry.feedName}
|
||||||
<span> · </span>
|
<span> · </span>
|
||||||
<RelativeDate date={props.entry.date} />
|
<RelativeDate date={props.entry.date} />
|
||||||
@@ -48,7 +49,7 @@ export function FeedEntryHeader(props: FeedEntryHeaderProps) {
|
|||||||
</Box>
|
</Box>
|
||||||
{props.expanded && (
|
{props.expanded && (
|
||||||
<Box className={classes.headerSubtext}>
|
<Box className={classes.headerSubtext}>
|
||||||
<Text color="dimmed">
|
<Text c="dimmed">
|
||||||
{props.entry.author && <span>by {props.entry.author}</span>}
|
{props.entry.author && <span>by {props.entry.author}</span>}
|
||||||
{props.entry.author && props.entry.categories && <span> · </span>}
|
{props.entry.author && props.entry.categories && <span> · </span>}
|
||||||
{props.entry.categories && <span>{props.entry.categories}</span>}
|
{props.entry.categories && <span>{props.entry.categories}</span>}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Box, TypographyStylesProvider } from "@mantine/core"
|
import { Box } from "@mantine/core"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { calculatePlaceholderSize } from "app/utils"
|
import { calculatePlaceholderSize } from "app/utils"
|
||||||
|
import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
|
||||||
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
|
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
|
||||||
import { Content } from "./Content"
|
import { Content } from "./Content"
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ export function Media(props: MediaProps) {
|
|||||||
maxWidth: Constants.layout.entryMaxWidth,
|
maxWidth: Constants.layout.entryMaxWidth,
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<TypographyStylesProvider>
|
<BasicHtmlStyles>
|
||||||
<ImageWithPlaceholderWhileLoading
|
<ImageWithPlaceholderWhileLoading
|
||||||
src={props.thumbnailUrl}
|
src={props.thumbnailUrl}
|
||||||
alt="media thumbnail"
|
alt="media thumbnail"
|
||||||
@@ -34,6 +35,6 @@ export function Media(props: MediaProps) {
|
|||||||
<Content content={props.description} />
|
<Content content={props.description} />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</TypographyStylesProvider>
|
</BasicHtmlStyles>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ActionIcon, Box, type MantineTheme, SimpleGrid, useMantineTheme } from
|
|||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { useAppSelector } from "app/store"
|
import { useAppSelector } from "app/store"
|
||||||
import { type SharingSettings } from "app/types"
|
import { type SharingSettings } from "app/types"
|
||||||
|
import { useColorScheme } from "hooks/useColorScheme"
|
||||||
import { type IconType } from "react-icons"
|
import { type IconType } from "react-icons"
|
||||||
import { tss } from "tss"
|
import { tss } from "tss"
|
||||||
|
|
||||||
@@ -10,20 +11,23 @@ type Color = `#${string}`
|
|||||||
const useStyles = tss
|
const useStyles = tss
|
||||||
.withParams<{
|
.withParams<{
|
||||||
theme: MantineTheme
|
theme: MantineTheme
|
||||||
|
colorScheme: "light" | "dark"
|
||||||
color: Color
|
color: Color
|
||||||
}>()
|
}>()
|
||||||
.create(({ theme, color }) => ({
|
.create(({ theme, colorScheme, color }) => ({
|
||||||
socialIcon: {
|
socialIcon: {
|
||||||
color,
|
color,
|
||||||
backgroundColor: theme.colorScheme === "dark" ? theme.colors.gray[2] : "white",
|
backgroundColor: colorScheme === "dark" ? theme.colors.gray[2] : "white",
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
function ShareButton({ url, icon, color }: { url: string; icon: IconType; color: Color }) {
|
function ShareButton({ url, icon, color }: { url: string; icon: IconType; color: Color }) {
|
||||||
const theme = useMantineTheme()
|
const theme = useMantineTheme()
|
||||||
|
const colorScheme = useColorScheme()
|
||||||
const { classes } = useStyles({
|
const { classes } = useStyles({
|
||||||
theme,
|
theme,
|
||||||
|
colorScheme,
|
||||||
color,
|
color,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -33,7 +37,7 @@ function ShareButton({ url, icon, color }: { url: string; icon: IconType; color:
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionIcon>
|
<ActionIcon variant="transparent">
|
||||||
<a href={url} target="_blank" rel="noreferrer" onClick={onClick}>
|
<a href={url} target="_blank" rel="noreferrer" onClick={onClick}>
|
||||||
<Box p={6} className={classes.socialIcon}>
|
<Box p={6} className={classes.socialIcon}>
|
||||||
{icon({ size: 18 })}
|
{icon({ size: 18 })}
|
||||||
@@ -51,7 +55,7 @@ export function ShareButtons(props: { url: string; description: string }) {
|
|||||||
return (
|
return (
|
||||||
<SimpleGrid cols={4}>
|
<SimpleGrid cols={4}>
|
||||||
{(Object.keys(Constants.sharing) as Array<keyof SharingSettings>)
|
{(Object.keys(Constants.sharing) as Array<keyof SharingSettings>)
|
||||||
.filter(site => sharingSettings && sharingSettings[site])
|
.filter(site => sharingSettings?.[site])
|
||||||
.map(site => (
|
.map(site => (
|
||||||
<ShareButton
|
<ShareButton
|
||||||
key={site}
|
key={site}
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ export function AddCategory() {
|
|||||||
<Stack>
|
<Stack>
|
||||||
<TextInput label={<Trans>Category</Trans>} placeholder={t`Category`} {...form.getInputProps("name")} required />
|
<TextInput label={<Trans>Category</Trans>} placeholder={t`Category`} {...form.getInputProps("name")} required />
|
||||||
<CategorySelect label={<Trans>Parent</Trans>} {...form.getInputProps("parentId")} clearable />
|
<CategorySelect label={<Trans>Parent</Trans>} {...form.getInputProps("parentId")} clearable />
|
||||||
<Group position="center">
|
<Group justify="center">
|
||||||
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
|
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
|
||||||
<Trans>Cancel</Trans>
|
<Trans>Cancel</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" leftIcon={<TbFolderPlus size={16} />} loading={addCategory.loading}>
|
<Button type="submit" leftSection={<TbFolderPlus size={16} />} loading={addCategory.loading}>
|
||||||
<Trans>Add</Trans>
|
<Trans>Add</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t } from "@lingui/macro"
|
import { t } from "@lingui/macro"
|
||||||
import { Select, type SelectItem, type SelectProps } from "@mantine/core"
|
import { Select, type SelectProps } from "@mantine/core"
|
||||||
|
import { type ComboboxItem } from "@mantine/core/lib/components/Combobox/Combobox.types"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { useAppSelector } from "app/store"
|
import { useAppSelector } from "app/store"
|
||||||
import { flattenCategoryTree } from "app/utils"
|
import { flattenCategoryTree } from "app/utils"
|
||||||
@@ -12,9 +13,9 @@ type CategorySelectProps = Partial<SelectProps> & {
|
|||||||
export function CategorySelect(props: CategorySelectProps) {
|
export function CategorySelect(props: CategorySelectProps) {
|
||||||
const rootCategory = useAppSelector(state => state.tree.rootCategory)
|
const rootCategory = useAppSelector(state => state.tree.rootCategory)
|
||||||
const categories = rootCategory && flattenCategoryTree(rootCategory)
|
const categories = rootCategory && flattenCategoryTree(rootCategory)
|
||||||
const selectData: SelectItem[] | undefined = categories
|
const selectData: ComboboxItem[] | undefined = categories
|
||||||
?.filter(c => c.id !== Constants.categories.all.id)
|
?.filter(c => c.id !== Constants.categories.all.id)
|
||||||
.filter(c => !props.withoutCategoryIds || !props.withoutCategoryIds.includes(c.id))
|
.filter(c => !props.withoutCategoryIds?.includes(c.id))
|
||||||
.sort((c1, c2) => c1.name.localeCompare(c2.name))
|
.sort((c1, c2) => c1.name.localeCompare(c2.name))
|
||||||
.map(c => ({
|
.map(c => ({
|
||||||
label: c.parentName ? t`${c.name} (in ${c.parentName})` : c.name,
|
label: c.parentName ? t`${c.name} (in ${c.parentName})` : c.name,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export function ImportOpml() {
|
|||||||
<Stack>
|
<Stack>
|
||||||
<FileInput
|
<FileInput
|
||||||
label={<Trans>OPML file</Trans>}
|
label={<Trans>OPML file</Trans>}
|
||||||
icon={<TbFileImport />}
|
leftSection={<TbFileImport />}
|
||||||
// https://github.com/mantinedev/mantine/issues/5401
|
// https://github.com/mantinedev/mantine/issues/5401
|
||||||
{...{ placeholder: t`OPML file` }}
|
{...{ placeholder: t`OPML file` }}
|
||||||
description={
|
description={
|
||||||
@@ -50,11 +50,11 @@ export function ImportOpml() {
|
|||||||
required
|
required
|
||||||
accept="application/xml"
|
accept="application/xml"
|
||||||
/>
|
/>
|
||||||
<Group position="center">
|
<Group justify="center">
|
||||||
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
|
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
|
||||||
<Trans>Cancel</Trans>
|
<Trans>Cancel</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" leftIcon={<TbFileImport size={16} />} loading={importOpml.loading}>
|
<Button type="submit" leftSection={<TbFileImport size={16} />} loading={importOpml.loading}>
|
||||||
<Trans>Import</Trans>
|
<Trans>Import</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export function Subscribe() {
|
|||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
</Stepper>
|
</Stepper>
|
||||||
|
|
||||||
<Group position="center" mt="xl">
|
<Group justify="center" mt="xl">
|
||||||
<Button variant="default" onClick={previousStep}>
|
<Button variant="default" onClick={previousStep}>
|
||||||
<Trans>Back</Trans>
|
<Trans>Back</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -118,7 +118,7 @@ export function Subscribe() {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{activeStep === 1 && (
|
{activeStep === 1 && (
|
||||||
<Button type="submit" leftIcon={<TbRss size={16} />} loading={fetchFeed.loading || subscribe.loading}>
|
<Button type="submit" leftSection={<TbRss size={16} />} loading={fetchFeed.loading || subscribe.loading}>
|
||||||
<Trans>Subscribe</Trans>
|
<Trans>Subscribe</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function HeaderToolbar(props: { children: React.ReactNode }) {
|
|||||||
return mobile ? (
|
return mobile ? (
|
||||||
// on mobile use all available width
|
// on mobile use all available width
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
@@ -46,7 +46,7 @@ function HeaderToolbar(props: { children: React.ReactNode }) {
|
|||||||
{props.children}
|
{props.children}
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Group spacing={spacing}>{props.children}</Group>
|
<Group gap={spacing}>{props.children}</Group>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ export function Header() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
placeholder={t`Search`}
|
placeholder={t`Search`}
|
||||||
{...searchForm.getInputProps("search")}
|
{...searchForm.getInputProps("search")}
|
||||||
icon={<TbSearch size={iconSize} />}
|
leftSection={<TbSearch size={iconSize} />}
|
||||||
rightSection={
|
rightSection={
|
||||||
<ActionIcon onClick={async () => await (searchFromStore && dispatch(search("")))}>
|
<ActionIcon onClick={async () => await (searchFromStore && dispatch(search("")))}>
|
||||||
<TbX />
|
<TbX />
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function MarkAllAsReadButton(props: { iconSize: number }) {
|
|||||||
value={threshold}
|
value={threshold}
|
||||||
onChange={setThreshold}
|
onChange={setThreshold}
|
||||||
/>
|
/>
|
||||||
<Group position="right">
|
<Group justify="flex-end">
|
||||||
<Button variant="default" onClick={() => setOpened(false)}>
|
<Button variant="default" onClick={() => setOpened(false)}>
|
||||||
<Trans>Cancel</Trans>
|
<Trans>Cancel</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
|
|||||||
<Menu.Dropdown>
|
<Menu.Dropdown>
|
||||||
{profile && <Menu.Label>{profile.name}</Menu.Label>}
|
{profile && <Menu.Label>{profile.name}</Menu.Label>}
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={<TbSettings size={iconSize} />}
|
leftSection={<TbSettings size={iconSize} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch(redirectToSettings())
|
dispatch(redirectToSettings())
|
||||||
setOpened(false)
|
setOpened(false)
|
||||||
@@ -108,7 +108,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
|
|||||||
<Trans>Settings</Trans>
|
<Trans>Settings</Trans>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={<TbWorldDownload size={iconSize} />}
|
leftSection={<TbWorldDownload size={iconSize} />}
|
||||||
onClick={async () =>
|
onClick={async () =>
|
||||||
await client.feed.refreshAll().then(() => {
|
await client.feed.refreshAll().then(() => {
|
||||||
showNotification({
|
showNotification({
|
||||||
@@ -128,7 +128,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
|
|||||||
<Menu.Label>
|
<Menu.Label>
|
||||||
<Trans>Theme</Trans>
|
<Trans>Theme</Trans>
|
||||||
</Menu.Label>
|
</Menu.Label>
|
||||||
<Menu.Item icon={dark ? <TbSun size={iconSize} /> : <TbMoon size={iconSize} />} onClick={() => toggleColorScheme()}>
|
<Menu.Item leftSection={dark ? <TbSun size={iconSize} /> : <TbMoon size={iconSize} />} onClick={() => toggleColorScheme()}>
|
||||||
{dark ? <Trans>Switch to light theme</Trans> : <Trans>Switch to dark theme</Trans>}
|
{dark ? <Trans>Switch to light theme</Trans> : <Trans>Switch to dark theme</Trans>}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
|
|||||||
<Trans>Admin</Trans>
|
<Trans>Admin</Trans>
|
||||||
</Menu.Label>
|
</Menu.Label>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={<TbUsers size={iconSize} />}
|
leftSection={<TbUsers size={iconSize} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch(redirectToAdminUsers())
|
dispatch(redirectToAdminUsers())
|
||||||
setOpened(false)
|
setOpened(false)
|
||||||
@@ -162,7 +162,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
|
|||||||
<Trans>Manage users</Trans>
|
<Trans>Manage users</Trans>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={<TbChartLine size={iconSize} />}
|
leftSection={<TbChartLine size={iconSize} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch(redirectToMetrics())
|
dispatch(redirectToMetrics())
|
||||||
setOpened(false)
|
setOpened(false)
|
||||||
@@ -176,7 +176,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
|
|||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={<TbHeartFilled size={iconSize} color="red" />}
|
leftSection={<TbHeartFilled size={iconSize} color="red" />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch(redirectToDonate())
|
dispatch(redirectToDonate())
|
||||||
setOpened(false)
|
setOpened(false)
|
||||||
@@ -186,7 +186,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
|
|||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={<TbHelp size={iconSize} />}
|
leftSection={<TbHelp size={iconSize} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch(redirectToAbout())
|
dispatch(redirectToAbout())
|
||||||
setOpened(false)
|
setOpened(false)
|
||||||
@@ -194,7 +194,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
|
|||||||
>
|
>
|
||||||
<Trans>About</Trans>
|
<Trans>About</Trans>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item icon={<TbPower size={iconSize} />} onClick={logout}>
|
<Menu.Item leftSection={<TbPower size={iconSize} />} onClick={logout}>
|
||||||
<Trans>Logout</Trans>
|
<Trans>Logout</Trans>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu.Dropdown>
|
</Menu.Dropdown>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function MetricAccordionItem({ metricKey, name, headerValue, children }:
|
|||||||
return (
|
return (
|
||||||
<Accordion.Item value={metricKey} key={metricKey}>
|
<Accordion.Item value={metricKey} key={metricKey}>
|
||||||
<Accordion.Control>
|
<Accordion.Control>
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Box>{name}</Box>
|
<Box>{name}</Box>
|
||||||
<Box>{headerValue}</Box>
|
<Box>{headerValue}</Box>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export function CustomCodeSettings() {
|
|||||||
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
|
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
|
||||||
<Trans>Cancel</Trans>
|
<Trans>Cancel</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveCustomCode.loading}>
|
<Button type="submit" leftSection={<TbDeviceFloppy size={16} />} loading={saveCustomCode.loading}>
|
||||||
<Trans>Save</Trans>
|
<Trans>Save</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export function DisplaySettings() {
|
|||||||
<Switch
|
<Switch
|
||||||
key={site}
|
key={site}
|
||||||
label={Constants.sharing[site].label}
|
label={Constants.sharing[site].label}
|
||||||
checked={sharingSettings && sharingSettings[site]}
|
checked={sharingSettings?.[site]}
|
||||||
onChange={async e =>
|
onChange={async e =>
|
||||||
await dispatch(
|
await dispatch(
|
||||||
changeSharingSetting({
|
changeSharingSetting({
|
||||||
|
|||||||
@@ -132,13 +132,13 @@ export function ProfileSettings() {
|
|||||||
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
|
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
|
||||||
<Trans>Cancel</Trans>
|
<Trans>Cancel</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveProfile.loading}>
|
<Button type="submit" leftSection={<TbDeviceFloppy size={16} />} loading={saveProfile.loading}>
|
||||||
<Trans>Save</Trans>
|
<Trans>Save</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
<Button
|
<Button
|
||||||
color="red"
|
color="red"
|
||||||
leftIcon={<TbTrash size={16} />}
|
leftSection={<TbTrash size={16} />}
|
||||||
onClick={() => openDeleteProfileModal()}
|
onClick={() => openDeleteProfileModal()}
|
||||||
loading={deleteProfile.loading}
|
loading={deleteProfile.loading}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Box, Center, type MantineTheme, useMantineTheme } from "@mantine/core"
|
import { Box, Center, type MantineTheme, useMantineTheme } from "@mantine/core"
|
||||||
import { FeedFavicon } from "components/content/FeedFavicon"
|
import { FeedFavicon } from "components/content/FeedFavicon"
|
||||||
|
import { useColorScheme } from "hooks/useColorScheme"
|
||||||
import React, { type ReactNode } from "react"
|
import React, { type ReactNode } from "react"
|
||||||
import { tss } from "tss"
|
import { tss } from "tss"
|
||||||
import { UnreadCount } from "./UnreadCount"
|
import { UnreadCount } from "./UnreadCount"
|
||||||
@@ -20,18 +21,19 @@ interface TreeNodeProps {
|
|||||||
const useStyles = tss
|
const useStyles = tss
|
||||||
.withParams<{
|
.withParams<{
|
||||||
theme: MantineTheme
|
theme: MantineTheme
|
||||||
|
colorScheme: "dark" | "light"
|
||||||
selected: boolean
|
selected: boolean
|
||||||
hasError: boolean
|
hasError: boolean
|
||||||
hasUnread: boolean
|
hasUnread: boolean
|
||||||
}>()
|
}>()
|
||||||
.create(({ theme, selected, hasError, hasUnread }) => {
|
.create(({ theme, colorScheme, selected, hasError, hasUnread }) => {
|
||||||
let backgroundColor = "inherit"
|
let backgroundColor = "inherit"
|
||||||
if (selected) backgroundColor = theme.colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[3]
|
if (selected) backgroundColor = colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[3]
|
||||||
|
|
||||||
let color
|
let color
|
||||||
if (hasError) {
|
if (hasError) {
|
||||||
color = theme.colors.red[6]
|
color = theme.colors.red[6]
|
||||||
} else if (theme.colorScheme === "dark") {
|
} else if (colorScheme === "dark") {
|
||||||
color = hasUnread ? theme.colors.dark[0] : theme.colors.dark[3]
|
color = hasUnread ? theme.colors.dark[0] : theme.colors.dark[3]
|
||||||
} else {
|
} else {
|
||||||
color = hasUnread ? theme.black : theme.colors.gray[6]
|
color = hasUnread ? theme.black : theme.colors.gray[6]
|
||||||
@@ -45,7 +47,7 @@ const useStyles = tss
|
|||||||
color,
|
color,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[0],
|
backgroundColor: colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[0],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nodeText: {
|
nodeText: {
|
||||||
@@ -59,8 +61,10 @@ const useStyles = tss
|
|||||||
|
|
||||||
export function TreeNode(props: TreeNodeProps) {
|
export function TreeNode(props: TreeNodeProps) {
|
||||||
const theme = useMantineTheme()
|
const theme = useMantineTheme()
|
||||||
|
const colorScheme = useColorScheme()
|
||||||
const { classes } = useStyles({
|
const { classes } = useStyles({
|
||||||
theme,
|
theme,
|
||||||
|
colorScheme,
|
||||||
selected: props.selected,
|
selected: props.selected,
|
||||||
hasError: props.hasError,
|
hasError: props.hasError,
|
||||||
hasUnread: props.unread > 0,
|
hasUnread: props.unread > 0,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { t, Trans } from "@lingui/macro"
|
import { t, Trans } from "@lingui/macro"
|
||||||
import { Box, Center, Kbd, TextInput } from "@mantine/core"
|
import { Box, Center, Kbd, TextInput } from "@mantine/core"
|
||||||
import { openSpotlight, type SpotlightAction, SpotlightProvider } from "@mantine/spotlight"
|
import { Spotlight, spotlight, type SpotlightActionData } from "@mantine/spotlight"
|
||||||
import { redirectToFeed } from "app/redirect/thunks"
|
import { redirectToFeed } from "app/redirect/thunks"
|
||||||
import { useAppDispatch } from "app/store"
|
import { useAppDispatch } from "app/store"
|
||||||
import { type Subscription } from "app/types"
|
import { type Subscription } from "app/types"
|
||||||
@@ -15,17 +15,18 @@ export interface TreeSearchProps {
|
|||||||
export function TreeSearch(props: TreeSearchProps) {
|
export function TreeSearch(props: TreeSearchProps) {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
const actions: SpotlightAction[] = props.feeds
|
const actions: SpotlightActionData[] = props.feeds
|
||||||
.sort((f1, f2) => f1.name.localeCompare(f2.name))
|
.toSorted((f1, f2) => f1.name.localeCompare(f2.name))
|
||||||
.map(f => ({
|
.map(f => ({
|
||||||
title: f.name,
|
id: `${f.id}`,
|
||||||
icon: <FeedFavicon url={f.iconUrl} />,
|
label: f.name,
|
||||||
onTrigger: async () => await dispatch(redirectToFeed(f.id)),
|
leftSection: <FeedFavicon url={f.iconUrl} />,
|
||||||
|
onClick: async () => await dispatch(redirectToFeed(f.id)),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const searchIcon = <TbSearch size={18} />
|
const searchIcon = <TbSearch size={18} />
|
||||||
const rightSection = (
|
const rightSection = (
|
||||||
<Center>
|
<Center style={{ cursor: "pointer" }} onClick={() => spotlight.open()}>
|
||||||
<Kbd>Ctrl</Kbd>
|
<Kbd>Ctrl</Kbd>
|
||||||
<Box mx={5}>+</Box>
|
<Box mx={5}>+</Box>
|
||||||
<Kbd>K</Kbd>
|
<Kbd>K</Kbd>
|
||||||
@@ -33,30 +34,35 @@ export function TreeSearch(props: TreeSearchProps) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// additional keyboard shortcut used by commafeed v1
|
// additional keyboard shortcut used by commafeed v1
|
||||||
useMousetrap("g u", () => openSpotlight())
|
useMousetrap("g u", () => spotlight.open())
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SpotlightProvider
|
<>
|
||||||
actions={actions}
|
|
||||||
searchIcon={searchIcon}
|
|
||||||
searchPlaceholder={t`Search`}
|
|
||||||
shortcut="ctrl+k"
|
|
||||||
nothingFoundMessage={<Trans>Nothing found</Trans>}
|
|
||||||
>
|
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder={t`Search`}
|
placeholder={t`Search`}
|
||||||
icon={searchIcon}
|
leftSection={searchIcon}
|
||||||
rightSectionWidth={100}
|
rightSectionWidth={100}
|
||||||
rightSection={rightSection}
|
rightSection={rightSection}
|
||||||
styles={{
|
styles={{
|
||||||
input: { cursor: "pointer" },
|
input: {
|
||||||
rightSection: { pointerEvents: "none" },
|
cursor: "pointer",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
onClick={() => openSpotlight()}
|
onClick={() => spotlight.open()}
|
||||||
// prevent focus
|
// prevent focus
|
||||||
onFocus={e => e.target.blur()}
|
onFocus={e => e.target.blur()}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
</SpotlightProvider>
|
<Spotlight
|
||||||
|
actions={actions}
|
||||||
|
limit={10}
|
||||||
|
shortcut="ctrl+k"
|
||||||
|
searchProps={{
|
||||||
|
leftSection: searchIcon,
|
||||||
|
placeholder: t`Search`,
|
||||||
|
}}
|
||||||
|
nothingFound={<Trans>Nothing found</Trans>}
|
||||||
|
></Spotlight>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ export function UnreadCount(props: { unreadCount: number }) {
|
|||||||
const count = props.unreadCount >= 10000 ? "10k+" : props.unreadCount
|
const count = props.unreadCount >= 10000 ? "10k+" : props.unreadCount
|
||||||
return (
|
return (
|
||||||
<Tooltip label={props.unreadCount} disabled={props.unreadCount === count}>
|
<Tooltip label={props.unreadCount} disabled={props.unreadCount === count}>
|
||||||
<Badge className={classes.badge}>{count}</Badge>
|
<Badge className={classes.badge} variant="light">
|
||||||
|
{count}
|
||||||
|
</Badge>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
4
commafeed-client/src/hooks/useColorScheme.ts
Normal file
4
commafeed-client/src/hooks/useColorScheme.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { useComputedColorScheme } from "@mantine/core"
|
||||||
|
|
||||||
|
// the color scheme to use to render components
|
||||||
|
export const useColorScheme = () => useComputedColorScheme("light")
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { useMediaQuery } from "@mantine/hooks"
|
import { useMediaQuery } from "@mantine/hooks"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
|
|
||||||
export const useMobile = (breakpoint: string = Constants.layout.mobileBreakpoint) =>
|
export const useMobile = (breakpoint: string | number = Constants.layout.mobileBreakpoint) => {
|
||||||
!useMediaQuery(`(min-width: ${breakpoint})`, undefined, {
|
const bp = typeof breakpoint === "number" ? `${breakpoint}px` : breakpoint
|
||||||
|
return !useMediaQuery(`(min-width: ${bp})`, undefined, {
|
||||||
getInitialValueInEffect: false,
|
getInitialValueInEffect: false,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import "@fontsource/open-sans"
|
import "@fontsource/open-sans"
|
||||||
|
import "@mantine/core/styles.css"
|
||||||
|
import "@mantine/notifications/styles.css"
|
||||||
|
import "@mantine/spotlight/styles.css"
|
||||||
|
import "react-contexify/ReactContexify.css"
|
||||||
|
import "main.css"
|
||||||
import { App } from "App"
|
import { App } from "App"
|
||||||
import { store } from "app/store"
|
import { store } from "app/store"
|
||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
import relativeTime from "dayjs/plugin/relativeTime"
|
import relativeTime from "dayjs/plugin/relativeTime"
|
||||||
import "main.css"
|
|
||||||
import "react-contexify/ReactContexify.css"
|
|
||||||
import ReactDOM from "react-dom/client"
|
import ReactDOM from "react-dom/client"
|
||||||
import { Provider } from "react-redux"
|
import { Provider } from "react-redux"
|
||||||
|
|
||||||
|
|||||||
@@ -50,11 +50,11 @@ export function ErrorPage(props: { error: Error }) {
|
|||||||
<Title className={classes.title}>
|
<Title className={classes.title}>
|
||||||
<Trans>Something bad just happened...</Trans>
|
<Trans>Something bad just happened...</Trans>
|
||||||
</Title>
|
</Title>
|
||||||
<Text size="lg" align="center" className={classes.description}>
|
<Text size="lg" ta="center" className={classes.description}>
|
||||||
{props.error.message}
|
{props.error.message}
|
||||||
</Text>
|
</Text>
|
||||||
<Group position="center">
|
<Group justify="center">
|
||||||
<Button size="md" onClick={() => window.location.reload()} leftIcon={<TbRefresh size={18} />}>
|
<Button size="md" onClick={() => window.location.reload()} leftSection={<TbRefresh size={18} />}>
|
||||||
Refresh the page
|
Refresh the page
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export function LoadingPage() {
|
|||||||
<RingProgress
|
<RingProgress
|
||||||
sections={[{ value: loadingPercentage, color: theme.primaryColor }]}
|
sections={[{ value: loadingPercentage, color: theme.primaryColor }]}
|
||||||
label={
|
label={
|
||||||
<Text weight="bold" align="center" size="xl">
|
<Text fw="bold" ta="center" size="xl">
|
||||||
{loadingPercentage}%
|
{loadingPercentage}%
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Trans } from "@lingui/macro"
|
import { Trans } from "@lingui/macro"
|
||||||
import { Anchor, Box, Center, Container, Divider, Group, Image, Title, useMantineColorScheme } from "@mantine/core"
|
import { Anchor, Box, Center, Container, Divider, Group, Image, Space, Title, useMantineColorScheme } from "@mantine/core"
|
||||||
import { client } from "app/client"
|
import { client } from "app/client"
|
||||||
import { redirectToApiDocumentation, redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/redirect/thunks"
|
import { redirectToApiDocumentation, redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/redirect/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
@@ -31,7 +31,7 @@ export function WelcomePage() {
|
|||||||
<Container>
|
<Container>
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<Center my="xl">
|
<Center my="lg">
|
||||||
<Title order={3}>Bloat-free feed reader</Title>
|
<Title order={3}>Bloat-free feed reader</Title>
|
||||||
</Center>
|
</Center>
|
||||||
|
|
||||||
@@ -47,13 +47,15 @@ export function WelcomePage() {
|
|||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Divider my="xl" />
|
<Divider my="lg" />
|
||||||
|
|
||||||
<Image src={image} />
|
<Image src={image} />
|
||||||
|
|
||||||
<Divider my="xl" />
|
<Divider my="lg" />
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|
||||||
|
<Space h="lg" />
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -73,9 +75,13 @@ function Header() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<PageTitle />
|
<Box>
|
||||||
<Buttons />
|
<PageTitle />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Buttons />
|
||||||
|
</Box>
|
||||||
</Group>
|
</Group>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -88,7 +94,7 @@ function Buttons() {
|
|||||||
const dark = colorScheme === "dark"
|
const dark = colorScheme === "dark"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group spacing={14}>
|
<Group gap={14}>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
label={<Trans>Log in</Trans>}
|
label={<Trans>Log in</Trans>}
|
||||||
icon={<TbKey size={iconSize} />}
|
icon={<TbKey size={iconSize} />}
|
||||||
@@ -128,7 +134,7 @@ function Buttons() {
|
|||||||
function Footer() {
|
function Footer() {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
return (
|
return (
|
||||||
<Group position="apart">
|
<Group justify="space-between">
|
||||||
<Group>
|
<Group>
|
||||||
<span>© CommaFeed</span>
|
<span>© CommaFeed</span>
|
||||||
<Anchor variant="text" href="https://github.com/Athou/commafeed/" target="_blank" rel="noreferrer">
|
<Anchor variant="text" href="https://github.com/Athou/commafeed/" target="_blank" rel="noreferrer">
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export function AdminUsersPage() {
|
|||||||
<Title order={3} mb="md">
|
<Title order={3} mb="md">
|
||||||
<Group>
|
<Group>
|
||||||
<Trans>Manage users</Trans>
|
<Trans>Manage users</Trans>
|
||||||
<ActionIcon color={theme.primaryColor} onClick={() => openUserEditModal(<Trans>Add user</Trans>)}>
|
<ActionIcon color={theme.primaryColor} variant="subtle" onClick={() => openUserEditModal(<Trans>Add user</Trans>)}>
|
||||||
<TbPlus size={20} />
|
<TbPlus size={20} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -79,69 +79,74 @@ export function AdminUsersPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Table striped highlightOnHover>
|
<Table striped highlightOnHover>
|
||||||
<thead>
|
<Table.Thead>
|
||||||
<tr>
|
<Table.Tr>
|
||||||
<th>
|
<Table.Th>
|
||||||
<Trans>Id</Trans>
|
<Trans>Id</Trans>
|
||||||
</th>
|
</Table.Th>
|
||||||
<th>
|
<Table.Th>
|
||||||
<Trans>Name</Trans>
|
<Trans>Name</Trans>
|
||||||
</th>
|
</Table.Th>
|
||||||
<th>
|
<Table.Th>
|
||||||
<Trans>E-mail</Trans>
|
<Trans>E-mail</Trans>
|
||||||
</th>
|
</Table.Th>
|
||||||
<th>
|
<Table.Th>
|
||||||
<Trans>Date created</Trans>
|
<Trans>Date created</Trans>
|
||||||
</th>
|
</Table.Th>
|
||||||
<th>
|
<Table.Th>
|
||||||
<Trans>Last login date</Trans>
|
<Trans>Last login date</Trans>
|
||||||
</th>
|
</Table.Th>
|
||||||
<th>
|
<Table.Th>
|
||||||
<Trans>Admin</Trans>
|
<Trans>Admin</Trans>
|
||||||
</th>
|
</Table.Th>
|
||||||
<th>
|
<Table.Th>
|
||||||
<Trans>Enabled</Trans>
|
<Trans>Enabled</Trans>
|
||||||
</th>
|
</Table.Th>
|
||||||
<th>
|
<Table.Th>
|
||||||
<Trans>Actions</Trans>
|
<Trans>Actions</Trans>
|
||||||
</th>
|
</Table.Th>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
</thead>
|
</Table.Thead>
|
||||||
<tbody>
|
<Table.Tbody>
|
||||||
{users?.map(u => (
|
{users?.map(u => (
|
||||||
<tr key={u.id}>
|
<Table.Tr key={u.id}>
|
||||||
<td>{u.id}</td>
|
<Table.Td>{u.id}</Table.Td>
|
||||||
<td>{u.name}</td>
|
<Table.Td>{u.name}</Table.Td>
|
||||||
<td>{u.email}</td>
|
<Table.Td>{u.email}</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<RelativeDate date={u.created} />
|
<RelativeDate date={u.created} />
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<RelativeDate date={u.lastLogin} />
|
<RelativeDate date={u.lastLogin} />
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<BooleanIcon value={u.admin} />
|
<BooleanIcon value={u.admin} />
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<BooleanIcon value={u.enabled} />
|
<BooleanIcon value={u.enabled} />
|
||||||
</td>
|
</Table.Td>
|
||||||
<td>
|
<Table.Td>
|
||||||
<Group>
|
<Group>
|
||||||
<ActionIcon color={theme.primaryColor} onClick={() => openUserEditModal(<Trans>Edit user</Trans>, u)}>
|
<ActionIcon
|
||||||
|
color={theme.primaryColor}
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => openUserEditModal(<Trans>Edit user</Trans>, u)}
|
||||||
|
>
|
||||||
<TbPencil size={18} />
|
<TbPencil size={18} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
color={theme.primaryColor}
|
color={theme.primaryColor}
|
||||||
|
variant="subtle"
|
||||||
onClick={() => openUserDeleteModal(u)}
|
onClick={() => openUserDeleteModal(u)}
|
||||||
loading={deleteUser.loading}
|
loading={deleteUser.loading}
|
||||||
>
|
>
|
||||||
<TbTrash size={18} />
|
<TbTrash size={18} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Group>
|
</Group>
|
||||||
</td>
|
</Table.Td>
|
||||||
</tr>
|
</Table.Tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</Table.Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ export function MetricsPage() {
|
|||||||
return (
|
return (
|
||||||
<Tabs defaultValue="stats">
|
<Tabs defaultValue="stats">
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
<Tabs.Tab value="stats" icon={<TbChartAreaLine size={14} />}>
|
<Tabs.Tab value="stats" leftSection={<TbChartAreaLine size={14} />}>
|
||||||
Stats
|
Stats
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
<Tabs.Tab value="timers" icon={<TbClock size={14} />}>
|
<Tabs.Tab value="timers" leftSection={<TbClock size={14} />}>
|
||||||
Timers
|
Timers
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export function AboutPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="xl">
|
<Container size="xl">
|
||||||
<SimpleGrid cols={2} breakpoints={[{ maxWidth: Constants.layout.mobileBreakpoint, cols: 1 }]}>
|
<SimpleGrid cols={{ base: 1, [Constants.layout.mobileBreakpointName]: 2 }}>
|
||||||
<Section title={<Trans>About</Trans>} icon={<TbHelp size={24} />}>
|
<Section title={<Trans>About</Trans>} icon={<TbHelp size={24} />}>
|
||||||
<Box>
|
<Box>
|
||||||
<Trans>
|
<Trans>
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ export function AddPage() {
|
|||||||
<Container size="sm" px={0}>
|
<Container size="sm" px={0}>
|
||||||
<Tabs defaultValue="subscribe">
|
<Tabs defaultValue="subscribe">
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
<Tabs.Tab value="subscribe" icon={<TbRss size={16} />}>
|
<Tabs.Tab value="subscribe" leftSection={<TbRss size={16} />}>
|
||||||
<Trans>Subscribe</Trans>
|
<Trans>Subscribe</Trans>
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
<Tabs.Tab value="category" icon={<TbFolderPlus size={16} />}>
|
<Tabs.Tab value="category" leftSection={<TbFolderPlus size={16} />}>
|
||||||
<Trans>Add category</Trans>
|
<Trans>Add category</Trans>
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
<Tabs.Tab value="opml" icon={<TbFileImport size={16} />}>
|
<Tabs.Tab value="opml" leftSection={<TbFileImport size={16} />}>
|
||||||
<Trans>OPML</Trans>
|
<Trans>OPML</Trans>
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
|
|||||||
@@ -127,13 +127,13 @@ export function CategoryDetailsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
{editable && (
|
{editable && (
|
||||||
<>
|
<>
|
||||||
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={modifyCategory.loading}>
|
<Button type="submit" leftSection={<TbDeviceFloppy size={16} />} loading={modifyCategory.loading}>
|
||||||
<Trans>Save</Trans>
|
<Trans>Save</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
<Button
|
<Button
|
||||||
color="red"
|
color="red"
|
||||||
leftIcon={<TbTrash size={16} />}
|
leftSection={<TbTrash size={16} />}
|
||||||
onClick={() => openDeleteCategoryModal()}
|
onClick={() => openDeleteCategoryModal()}
|
||||||
loading={deleteCategory.loading}
|
loading={deleteCategory.loading}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -166,13 +166,13 @@ export function FeedDetailsPage() {
|
|||||||
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
|
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
|
||||||
<Trans>Cancel</Trans>
|
<Trans>Cancel</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={modifyFeed.loading}>
|
<Button type="submit" leftSection={<TbDeviceFloppy size={16} />} loading={modifyFeed.loading}>
|
||||||
<Trans>Save</Trans>
|
<Trans>Save</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
<Button
|
<Button
|
||||||
color="red"
|
color="red"
|
||||||
leftIcon={<TbTrash size={16} />}
|
leftSection={<TbTrash size={16} />}
|
||||||
onClick={() => openUnsubscribeModal()}
|
onClick={() => openUnsubscribeModal()}
|
||||||
loading={unsubscribe.loading}
|
loading={unsubscribe.loading}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export function FeedEntriesPage(props: FeedEntriesPageProps) {
|
|||||||
return (
|
return (
|
||||||
// add some room at the bottom of the page in order to be able to scroll the current entry at the top of the page when expanding
|
// add some room at the bottom of the page in order to be able to scroll the current entry at the top of the page when expanding
|
||||||
<Box mb={viewport.height - Constants.layout.headerHeight - 210}>
|
<Box mb={viewport.height - Constants.layout.headerHeight - 210}>
|
||||||
<Group spacing="xl">
|
<Group gap="xl">
|
||||||
{sourceWebsiteUrl && (
|
{sourceWebsiteUrl && (
|
||||||
<a href={sourceWebsiteUrl} target="_blank" rel="noreferrer" className={classes.sourceWebsiteLink}>
|
<a href={sourceWebsiteUrl} target="_blank" rel="noreferrer" className={classes.sourceWebsiteLink}>
|
||||||
<Title order={3}>{sourceLabel}</Title>
|
<Title order={3}>{sourceLabel}</Title>
|
||||||
|
|||||||
@@ -1,88 +1,32 @@
|
|||||||
import {
|
import { Trans } from "@lingui/macro"
|
||||||
ActionIcon,
|
import { ActionIcon, AppShell, Box, Center, Group, ScrollArea, Title, useMantineTheme } from "@mantine/core"
|
||||||
AppShell,
|
|
||||||
Box,
|
|
||||||
Burger,
|
|
||||||
Center,
|
|
||||||
DEFAULT_THEME,
|
|
||||||
Group,
|
|
||||||
Header,
|
|
||||||
type MantineTheme,
|
|
||||||
Navbar,
|
|
||||||
ScrollArea,
|
|
||||||
Title,
|
|
||||||
useMantineTheme,
|
|
||||||
} from "@mantine/core"
|
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { redirectToAdd, redirectToRootCategory } from "app/redirect/thunks"
|
import { redirectToAdd, redirectToRootCategory } from "app/redirect/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { setMobileMenuOpen, setSidebarWidth } from "app/tree/slice"
|
import { setMobileMenuOpen, setSidebarWidth } from "app/tree/slice"
|
||||||
import { reloadTree } from "app/tree/thunks"
|
import { reloadTree } from "app/tree/thunks"
|
||||||
import { reloadProfile, reloadSettings, reloadTags } from "app/user/thunks"
|
import { reloadProfile, reloadSettings, reloadTags } from "app/user/thunks"
|
||||||
|
import { ActionButton } from "components/ActionButton"
|
||||||
import { AnnouncementDialog } from "components/AnnouncementDialog"
|
import { AnnouncementDialog } from "components/AnnouncementDialog"
|
||||||
import { Loader } from "components/Loader"
|
import { Loader } from "components/Loader"
|
||||||
import { Logo } from "components/Logo"
|
import { Logo } from "components/Logo"
|
||||||
import { OnDesktop } from "components/responsive/OnDesktop"
|
import { OnDesktop } from "components/responsive/OnDesktop"
|
||||||
import { OnMobile } from "components/responsive/OnMobile"
|
import { OnMobile } from "components/responsive/OnMobile"
|
||||||
import { useAppLoading } from "hooks/useAppLoading"
|
import { useAppLoading } from "hooks/useAppLoading"
|
||||||
import { useMobile } from "hooks/useMobile"
|
|
||||||
import { useWebSocket } from "hooks/useWebSocket"
|
import { useWebSocket } from "hooks/useWebSocket"
|
||||||
import { LoadingPage } from "pages/LoadingPage"
|
import { LoadingPage } from "pages/LoadingPage"
|
||||||
import { Resizable } from "re-resizable"
|
|
||||||
import { type ReactNode, Suspense, useEffect } from "react"
|
import { type ReactNode, Suspense, useEffect } from "react"
|
||||||
import { TbPlus } from "react-icons/tb"
|
import Draggable from "react-draggable"
|
||||||
|
import { TbMenu2, TbPlus, TbX } from "react-icons/tb"
|
||||||
import { Outlet } from "react-router-dom"
|
import { Outlet } from "react-router-dom"
|
||||||
import { tss } from "tss"
|
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
sidebar: ReactNode
|
sidebar: ReactNode
|
||||||
sidebarWidth: number
|
sidebarWidth: number
|
||||||
|
sidebarVisible: boolean
|
||||||
header: ReactNode
|
header: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const sidebarPadding = DEFAULT_THEME.spacing.xs
|
|
||||||
const sidebarRightBorderWidth = "1px"
|
|
||||||
|
|
||||||
const useStyles = tss
|
|
||||||
.withParams<{
|
|
||||||
theme: MantineTheme
|
|
||||||
sidebarWidth: number
|
|
||||||
}>()
|
|
||||||
.create(({ theme, sidebarWidth }) => ({
|
|
||||||
sidebar: {
|
|
||||||
"& .mantine-ScrollArea-scrollbar[data-orientation='horizontal']": {
|
|
||||||
display: "none",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sidebarContentResizeWrapper: {
|
|
||||||
padding: sidebarPadding,
|
|
||||||
minHeight: `calc(100vh - ${Constants.layout.headerHeight}px)`,
|
|
||||||
},
|
|
||||||
sidebarContent: {
|
|
||||||
maxWidth: `calc(${sidebarWidth}px - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`,
|
|
||||||
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
|
|
||||||
maxWidth: `calc(100vw - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mainContentWrapper: {
|
|
||||||
paddingTop: Constants.layout.headerHeight,
|
|
||||||
paddingLeft: sidebarWidth,
|
|
||||||
paddingRight: 0,
|
|
||||||
paddingBottom: 0,
|
|
||||||
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
|
|
||||||
paddingLeft: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mainContent: {
|
|
||||||
maxWidth: `calc(100vw - ${sidebarWidth}px)`,
|
|
||||||
padding: theme.spacing.md,
|
|
||||||
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
|
|
||||||
maxWidth: "100vw",
|
|
||||||
padding: "6px",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
function LogoAndTitle() {
|
function LogoAndTitle() {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
return (
|
return (
|
||||||
@@ -97,21 +41,13 @@ function LogoAndTitle() {
|
|||||||
|
|
||||||
export default function Layout(props: LayoutProps) {
|
export default function Layout(props: LayoutProps) {
|
||||||
const theme = useMantineTheme()
|
const theme = useMantineTheme()
|
||||||
const { classes } = useStyles({
|
|
||||||
theme,
|
|
||||||
sidebarWidth: props.sidebarWidth,
|
|
||||||
})
|
|
||||||
const { loading } = useAppLoading()
|
const { loading } = useAppLoading()
|
||||||
const mobile = useMobile()
|
|
||||||
const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen)
|
const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen)
|
||||||
const webSocketConnected = useAppSelector(state => state.server.webSocketConnected)
|
const webSocketConnected = useAppSelector(state => state.server.webSocketConnected)
|
||||||
const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval)
|
const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval)
|
||||||
const sidebarHidden = props.sidebarWidth === 0
|
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
useWebSocket()
|
useWebSocket()
|
||||||
|
|
||||||
const handleResize = (element: HTMLElement) => dispatch(setSidebarWidth(element.offsetWidth))
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// load initial data
|
// load initial data
|
||||||
dispatch(reloadSettings())
|
dispatch(reloadSettings())
|
||||||
@@ -132,18 +68,20 @@ export default function Layout(props: LayoutProps) {
|
|||||||
}, [dispatch, webSocketConnected, treeReloadInterval])
|
}, [dispatch, webSocketConnected, treeReloadInterval])
|
||||||
|
|
||||||
const burger = (
|
const burger = (
|
||||||
<Center>
|
<ActionButton
|
||||||
<Burger
|
label={mobileMenuOpen ? <Trans>Open menu</Trans> : <Trans>Close menu</Trans>}
|
||||||
color={theme.fn.variant({ color: theme.primaryColor, variant: "subtle" }).color}
|
icon={mobileMenuOpen ? <TbX size={18} /> : <TbMenu2 size={18} />}
|
||||||
opened={mobileMenuOpen}
|
onClick={() => dispatch(setMobileMenuOpen(!mobileMenuOpen))}
|
||||||
onClick={() => dispatch(setMobileMenuOpen(!mobileMenuOpen))}
|
></ActionButton>
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</Center>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const addButton = (
|
const addButton = (
|
||||||
<ActionIcon color={theme.primaryColor} onClick={async () => await dispatch(redirectToAdd())} aria-label="Subscribe">
|
<ActionIcon
|
||||||
|
color={theme.primaryColor}
|
||||||
|
variant="subtle"
|
||||||
|
onClick={async () => await dispatch(redirectToAdd())}
|
||||||
|
aria-label="Subscribe"
|
||||||
|
>
|
||||||
<TbPlus size={18} />
|
<TbPlus size={18} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
)
|
)
|
||||||
@@ -151,77 +89,80 @@ export default function Layout(props: LayoutProps) {
|
|||||||
if (loading) return <LoadingPage />
|
if (loading) return <LoadingPage />
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
fixed
|
header={{ height: Constants.layout.headerHeight }}
|
||||||
navbarOffsetBreakpoint={Constants.layout.mobileBreakpoint}
|
navbar={{
|
||||||
classNames={{ main: classes.mainContentWrapper }}
|
width: props.sidebarWidth,
|
||||||
navbar={
|
breakpoint: Constants.layout.mobileBreakpoint,
|
||||||
<Navbar
|
collapsed: { mobile: !mobileMenuOpen, desktop: !props.sidebarVisible },
|
||||||
id="sidebar"
|
}}
|
||||||
hiddenBreakpoint={sidebarHidden ? 99999999 : Constants.layout.mobileBreakpoint}
|
padding={{ base: 6, [Constants.layout.mobileBreakpointName]: "md" }}
|
||||||
hidden={sidebarHidden || !mobileMenuOpen}
|
|
||||||
width={{ md: props.sidebarWidth }}
|
|
||||||
className={classes.sidebar}
|
|
||||||
>
|
|
||||||
<Navbar.Section grow component={ScrollArea} mx={mobile ? 0 : "-sm"} px={mobile ? 0 : "sm"}>
|
|
||||||
<Resizable
|
|
||||||
enable={{
|
|
||||||
top: false,
|
|
||||||
right: !mobile,
|
|
||||||
bottom: false,
|
|
||||||
left: false,
|
|
||||||
topRight: false,
|
|
||||||
bottomRight: false,
|
|
||||||
bottomLeft: false,
|
|
||||||
topLeft: false,
|
|
||||||
}}
|
|
||||||
onResize={(e, dir, el) => handleResize(el)}
|
|
||||||
minWidth={120}
|
|
||||||
className={classes.sidebarContentResizeWrapper}
|
|
||||||
>
|
|
||||||
<Box className={classes.sidebarContent}>{props.sidebar}</Box>
|
|
||||||
</Resizable>
|
|
||||||
</Navbar.Section>
|
|
||||||
</Navbar>
|
|
||||||
}
|
|
||||||
header={
|
|
||||||
<Header id="header" height={Constants.layout.headerHeight} p="md">
|
|
||||||
<OnMobile>
|
|
||||||
{mobileMenuOpen && (
|
|
||||||
<Group position="apart">
|
|
||||||
<Box>{burger}</Box>
|
|
||||||
<Box>
|
|
||||||
<LogoAndTitle />
|
|
||||||
</Box>
|
|
||||||
<Box>{addButton}</Box>
|
|
||||||
</Group>
|
|
||||||
)}
|
|
||||||
{!mobileMenuOpen && (
|
|
||||||
<Group>
|
|
||||||
<Box>{burger}</Box>
|
|
||||||
<Box sx={{ flexGrow: 1 }}>{props.header}</Box>
|
|
||||||
</Group>
|
|
||||||
)}
|
|
||||||
</OnMobile>
|
|
||||||
<OnDesktop>
|
|
||||||
<Group>
|
|
||||||
<Group position="apart" sx={{ width: props.sidebarWidth - 16 }}>
|
|
||||||
<Box>
|
|
||||||
<LogoAndTitle />
|
|
||||||
</Box>
|
|
||||||
<Box>{addButton}</Box>
|
|
||||||
</Group>
|
|
||||||
<Box sx={{ flexGrow: 1 }}>{props.header}</Box>
|
|
||||||
</Group>
|
|
||||||
</OnDesktop>
|
|
||||||
</Header>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Box id="content" className={classes.mainContent}>
|
<AppShell.Header id="header">
|
||||||
|
<OnMobile>
|
||||||
|
{mobileMenuOpen && (
|
||||||
|
<Group justify="space-between" p="md">
|
||||||
|
<Box>{burger}</Box>
|
||||||
|
<Box>
|
||||||
|
<LogoAndTitle />
|
||||||
|
</Box>
|
||||||
|
<Box>{addButton}</Box>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
{!mobileMenuOpen && (
|
||||||
|
<Group p="md">
|
||||||
|
<Box>{burger}</Box>
|
||||||
|
<Box style={{ flexGrow: 1 }}>{props.header}</Box>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</OnMobile>
|
||||||
|
<OnDesktop>
|
||||||
|
<Group p="md">
|
||||||
|
<Group justify="space-between" style={{ width: props.sidebarWidth - 16 }}>
|
||||||
|
<Box>
|
||||||
|
<LogoAndTitle />
|
||||||
|
</Box>
|
||||||
|
<Box>{addButton}</Box>
|
||||||
|
</Group>
|
||||||
|
<Box style={{ flexGrow: 1 }}>{props.header}</Box>
|
||||||
|
</Group>
|
||||||
|
</OnDesktop>
|
||||||
|
</AppShell.Header>
|
||||||
|
<AppShell.Navbar id="sidebar" p="xs">
|
||||||
|
<AppShell.Section grow component={ScrollArea} mx="-sm" px="sm">
|
||||||
|
<Box>{props.sidebar}</Box>
|
||||||
|
</AppShell.Section>
|
||||||
|
</AppShell.Navbar>
|
||||||
|
<Draggable
|
||||||
|
axis="x"
|
||||||
|
defaultPosition={{
|
||||||
|
x: props.sidebarWidth,
|
||||||
|
y: Constants.layout.headerHeight,
|
||||||
|
}}
|
||||||
|
bounds={{
|
||||||
|
left: 120,
|
||||||
|
right: 1000,
|
||||||
|
}}
|
||||||
|
grid={[30, 30]}
|
||||||
|
onDrag={(_e, data) => {
|
||||||
|
dispatch(setSidebarWidth(data.x))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
height: "100%",
|
||||||
|
width: "10px",
|
||||||
|
cursor: "ew-resize",
|
||||||
|
}}
|
||||||
|
></Box>
|
||||||
|
</Draggable>
|
||||||
|
|
||||||
|
<AppShell.Main id="content">
|
||||||
<Suspense fallback={<Loader />}>
|
<Suspense fallback={<Loader />}>
|
||||||
<AnnouncementDialog />
|
<AnnouncementDialog />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Box>
|
</AppShell.Main>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ export function SettingsPage() {
|
|||||||
<Container size="sm" px={0}>
|
<Container size="sm" px={0}>
|
||||||
<Tabs defaultValue="display" keepMounted={false}>
|
<Tabs defaultValue="display" keepMounted={false}>
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
<Tabs.Tab value="display" icon={<TbPhoto size={16} />}>
|
<Tabs.Tab value="display" leftSection={<TbPhoto size={16} />}>
|
||||||
<Trans>Display</Trans>
|
<Trans>Display</Trans>
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
<Tabs.Tab value="customCode" icon={<TbCode size={16} />}>
|
<Tabs.Tab value="customCode" leftSection={<TbCode size={16} />}>
|
||||||
<Trans>Custom code</Trans>
|
<Trans>Custom code</Trans>
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
<Tabs.Tab value="profile" icon={<TbUser size={16} />}>
|
<Tabs.Tab value="profile" leftSection={<TbUser size={16} />}>
|
||||||
<Trans>Profile</Trans>
|
<Trans>Profile</Trans>
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export function LoginPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{serverInfos?.smtpEnabled && (
|
{serverInfos?.smtpEnabled && (
|
||||||
<Anchor component={Link} to="/passwordRecovery" color="dimmed">
|
<Anchor component={Link} to="/passwordRecovery" c="dimmed">
|
||||||
<Trans>Forgot password?</Trans>
|
<Trans>Forgot password?</Trans>
|
||||||
</Anchor>
|
</Anchor>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user