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