update to mantine 7

This commit is contained in:
Athou
2023-12-29 23:09:30 +01:00
parent 2742b7fff6
commit 7e02380858
52 changed files with 653 additions and 751 deletions

View File

@@ -13,13 +13,12 @@
"@lingui/core": "^4.6.0", "@lingui/core": "^4.6.0",
"@lingui/macro": "^4.6.0", "@lingui/macro": "^4.6.0",
"@lingui/react": "^4.6.0", "@lingui/react": "^4.6.0",
"@mantine/core": "^6.0.21", "@mantine/core": "^7.3.2",
"@mantine/form": "^6.0.21", "@mantine/form": "^7.3.2",
"@mantine/hooks": "^6.0.21", "@mantine/hooks": "^7.3.2",
"@mantine/modals": "^6.0.21", "@mantine/modals": "^7.3.2",
"@mantine/notifications": "^6.0.21", "@mantine/notifications": "^7.3.2",
"@mantine/spotlight": "^6.0.21", "@mantine/spotlight": "^7.3.2",
"@mantine/styles": "^6.0.21",
"@monaco-editor/react": "^4.6.0", "@monaco-editor/react": "^4.6.0",
"@reduxjs/toolkit": "^2.0.1", "@reduxjs/toolkit": "^2.0.1",
"axios": "^1.6.3", "axios": "^1.6.3",
@@ -28,11 +27,11 @@
"interweave": "^13.1.0", "interweave": "^13.1.0",
"monaco-editor": "^0.45.0", "monaco-editor": "^0.45.0",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"re-resizable": "^6.9.11",
"react": "^18.2.0", "react": "^18.2.0",
"react-async-hook": "^4.0.0", "react-async-hook": "^4.0.0",
"react-contexify": "^6.0.0", "react-contexify": "^6.0.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-draggable": "^4.4.6",
"react-ga4": "^2.1.0", "react-ga4": "^2.1.0",
"react-icons": "^4.12.0", "react-icons": "^4.12.0",
"react-infinite-scroller": "^1.2.6", "react-infinite-scroller": "^1.2.6",
@@ -1130,25 +1129,29 @@
"integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==" "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw=="
}, },
"node_modules/@floating-ui/core": { "node_modules/@floating-ui/core": {
"version": "1.2.6", "version": "1.5.2",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.6.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.2.tgz",
"integrity": "sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==" "integrity": "sha512-Ii3MrfY/GAIN3OhXNzpCKaLxHQfJF9qvwq/kEJYdqDxeIHa01K8sldugal6TmeeXl+WMvhv9cnVzUTaFFJF09A==",
"dependencies": {
"@floating-ui/utils": "^0.1.3"
}
}, },
"node_modules/@floating-ui/dom": { "node_modules/@floating-ui/dom": {
"version": "1.2.7", "version": "1.5.3",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.7.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz",
"integrity": "sha512-DyqylONj1ZaBnzj+uBnVfzdjjCkFCL2aA9ESHLyUOGSqb03RpbLMImP1ekIQXYs4KLk9jAjJfZAU8hXfWSahEg==", "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==",
"dependencies": { "dependencies": {
"@floating-ui/core": "^1.2.6" "@floating-ui/core": "^1.4.2",
"@floating-ui/utils": "^0.1.3"
} }
}, },
"node_modules/@floating-ui/react": { "node_modules/@floating-ui/react": {
"version": "0.19.2", "version": "0.24.8",
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.19.2.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.24.8.tgz",
"integrity": "sha512-JyNk4A0Ezirq8FlXECvRtQOX/iBe5Ize0W/pLkrZjfHW9GUV7Xnq6zm6fyZuQzaHHqEnVizmvlA96e1/CkZv+w==", "integrity": "sha512-AuYeDoaR8jtUlUXtZ1IJ/6jtBkGnSpJXbGNzokBL87VDJ8opMq1Bgrc0szhK482ReQY6KZsMoZCVSb4xwalkBA==",
"dependencies": { "dependencies": {
"@floating-ui/react-dom": "^1.3.0", "@floating-ui/react-dom": "^2.0.1",
"aria-hidden": "^1.1.3", "aria-hidden": "^1.2.3",
"tabbable": "^6.0.1" "tabbable": "^6.0.1"
}, },
"peerDependencies": { "peerDependencies": {
@@ -1157,17 +1160,22 @@
} }
}, },
"node_modules/@floating-ui/react-dom": { "node_modules/@floating-ui/react-dom": {
"version": "1.3.0", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz",
"integrity": "sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==", "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==",
"dependencies": { "dependencies": {
"@floating-ui/dom": "^1.2.1" "@floating-ui/dom": "^1.5.1"
}, },
"peerDependencies": { "peerDependencies": {
"react": ">=16.8.0", "react": ">=16.8.0",
"react-dom": ">=16.8.0" "react-dom": ">=16.8.0"
} }
}, },
"node_modules/@floating-ui/utils": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz",
"integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
},
"node_modules/@fontsource/open-sans": { "node_modules/@fontsource/open-sans": {
"version": "5.0.20", "version": "5.0.20",
"resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-5.0.20.tgz", "resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-5.0.20.tgz",
@@ -1490,111 +1498,108 @@
} }
}, },
"node_modules/@mantine/core": { "node_modules/@mantine/core": {
"version": "6.0.21", "version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-6.0.21.tgz", "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.3.2.tgz",
"integrity": "sha512-Kx4RrRfv0I+cOCIcsq/UA2aWcYLyXgW3aluAuW870OdXnbII6qg7RW28D+r9D76SHPxWFKwIKwmcucAG08Divg==", "integrity": "sha512-CwAuQogVLcLR7O9e1eOgi3gtk4XX6cnaqevAxzJJpIOIyCnHiQ3cEGINVXyUUjUUipBlvK3sqz3NPGJ2ekLFDQ==",
"dependencies": { "dependencies": {
"@floating-ui/react": "^0.19.1", "@floating-ui/react": "^0.24.8",
"@mantine/styles": "6.0.21", "clsx": "2.0.0",
"@mantine/utils": "6.0.21", "react-number-format": "^5.3.1",
"@radix-ui/react-scroll-area": "1.0.2", "react-remove-scroll": "^2.5.7",
"react-remove-scroll": "^2.5.5", "react-textarea-autosize": "8.5.3",
"react-textarea-autosize": "8.3.4" "type-fest": "^3.13.1"
}, },
"peerDependencies": { "peerDependencies": {
"@mantine/hooks": "6.0.21", "@mantine/hooks": "7.3.2",
"react": ">=16.8.0", "react": "^18.2.0",
"react-dom": ">=16.8.0" "react-dom": "^18.2.0"
}
},
"node_modules/@mantine/core/node_modules/clsx": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
"integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==",
"engines": {
"node": ">=6"
}
},
"node_modules/@mantine/core/node_modules/type-fest": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@mantine/form": { "node_modules/@mantine/form": {
"version": "6.0.21", "version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mantine/form/-/form-6.0.21.tgz", "resolved": "https://registry.npmjs.org/@mantine/form/-/form-7.3.2.tgz",
"integrity": "sha512-d4tlxyZic7MSDnaPx/WliCX1sRFDkUd2nxx4MxxO2T4OSek0YDqTlSBCxeoveu60P+vrQQN5rbbsVsaOJBe4SQ==", "integrity": "sha512-/qa1KQKVC46XWgIU190r3XM3Xld8Lsvz4L/an//TO67RnAGEdC5OCvr2JCb+fprZZi3YdxaKOkVNvP20W23qkg==",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"klona": "^2.0.5" "klona": "^2.0.6"
}, },
"peerDependencies": { "peerDependencies": {
"react": ">=16.8.0" "react": "^18.2.0"
} }
}, },
"node_modules/@mantine/hooks": { "node_modules/@mantine/hooks": {
"version": "6.0.21", "version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-6.0.21.tgz", "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.3.2.tgz",
"integrity": "sha512-sYwt5wai25W6VnqHbS5eamey30/HD5dNXaZuaVEAJ2i2bBv8C0cCiczygMDpAFiSYdXoSMRr/SZ2CrrPTzeNew==", "integrity": "sha512-xgumuuI3PBWXff5N02HCI7PEy25mDEdyXDQklUYK93J6FKwpcosyZnGVitoUrV1gLtYYa9ZudeAWdhHuh/CpOg==",
"peerDependencies": { "peerDependencies": {
"react": ">=16.8.0" "react": "^18.2.0"
} }
}, },
"node_modules/@mantine/modals": { "node_modules/@mantine/modals": {
"version": "6.0.21", "version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-6.0.21.tgz", "resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-7.3.2.tgz",
"integrity": "sha512-Gx2D/ZHMUuYF197JKMWey4K9FeGP9rxYp4lmAEXUrjXiST2fEhLZOdiD75KuOHXd1/sYAU9NcNRo9wXrlF/gUA==", "integrity": "sha512-vhpcp0Yqgm+K/vorDbuweTjzDO4pJaG2POc00cSTV3zJdsbeMAzVClovTuseJT+UO2lUdUP3RG1cInaZqSclhA==",
"dependencies": {
"@mantine/utils": "6.0.21"
},
"peerDependencies": { "peerDependencies": {
"@mantine/core": "6.0.21", "@mantine/core": "7.3.2",
"@mantine/hooks": "6.0.21", "@mantine/hooks": "7.3.2",
"react": ">=16.8.0", "react": "^18.2.0",
"react-dom": ">=16.8.0" "react-dom": "^18.2.0"
} }
}, },
"node_modules/@mantine/notifications": { "node_modules/@mantine/notifications": {
"version": "6.0.21", "version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-6.0.21.tgz", "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.3.2.tgz",
"integrity": "sha512-qsrqxuJHK8b67sf9Pfk+xyhvpf9jMsivW8vchfnJfjv7yz1lLvezjytMFp4fMDoYhjHnDPOEc/YFockK4muhOw==", "integrity": "sha512-XOzgm4pm4XszavVN0QUjN+IP0xiG2IochxJSz/FduTI0r3u1WxdpvDYlOvEJpHhtWvyqI8W8rx6cPJaD2HdAwQ==",
"dependencies": { "dependencies": {
"@mantine/utils": "6.0.21", "@mantine/store": "7.3.2",
"react-transition-group": "4.4.2" "react-transition-group": "4.4.5"
}, },
"peerDependencies": { "peerDependencies": {
"@mantine/core": "6.0.21", "@mantine/core": "7.3.2",
"@mantine/hooks": "6.0.21", "@mantine/hooks": "7.3.2",
"react": ">=16.8.0", "react": "^18.2.0",
"react-dom": ">=16.8.0" "react-dom": "^18.2.0"
} }
}, },
"node_modules/@mantine/spotlight": { "node_modules/@mantine/spotlight": {
"version": "6.0.21", "version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mantine/spotlight/-/spotlight-6.0.21.tgz", "resolved": "https://registry.npmjs.org/@mantine/spotlight/-/spotlight-7.3.2.tgz",
"integrity": "sha512-xJqF2Vpn8s6I4mSF+iCi7IzqL8iaqbvq0RcYlF1usLZYW2HrArX31s1r11DmzqM1PIuBQUhquW8jUXx/MZy3oA==", "integrity": "sha512-wvrIj7ZZKoVwKFxgY+KvWilu1YYdkv8HDUzZzRxOlD9fjPyyMRgBxAdVkxA4sLbol4XoCpW83dNIiXDII4httw==",
"dependencies": { "dependencies": {
"@mantine/utils": "6.0.21" "@mantine/store": "7.3.2"
}, },
"peerDependencies": { "peerDependencies": {
"@mantine/core": "6.0.21", "@mantine/core": "7.3.2",
"@mantine/hooks": "6.0.21", "@mantine/hooks": "7.3.2",
"react": ">=16.8.0", "react": "^18.2.0",
"react-dom": ">=16.8.0" "react-dom": "^18.2.0"
} }
}, },
"node_modules/@mantine/styles": { "node_modules/@mantine/store": {
"version": "6.0.21", "version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mantine/styles/-/styles-6.0.21.tgz", "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.3.2.tgz",
"integrity": "sha512-PVtL7XHUiD/B5/kZ/QvZOZZQQOj12QcRs3Q6nPoqaoPcOX5+S7bMZLMH0iLtcGq5OODYk0uxlvuJkOZGoPj8Mg==", "integrity": "sha512-M1eWHzTRCeCFvrpFhXKIM9zblrlIT5/XrMue/fP2HrkA43dpkgq+ArnZkN3LhG9lWR/EKbRwQWDhDIvdLtfD7w==",
"dependencies": {
"clsx": "1.1.1",
"csstype": "3.0.9"
},
"peerDependencies": { "peerDependencies": {
"@emotion/react": ">=11.9.0", "react": "^18.2.0"
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@mantine/styles/node_modules/csstype": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz",
"integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw=="
},
"node_modules/@mantine/utils": {
"version": "6.0.21",
"resolved": "https://registry.npmjs.org/@mantine/utils/-/utils-6.0.21.tgz",
"integrity": "sha512-33RVDRop5jiWFao3HKd3Yp7A9mEq4HAJxJPTuYm1NkdqX6aTKOQK7wT8v8itVodBp+sb4cJK6ZVdD1UurK/txQ==",
"peerDependencies": {
"react": ">=16.8.0"
} }
}, },
"node_modules/@messageformat/parser": { "node_modules/@messageformat/parser": {
@@ -1676,137 +1681,6 @@
"url": "https://opencollective.com/unts" "url": "https://opencollective.com/unts"
} }
}, },
"node_modules/@radix-ui/number": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.0.tgz",
"integrity": "sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==",
"dependencies": {
"@babel/runtime": "^7.13.10"
}
},
"node_modules/@radix-ui/primitive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
"integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
"dependencies": {
"@babel/runtime": "^7.13.10"
}
},
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
"integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-context": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
"integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-direction": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz",
"integrity": "sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-presence": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz",
"integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-use-layout-effect": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-primitive": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.1.tgz",
"integrity": "sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-slot": "1.0.1"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-scroll-area": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.2.tgz",
"integrity": "sha512-k8VseTxI26kcKJaX0HPwkvlNBPTs56JRdYzcZ/vzrNUkDlvXBy8sMc7WvCpYzZkHgb+hd72VW9MqkqecGtuNgg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/number": "1.0.0",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-direction": "1.0.0",
"@radix-ui/react-presence": "1.0.0",
"@radix-ui/react-primitive": "1.0.1",
"@radix-ui/react-use-callback-ref": "1.0.0",
"@radix-ui/react-use-layout-effect": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
"integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
"integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
"integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@redocly/ajv": { "node_modules/@redocly/ajv": {
"version": "8.11.0", "version": "8.11.0",
"resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.0.tgz", "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.0.tgz",
@@ -6733,15 +6607,6 @@
"integrity": "sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==", "integrity": "sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==",
"dev": true "dev": true
}, },
"node_modules/re-resizable": {
"version": "6.9.11",
"resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.11.tgz",
"integrity": "sha512-a3hiLWck/NkmyLvGWUuvkAmN1VhwAz4yOhS6FdMTaxCUVN9joIWkT11wsO68coG/iEYuwn+p/7qAmfQzRhiPLQ==",
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react": { "node_modules/react": {
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
@@ -6797,6 +6662,19 @@
"react": "^18.2.0" "react": "^18.2.0"
} }
}, },
"node_modules/react-draggable": {
"version": "4.4.6",
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz",
"integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==",
"dependencies": {
"clsx": "^1.1.1",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"react": ">= 16.3.0",
"react-dom": ">= 16.3.0"
}
},
"node_modules/react-ga4": { "node_modules/react-ga4": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz",
@@ -6826,6 +6704,18 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"node_modules/react-number-format": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.3.1.tgz",
"integrity": "sha512-qpYcQLauIeEhCZUZY9jXZnnroOtdy3jYaS1zQ3M1Sr6r/KMOBEIGNIb7eKT19g2N1wbYgFgvDzs19hw5TrB8XQ==",
"dependencies": {
"prop-types": "^15.7.2"
},
"peerDependencies": {
"react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-redux": { "node_modules/react-redux": {
"version": "9.0.4", "version": "9.0.4",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.0.4.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.0.4.tgz",
@@ -6862,9 +6752,9 @@
} }
}, },
"node_modules/react-remove-scroll": { "node_modules/react-remove-scroll": {
"version": "2.5.6", "version": "2.5.7",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.6.tgz", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz",
"integrity": "sha512-bO856ad1uDYLefgArk559IzUNeQ6SWH4QnrevIUjH+GczV56giDfl3h0Idptf2oIKxQmd1p9BN25jleKodTALg==", "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==",
"dependencies": { "dependencies": {
"react-remove-scroll-bar": "^2.3.4", "react-remove-scroll-bar": "^2.3.4",
"react-style-singleton": "^2.2.1", "react-style-singleton": "^2.2.1",
@@ -6979,11 +6869,11 @@
} }
}, },
"node_modules/react-textarea-autosize": { "node_modules/react-textarea-autosize": {
"version": "8.3.4", "version": "8.5.3",
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.3.4.tgz", "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz",
"integrity": "sha512-CdtmP8Dc19xL8/R6sWvtknD/eCXkQr30dtvC4VmGInhRsfF8X/ihXCq6+9l9qbxmKRiq407/7z5fxE7cVWQNgQ==", "integrity": "sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.10.2", "@babel/runtime": "^7.20.13",
"use-composed-ref": "^1.3.0", "use-composed-ref": "^1.3.0",
"use-latest": "^1.2.1" "use-latest": "^1.2.1"
}, },
@@ -6995,9 +6885,9 @@
} }
}, },
"node_modules/react-transition-group": { "node_modules/react-transition-group": {
"version": "4.4.2", "version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1", "dom-helpers": "^5.0.1",
@@ -7875,9 +7765,9 @@
} }
}, },
"node_modules/tabbable": { "node_modules/tabbable": {
"version": "6.1.2", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.1.2.tgz", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
"integrity": "sha512-qCN98uP7i9z0fIS4amQ5zbGBOq+OSigYeGvPy7NDk8Y9yncqDZ9pRPgfsc2PJIVM9RrJj7GIfuRgmjoUU9zTHQ==" "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
}, },
"node_modules/text-table": { "node_modules/text-table": {
"version": "0.2.0", "version": "0.2.0",
@@ -8240,9 +8130,9 @@
"integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="
}, },
"node_modules/use-callback-ref": { "node_modules/use-callback-ref": {
"version": "1.3.0", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz",
"integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==", "integrity": "sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==",
"dependencies": { "dependencies": {
"tslib": "^2.0.0" "tslib": "^2.0.0"
}, },

View File

@@ -19,13 +19,12 @@
"@lingui/core": "^4.6.0", "@lingui/core": "^4.6.0",
"@lingui/macro": "^4.6.0", "@lingui/macro": "^4.6.0",
"@lingui/react": "^4.6.0", "@lingui/react": "^4.6.0",
"@mantine/core": "^6.0.21", "@mantine/core": "^7.3.2",
"@mantine/form": "^6.0.21", "@mantine/form": "^7.3.2",
"@mantine/hooks": "^6.0.21", "@mantine/hooks": "^7.3.2",
"@mantine/modals": "^6.0.21", "@mantine/modals": "^7.3.2",
"@mantine/notifications": "^6.0.21", "@mantine/notifications": "^7.3.2",
"@mantine/spotlight": "^6.0.21", "@mantine/spotlight": "^7.3.2",
"@mantine/styles": "^6.0.21",
"@monaco-editor/react": "^4.6.0", "@monaco-editor/react": "^4.6.0",
"@reduxjs/toolkit": "^2.0.1", "@reduxjs/toolkit": "^2.0.1",
"axios": "^1.6.3", "axios": "^1.6.3",
@@ -34,11 +33,11 @@
"interweave": "^13.1.0", "interweave": "^13.1.0",
"monaco-editor": "^0.45.0", "monaco-editor": "^0.45.0",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"re-resizable": "^6.9.11",
"react": "^18.2.0", "react": "^18.2.0",
"react-async-hook": "^4.0.0", "react-async-hook": "^4.0.0",
"react-contexify": "^6.0.0", "react-contexify": "^6.0.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-draggable": "^4.4.6",
"react-ga4": "^2.1.0", "react-ga4": "^2.1.0",
"react-icons": "^4.12.0", "react-icons": "^4.12.0",
"react-infinite-scroller": "^1.2.6", "react-infinite-scroller": "^1.2.6",

View File

@@ -1,7 +1,6 @@
import { i18n } from "@lingui/core" import { i18n } from "@lingui/core"
import { I18nProvider } from "@lingui/react" import { I18nProvider } from "@lingui/react"
import { type ColorScheme, ColorSchemeProvider, MantineProvider } from "@mantine/core" import { MantineProvider } from "@mantine/core"
import { useColorScheme } from "@mantine/hooks"
import { ModalsProvider } from "@mantine/modals" import { ModalsProvider } from "@mantine/modals"
import { Notifications } from "@mantine/notifications" import { Notifications } from "@mantine/notifications"
import { Constants } from "app/constants" import { Constants } from "app/constants"
@@ -33,31 +32,37 @@ import React, { useEffect } from "react"
import ReactGA from "react-ga4" import ReactGA from "react-ga4"
import { HashRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom" import { HashRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom"
import Tinycon from "tinycon" import Tinycon from "tinycon"
import useLocalStorage from "use-local-storage"
function Providers(props: { children: React.ReactNode }) { function Providers(props: { children: React.ReactNode }) {
const preferredColorScheme = useColorScheme()
const [colorScheme, setColorScheme] = useLocalStorage<ColorScheme>("color-scheme", preferredColorScheme)
const toggleColorScheme = (value?: ColorScheme) => setColorScheme(value ?? (colorScheme === "dark" ? "light" : "dark"))
return ( return (
<I18nProvider i18n={i18n}> <I18nProvider i18n={i18n}>
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}> <MantineProvider
<MantineProvider theme={{
withGlobalStyles primaryColor: "orange",
withNormalizeCSS fontFamily: "Open Sans",
theme={{ colors: {
primaryColor: "orange", // keep using dark colors from mantine v6
colorScheme, // https://v6.mantine.dev/theming/colors/#default-colors
fontFamily: "Open Sans", dark: [
}} "#C1C2C5",
> "#A6A7AB",
<ModalsProvider> "#909296",
<Notifications position="bottom-right" zIndex={9999} /> "#5c5f66",
<ErrorBoundary>{props.children}</ErrorBoundary> "#373A40",
</ModalsProvider> "#2C2E33",
</MantineProvider> "#25262b",
</ColorSchemeProvider> "#1A1B1E",
"#141517",
"#101113",
],
},
}}
>
<ModalsProvider>
<Notifications position="bottom-right" zIndex={9999} />
<ErrorBoundary>{props.children}</ErrorBoundary>
</ModalsProvider>
</MantineProvider>
</I18nProvider> </I18nProvider>
) )
} }
@@ -77,7 +82,10 @@ function AppRoutes() {
<Route path="register" element={<RegistrationPage />} /> <Route path="register" element={<RegistrationPage />} />
<Route path="passwordRecovery" element={<PasswordRecoveryPage />} /> <Route path="passwordRecovery" element={<PasswordRecoveryPage />} />
<Route path="api" element={<ApiDocumentationPage />} /> <Route path="api" element={<ApiDocumentationPage />} />
<Route path="app" element={<Layout header={<Header />} sidebar={<Tree />} sidebarWidth={sidebarVisible ? sidebarWidth : 0} />}> <Route
path="app"
element={<Layout header={<Header />} sidebar={<Tree />} sidebarWidth={sidebarWidth} sidebarVisible={sidebarVisible} />}
>
<Route path="category"> <Route path="category">
<Route path=":id" element={<FeedEntriesPage sourceType="category" />} /> <Route path=":id" element={<FeedEntriesPage sourceType="category" />} />
<Route path=":id/details" element={<CategoryDetailsPage />} /> <Route path=":id/details" element={<CategoryDetailsPage />} />

View File

@@ -1,5 +1,4 @@
import { t } from "@lingui/macro" import { t } from "@lingui/macro"
import { DEFAULT_THEME } from "@mantine/core"
import { type IconType } from "react-icons" import { type IconType } from "react-icons"
import { FaAt } from "react-icons/fa" import { FaAt } from "react-icons/fa"
import { SiBuffer, SiFacebook, SiGmail, SiInstapaper, SiPocket, SiTumblr, SiTwitter } from "react-icons/si" import { SiBuffer, SiFacebook, SiGmail, SiInstapaper, SiPocket, SiTumblr, SiTwitter } from "react-icons/si"
@@ -86,7 +85,8 @@ export const Constants = {
categories, categories,
sharing, sharing,
layout: { layout: {
mobileBreakpoint: DEFAULT_THEME.breakpoints.md, mobileBreakpoint: 992,
mobileBreakpointName: "md",
headerHeight: 60, headerHeight: 60,
entryMaxWidth: 650, entryMaxWidth: 650,
isTopVisible: (div: HTMLElement) => div.getBoundingClientRect().top >= Constants.layout.headerHeight, isTopVisible: (div: HTMLElement) => div.getBoundingClientRect().top >= Constants.layout.headerHeight,

View File

@@ -1,6 +1,5 @@
import { ActionIcon, Button, Tooltip, useMantineTheme } from "@mantine/core" import { ActionIcon, Button, type ButtonVariant, Tooltip, useMantineTheme } from "@mantine/core"
import { type ActionIconProps } from "@mantine/core/lib/ActionIcon/ActionIcon" import { type ActionIconVariant } from "@mantine/core/lib/components/ActionIcon/ActionIcon"
import { type ButtonProps } from "@mantine/core/lib/Button/Button"
import { useActionButton } from "hooks/useActionButton" import { useActionButton } from "hooks/useActionButton"
import { forwardRef, type MouseEventHandler, type ReactNode } from "react" import { forwardRef, type MouseEventHandler, type ReactNode } from "react"
@@ -9,7 +8,7 @@ interface ActionButtonProps {
icon?: ReactNode icon?: ReactNode
label: ReactNode label: ReactNode
onClick?: MouseEventHandler onClick?: MouseEventHandler
variant?: ActionIconProps["variant"] & ButtonProps["variant"] variant?: ActionIconVariant & ButtonVariant
hideLabelOnDesktop?: boolean hideLabelOnDesktop?: boolean
showLabelOnMobile?: boolean showLabelOnMobile?: boolean
} }
@@ -29,7 +28,7 @@ export const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>((pr
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
) : ( ) : (
<Button ref={ref} variant={variant} size="xs" className={props.className} leftIcon={props.icon} onClick={props.onClick}> <Button ref={ref} variant={variant} size="xs" className={props.className} leftSection={props.icon} onClick={props.onClick}>
{props.label} {props.label}
</Button> </Button>
) )

View File

@@ -24,7 +24,7 @@ export function AnnouncementDialog() {
return ( return (
<Dialog opened={opened} withCloseButton onClose={onClosed} size="xl" radius="md"> <Dialog opened={opened} withCloseButton onClose={onClosed} size="xl" radius="md">
<Box> <Box>
<Text weight="bold"> <Text fw="bold">
<Trans>Announcement</Trans> <Trans>Announcement</Trans>
</Text> </Text>
</Box> </Box>

View File

@@ -1,4 +1,5 @@
import { Box, Center, type MantineTheme, useMantineTheme } from "@mantine/core" import { Box, Center, type MantineTheme, useMantineTheme } from "@mantine/core"
import { useColorScheme } from "hooks/useColorScheme"
import { useState } from "react" import { useState } from "react"
import { TbPhoto } from "react-icons/tb" import { TbPhoto } from "react-icons/tb"
import { tss } from "tss" import { tss } from "tss"
@@ -19,6 +20,7 @@ interface ImageWithPlaceholderWhileLoadingProps {
const useStyles = tss const useStyles = tss
.withParams<{ .withParams<{
theme: MantineTheme theme: MantineTheme
colorScheme: "light" | "dark"
placeholderWidth?: number placeholderWidth?: number
placeholderHeight?: number placeholderHeight?: number
placeholderBackgroundColor?: string placeholderBackgroundColor?: string
@@ -31,13 +33,14 @@ const useStyles = tss
maxWidth: "100%", maxWidth: "100%",
color: color:
props.placeholderIconColor ?? props.placeholderIconColor ??
props.theme.fn.variant({ props.theme.variantColorResolver({
theme: props.theme,
color: props.theme.primaryColor, color: props.theme.primaryColor,
variant: "subtle", variant: "subtle",
}).color, }).color,
backgroundColor: backgroundColor:
props.placeholderBackgroundColor ?? props.placeholderBackgroundColor ??
(props.theme.colorScheme === "dark" ? props.theme.colors.dark[5] : props.theme.colors.gray[1]), (props.colorScheme === "dark" ? props.theme.colors.dark[5] : props.theme.colors.gray[1]),
}, },
})) }))
@@ -54,8 +57,10 @@ export function ImageWithPlaceholderWhileLoading({
width, width,
}: ImageWithPlaceholderWhileLoadingProps) { }: ImageWithPlaceholderWhileLoadingProps) {
const theme = useMantineTheme() const theme = useMantineTheme()
const colorScheme = useColorScheme()
const { classes } = useStyles({ const { classes } = useStyles({
theme, theme,
colorScheme,
placeholderWidth, placeholderWidth,
placeholderHeight, placeholderHeight,
placeholderBackgroundColor, placeholderBackgroundColor,

View File

@@ -4,64 +4,64 @@ import { Constants } from "app/constants"
export function KeyboardShortcutsHelp() { export function KeyboardShortcutsHelp() {
return ( return (
<Stack spacing="xs"> <Stack gap="xs">
<Table striped highlightOnHover> <Table striped highlightOnHover>
<tbody> <Table.Tbody>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Refresh</Trans> <Trans>Refresh</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd>R</Kbd> <Kbd>R</Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Open next entry</Trans> <Trans>Open next entry</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd>J</Kbd> <Kbd>J</Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Open previous entry</Trans> <Trans>Open previous entry</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd>K</Kbd> <Kbd>K</Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Set focus on next entry without opening it</Trans> <Trans>Set focus on next entry without opening it</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd>N</Kbd> <Kbd>N</Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Set focus on previous entry without opening it</Trans> <Trans>Set focus on previous entry without opening it</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd>P</Kbd> <Kbd>P</Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Move the page down</Trans> <Trans>Move the page down</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd> <Kbd>
<Trans>Space</Trans> <Trans>Space</Trans>
</Kbd> </Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Move the page up</Trans> <Trans>Move the page up</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd> <Kbd>
<Trans>Shift</Trans> <Trans>Shift</Trans>
</Kbd> </Kbd>
@@ -69,85 +69,85 @@ export function KeyboardShortcutsHelp() {
<Kbd> <Kbd>
<Trans>Space</Trans> <Trans>Space</Trans>
</Kbd> </Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Open/close current entry</Trans> <Trans>Open/close current entry</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd>O</Kbd> <Kbd>O</Kbd>
<span>, </span> <span>, </span>
<Kbd> <Kbd>
<Trans>Enter</Trans> <Trans>Enter</Trans>
</Kbd> </Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Open current entry in a new tab</Trans> <Trans>Open current entry in a new tab</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd>V</Kbd> <Kbd>V</Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Open current entry in a new tab in the background</Trans> <Trans>Open current entry in a new tab in the background</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd>B</Kbd> <Kbd>B</Kbd>
<span>*, </span> <span>*, </span>
<Kbd> <Kbd>
<Trans>Middle click</Trans> <Trans>Middle click</Trans>
</Kbd> </Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Toggle read status of current entry</Trans> <Trans>Toggle read status of current entry</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd>M</Kbd> <Kbd>M</Kbd>
<span>, </span> <span>, </span>
<Trans>Swipe header to the right</Trans> <Trans>Swipe header to the right</Trans>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Toggle starred status of current entry</Trans> <Trans>Toggle starred status of current entry</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd>S</Kbd> <Kbd>S</Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Mark all entries as read</Trans> <Trans>Mark all entries as read</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd> <Kbd>
<Trans>Shift</Trans> <Trans>Shift</Trans>
</Kbd> </Kbd>
<span> + </span> <span> + </span>
<Kbd>A</Kbd> <Kbd>A</Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Go to the All view</Trans> <Trans>Go to the All view</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd>G</Kbd> <Kbd>G</Kbd>
<span> </span> <span> </span>
<Kbd>A</Kbd> <Kbd>A</Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Navigate to a subscription by entering its name</Trans> <Trans>Navigate to a subscription by entering its name</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd> <Kbd>
<Trans>Ctrl</Trans> <Trans>Ctrl</Trans>
</Kbd> </Kbd>
@@ -157,23 +157,23 @@ export function KeyboardShortcutsHelp() {
<Kbd>G</Kbd> <Kbd>G</Kbd>
<span> </span> <span> </span>
<Kbd>U</Kbd> <Kbd>U</Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Show entry menu (desktop)</Trans> <Trans>Show entry menu (desktop)</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd> <Kbd>
<Trans>Right click</Trans> <Trans>Right click</Trans>
</Kbd> </Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Show native menu (desktop)</Trans> <Trans>Show native menu (desktop)</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd> <Kbd>
<Trans>Shift</Trans> <Trans>Shift</Trans>
</Kbd> </Kbd>
@@ -181,35 +181,35 @@ export function KeyboardShortcutsHelp() {
<Kbd> <Kbd>
<Trans>Right click</Trans> <Trans>Right click</Trans>
</Kbd> </Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Show entry menu (mobile)</Trans> <Trans>Show entry menu (mobile)</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd> <Kbd>
<Trans>Long press</Trans> <Trans>Long press</Trans>
</Kbd> </Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Toggle sidebar</Trans> <Trans>Toggle sidebar</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd>F</Kbd> <Kbd>F</Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
<tr> <Table.Tr>
<td> <Table.Td>
<Trans>Show keyboard shortcut help</Trans> <Trans>Show keyboard shortcut help</Trans>
</td> </Table.Td>
<td> <Table.Td>
<Kbd>?</Kbd> <Kbd>?</Kbd>
</td> </Table.Td>
</tr> </Table.Tr>
</tbody> </Table.Tbody>
</Table> </Table>
<Box> <Box>
<span>* </span> <span>* </span>

View File

@@ -3,7 +3,7 @@ import { Center, Loader as MantineLoader } from "@mantine/core"
export function Loader() { export function Loader() {
return ( return (
<Center> <Center>
<MantineLoader size="xl" variant="bars" /> <MantineLoader size="lg" type="bars" />
</Center> </Center>
) )
} }

View File

@@ -6,5 +6,5 @@ export interface LogoProps {
} }
export function Logo(props: LogoProps) { export function Logo(props: LogoProps) {
return <Image src={logo} width={props.size} /> return <Image src={logo} w={props.size} />
} }

View File

@@ -43,7 +43,7 @@ export function UserEdit(props: UserEditProps) {
<Button variant="default" onClick={props.onCancel}> <Button variant="default" onClick={props.onCancel}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Button> </Button>
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveUser.loading}> <Button type="submit" leftSection={<TbDeviceFloppy size={16} />} loading={saveUser.loading}>
<Trans>Save</Trans> <Trans>Save</Trans>
</Button> </Button>
</Group> </Group>

View File

@@ -1,5 +1,5 @@
import { useMantineTheme } from "@mantine/core"
import { Loader } from "components/Loader" import { Loader } from "components/Loader"
import { useColorScheme } from "hooks/useColorScheme"
import { useAsync } from "react-async-hook" import { useAsync } from "react-async-hook"
const init = async () => { const init = async () => {
@@ -32,8 +32,8 @@ interface RichCodeEditorProps {
} }
function RichCodeEditor(props: RichCodeEditorProps) { function RichCodeEditor(props: RichCodeEditorProps) {
const theme = useMantineTheme() const colorScheme = useColorScheme()
const editorTheme = theme.colorScheme === "dark" ? "vs-dark" : "light" const editorTheme = colorScheme === "dark" ? "vs-dark" : "light"
const { result: Editor } = useAsync(init, []) const { result: Editor } = useAsync(init, [])
if (!Editor) return <Loader /> if (!Editor) return <Loader />

View 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>
}

View File

@@ -1,6 +1,7 @@
import { Box, type MantineTheme, Mark, TypographyStylesProvider, useMantineTheme } from "@mantine/core" import { Box, type MantineTheme, Mark, useMantineTheme } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { calculatePlaceholderSize } from "app/utils" import { calculatePlaceholderSize } from "app/utils"
import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading" import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
import escapeStringRegexp from "escape-string-regexp" import escapeStringRegexp from "escape-string-regexp"
import { type ChildrenNode, Interweave, Matcher, type MatchResponse, type Node, type TransformCallback } from "interweave" import { type ChildrenNode, Interweave, Matcher, type MatchResponse, type Node, type TransformCallback } from "interweave"
@@ -21,7 +22,11 @@ const useStyles = tss
// break long links or long words // break long links or long words
overflowWrap: "anywhere", overflowWrap: "anywhere",
"& a": { "& a": {
color: theme.fn.variant({ color: theme.primaryColor, variant: "subtle" }).color, color: theme.variantColorResolver({
theme,
color: theme.primaryColor,
variant: "subtle",
}).color,
}, },
"& iframe": { "& iframe": {
maxWidth: "100%", maxWidth: "100%",
@@ -96,11 +101,11 @@ const Content = React.memo((props: ContentProps) => {
const matchers = props.highlight ? [new HighlightMatcher(props.highlight)] : [] const matchers = props.highlight ? [new HighlightMatcher(props.highlight)] : []
return ( return (
<TypographyStylesProvider> <BasicHtmlStyles>
<Box className={classes.content}> <Box className={classes.content}>
<Interweave content={props.content} transform={transform} matchers={matchers} /> <Interweave content={props.content} transform={transform} matchers={matchers} />
</Box> </Box>
</TypographyStylesProvider> </BasicHtmlStyles>
) )
}) })
Content.displayName = "Content" Content.displayName = "Content"

View File

@@ -1,13 +1,13 @@
import { TypographyStylesProvider } from "@mantine/core" import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading" import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
export function Enclosure(props: { enclosureType: string; enclosureUrl: string }) { export function Enclosure(props: { enclosureType: string; enclosureUrl: string }) {
const hasVideo = props.enclosureType && props.enclosureType.indexOf("video") === 0 const hasVideo = props.enclosureType?.startsWith("video")
const hasAudio = props.enclosureType && props.enclosureType.indexOf("audio") === 0 const hasAudio = props.enclosureType?.startsWith("audio")
const hasImage = props.enclosureType && props.enclosureType.indexOf("image") === 0 const hasImage = props.enclosureType?.startsWith("image")
return ( return (
<TypographyStylesProvider> <BasicHtmlStyles>
{hasVideo && ( {hasVideo && (
<video controls> <video controls>
<source src={props.enclosureUrl} type={props.enclosureType} /> <source src={props.enclosureUrl} type={props.enclosureType} />
@@ -19,6 +19,6 @@ export function Enclosure(props: { enclosureType: string; enclosureUrl: string }
</audio> </audio>
)} )}
{hasImage && <ImageWithPlaceholderWhileLoading src={props.enclosureUrl} alt="enclosure" />} {hasImage && <ImageWithPlaceholderWhileLoading src={props.enclosureUrl} alt="enclosure" />}
</TypographyStylesProvider> </BasicHtmlStyles>
) )
} }

View File

@@ -1,7 +1,7 @@
import { Box, Divider, type MantineTheme, Paper, useMantineTheme } from "@mantine/core" import { Box, Divider, type MantineRadius, type MantineSpacing, type MantineTheme, Paper, useMantineTheme } from "@mantine/core"
import { type MantineNumberSize } from "@mantine/styles"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { type Entry, type ViewMode } from "app/types" import { type Entry, type ViewMode } from "app/types"
import { useColorScheme } from "hooks/useColorScheme"
import { useViewMode } from "hooks/useViewMode" import { useViewMode } from "hooks/useViewMode"
import React from "react" import React from "react"
import { useSwipeable } from "react-swipeable" import { useSwipeable } from "react-swipeable"
@@ -27,6 +27,7 @@ interface FeedEntryProps {
const useStyles = tss const useStyles = tss
.withParams<{ .withParams<{
theme: MantineTheme theme: MantineTheme
colorScheme: "light" | "dark"
read: boolean read: boolean
expanded: boolean expanded: boolean
viewMode: ViewMode viewMode: ViewMode
@@ -34,9 +35,9 @@ const useStyles = tss
showSelectionIndicator: boolean showSelectionIndicator: boolean
maxWidth?: number maxWidth?: number
}>() }>()
.create(({ theme, read, expanded, viewMode, rtl, showSelectionIndicator, maxWidth }) => { .create(({ theme, colorScheme, read, expanded, viewMode, rtl, showSelectionIndicator, maxWidth }) => {
let backgroundColor let backgroundColor
if (theme.colorScheme === "dark") { if (colorScheme === "dark") {
backgroundColor = read ? "inherit" : theme.colors.dark[5] backgroundColor = read ? "inherit" : theme.colors.dark[5]
} else { } else {
backgroundColor = read && !expanded ? theme.colors.gray[0] : "inherit" backgroundColor = read && !expanded ? theme.colors.gray[0] : "inherit"
@@ -58,12 +59,12 @@ const useStyles = tss
let backgroundHoverColor = backgroundColor let backgroundHoverColor = backgroundColor
if (!expanded && !read) { if (!expanded && !read) {
backgroundHoverColor = theme.colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[1] backgroundHoverColor = colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[1]
} }
let paperBorderLeftColor let paperBorderLeftColor
if (showSelectionIndicator) { if (showSelectionIndicator) {
const borderLeftColor = theme.colorScheme === "dark" ? theme.colors.orange[4] : theme.colors.orange[6] const borderLeftColor = colorScheme === "dark" ? theme.colors[theme.primaryColor][4] : theme.colors[theme.primaryColor][6]
paperBorderLeftColor = `${borderLeftColor} !important` paperBorderLeftColor = `${borderLeftColor} !important`
} }
@@ -73,7 +74,7 @@ const useStyles = tss
borderLeftColor: paperBorderLeftColor, borderLeftColor: paperBorderLeftColor,
marginTop: marginY, marginTop: marginY,
marginBottom: marginY, marginBottom: marginY,
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: { [`@media (max-width: ${Constants.layout.mobileBreakpoint}px)`]: {
marginTop: mobileMarginY, marginTop: mobileMarginY,
marginBottom: mobileMarginY, marginBottom: mobileMarginY,
}, },
@@ -96,9 +97,11 @@ const useStyles = tss
export function FeedEntry(props: FeedEntryProps) { export function FeedEntry(props: FeedEntryProps) {
const theme = useMantineTheme() const theme = useMantineTheme()
const colorScheme = useColorScheme()
const { viewMode } = useViewMode() const { viewMode } = useViewMode()
const { classes, cx } = useStyles({ const { classes, cx } = useStyles({
theme, theme,
colorScheme,
read: props.entry.read, read: props.entry.read,
expanded: props.expanded, expanded: props.expanded,
viewMode, viewMode,
@@ -111,17 +114,17 @@ export function FeedEntry(props: FeedEntryProps) {
onSwipedRight: props.onSwipedRight, onSwipedRight: props.onSwipedRight,
}) })
let paddingX: MantineNumberSize = "xs" let paddingX: MantineSpacing = "xs"
if (viewMode === "title" || viewMode === "cozy") paddingX = 6 if (viewMode === "title" || viewMode === "cozy") paddingX = 6
let paddingY: MantineNumberSize = "xs" let paddingY: MantineSpacing = "xs"
if (viewMode === "title") { if (viewMode === "title") {
paddingY = 4 paddingY = 4
} else if (viewMode === "cozy") { } else if (viewMode === "cozy") {
paddingY = 8 paddingY = 8
} }
let borderRadius: MantineNumberSize = "sm" let borderRadius: MantineRadius = "sm"
if (viewMode === "title") { if (viewMode === "title") {
borderRadius = 0 borderRadius = 0
} else if (viewMode === "cozy") { } else if (viewMode === "cozy") {

View File

@@ -1,7 +1,8 @@
import { Box, type MantineTheme, Text, useMantineTheme } from "@mantine/core" import { Box, Text } from "@mantine/core"
import { type Entry } from "app/types" import { type Entry } from "app/types"
import { RelativeDate } from "components/RelativeDate" import { RelativeDate } from "components/RelativeDate"
import { OnDesktop } from "components/responsive/OnDesktop" import { OnDesktop } from "components/responsive/OnDesktop"
import { useColorScheme } from "hooks/useColorScheme"
import { tss } from "tss" import { tss } from "tss"
import { FeedEntryTitle } from "./FeedEntryTitle" import { FeedEntryTitle } from "./FeedEntryTitle"
import { FeedFavicon } from "./FeedFavicon" import { FeedFavicon } from "./FeedFavicon"
@@ -12,10 +13,10 @@ export interface FeedEntryHeaderProps {
const useStyles = tss const useStyles = tss
.withParams<{ .withParams<{
theme: MantineTheme colorScheme: "light" | "dark"
read: boolean read: boolean
}>() }>()
.create(({ read, theme }) => ({ .create(({ colorScheme, read }) => ({
wrapper: { wrapper: {
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
@@ -23,7 +24,7 @@ const useStyles = tss
}, },
title: { title: {
flexGrow: 1, flexGrow: 1,
fontWeight: theme.colorScheme === "light" && !read ? "bold" : "inherit", fontWeight: colorScheme === "light" && !read ? "bold" : "inherit",
whiteSpace: "nowrap", whiteSpace: "nowrap",
overflow: "hidden", overflow: "hidden",
textOverflow: "ellipsis", textOverflow: "ellipsis",
@@ -41,9 +42,9 @@ const useStyles = tss
})) }))
export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) { export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) {
const theme = useMantineTheme() const colorScheme = useColorScheme()
const { classes } = useStyles({ const { classes } = useStyles({
theme, colorScheme,
read: props.entry.read, read: props.entry.read,
}) })
return ( return (
@@ -52,7 +53,7 @@ export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) {
<FeedFavicon url={props.entry.iconUrl} /> <FeedFavicon url={props.entry.iconUrl} />
</Box> </Box>
<OnDesktop> <OnDesktop>
<Text color="dimmed" className={classes.feedName}> <Text c="dimmed" className={classes.feedName}>
{props.entry.feedName} {props.entry.feedName}
</Text> </Text>
</OnDesktop> </OnDesktop>
@@ -60,7 +61,7 @@ export function FeedEntryCompactHeader(props: FeedEntryHeaderProps) {
<FeedEntryTitle entry={props.entry} /> <FeedEntryTitle entry={props.entry} />
</Box> </Box>
<OnDesktop> <OnDesktop>
<Text color="dimmed" className={classes.date}> <Text c="dimmed" className={classes.date}>
<RelativeDate date={props.entry.date} /> <RelativeDate date={props.entry.date} />
</Text> </Text>
</OnDesktop> </OnDesktop>

View File

@@ -7,6 +7,7 @@ import { useAppDispatch, useAppSelector } from "app/store"
import { type Entry } from "app/types" import { type Entry } from "app/types"
import { truncate } from "app/utils" import { truncate } from "app/utils"
import { useBrowserExtension } from "hooks/useBrowserExtension" import { useBrowserExtension } from "hooks/useBrowserExtension"
import { useColorScheme } from "hooks/useColorScheme"
import { Item, Menu, Separator } from "react-contexify" import { Item, Menu, Separator } from "react-contexify"
import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbRss, TbStar, TbStarOff } from "react-icons/tb" import { TbArrowBarToDown, TbExternalLink, TbEyeCheck, TbEyeOff, TbRss, TbStar, TbStarOff } from "react-icons/tb"
import { tss } from "tss" import { tss } from "tss"
@@ -19,30 +20,31 @@ const iconSize = 16
const useStyles = tss const useStyles = tss
.withParams<{ .withParams<{
theme: MantineTheme theme: MantineTheme
colorScheme: "light" | "dark"
}>() }>()
.create(({ theme }) => ({ .create(({ theme, colorScheme }) => ({
menu: { menu: {
// apply mantine theme from MenuItem.styles.ts // apply mantine theme from MenuItem.styles.ts
fontSize: theme.fontSizes.sm, fontSize: theme.fontSizes.sm,
"--contexify-item-color": `${theme.colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`, "--contexify-item-color": `${colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
"--contexify-activeItem-color": `${theme.colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`, "--contexify-activeItem-color": `${colorScheme === "dark" ? theme.colors.dark[0] : theme.black} !important`,
"--contexify-activeItem-bgColor": `${ "--contexify-activeItem-bgColor": `${colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[1]} !important`,
theme.colorScheme === "dark" ? theme.fn.rgba(theme.colors.dark[3], 0.35) : theme.colors.gray[1]
} !important`,
}, },
})) }))
export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) { export function FeedEntryContextMenu(props: FeedEntryContextMenuProps) {
const theme = useMantineTheme() const theme = useMantineTheme()
const colorScheme = useColorScheme()
const { classes } = useStyles({ const { classes } = useStyles({
theme, theme,
colorScheme,
}) })
const sourceType = useAppSelector(state => state.entries.source.type) const sourceType = useAppSelector(state => state.entries.source.type)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { openLinkInBackgroundTab } = useBrowserExtension() const { openLinkInBackgroundTab } = useBrowserExtension()
return ( return (
<Menu id={Constants.dom.entryContextMenuId(props.entry)} theme={theme.colorScheme} animation={false} className={classes.menu}> <Menu id={Constants.dom.entryContextMenuId(props.entry)} theme={colorScheme} animation={false} className={classes.menu}>
<Item <Item
onClick={() => { onClick={() => {
window.open(props.entry.url, "_blank", "noreferrer") window.open(props.entry.url, "_blank", "noreferrer")

View File

@@ -1,5 +1,5 @@
import { t, Trans } from "@lingui/macro" import { t, Trans } from "@lingui/macro"
import { Group, Indicator, MultiSelect, Popover } from "@mantine/core" import { Group, Indicator, Popover, TagsInput } from "@mantine/core"
import { markEntriesUpToEntry, markEntry, starEntry, tagEntry } from "app/entries/thunks" import { markEntriesUpToEntry, markEntry, starEntry, tagEntry } from "app/entries/thunks"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { type Entry } from "app/types" import { type Entry } from "app/types"
@@ -38,8 +38,8 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
) )
return ( return (
<Group position="apart"> <Group justify="space-between">
<Group spacing={spacing}> <Group gap={spacing}>
{props.entry.markable && ( {props.entry.markable && (
<ActionButton <ActionButton
icon={props.entry.read ? <TbEyeOff size={18} /> : <TbEyeCheck size={18} />} icon={props.entry.read ? <TbEyeOff size={18} /> : <TbEyeCheck size={18} />}
@@ -72,22 +72,21 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
)} )}
{tags && ( {tags && (
<Popover withArrow withinPortal shadow="md" closeOnClickOutside={!mobile}> <Popover withArrow shadow="md" closeOnClickOutside={!mobile}>
<Popover.Target> <Popover.Target>
<Indicator label={props.entry.tags.length} disabled={props.entry.tags.length === 0} inline size={16}> <Indicator label={props.entry.tags.length} disabled={props.entry.tags.length === 0} inline size={16}>
<ActionButton icon={<TbTag size={18} />} label={<Trans>Tags</Trans>} /> <ActionButton icon={<TbTag size={18} />} label={<Trans>Tags</Trans>} />
</Indicator> </Indicator>
</Popover.Target> </Popover.Target>
<Popover.Dropdown> <Popover.Dropdown>
<MultiSelect <TagsInput
placeholder={t`Tags`}
data={tags} data={tags}
placeholder="Tags"
searchable
creatable
autoFocus
getCreateLabel={query => t`Create tag: ${query}`}
value={props.entry.tags} value={props.entry.tags}
onChange={onTagsChange} onChange={onTagsChange}
comboboxProps={{
withinPortal: false,
}}
/> />
</Popover.Dropdown> </Popover.Dropdown>
</Popover> </Popover>

View File

@@ -1,6 +1,7 @@
import { Box, type MantineTheme, Space, Text, useMantineTheme } from "@mantine/core" import { Box, Space, Text } from "@mantine/core"
import { type Entry } from "app/types" import { type Entry } from "app/types"
import { RelativeDate } from "components/RelativeDate" import { RelativeDate } from "components/RelativeDate"
import { useColorScheme } from "hooks/useColorScheme"
import { tss } from "tss" import { tss } from "tss"
import { FeedEntryTitle } from "./FeedEntryTitle" import { FeedEntryTitle } from "./FeedEntryTitle"
import { FeedFavicon } from "./FeedFavicon" import { FeedFavicon } from "./FeedFavicon"
@@ -12,12 +13,12 @@ export interface FeedEntryHeaderProps {
const useStyles = tss const useStyles = tss
.withParams<{ .withParams<{
theme: MantineTheme colorScheme: "light" | "dark"
read: boolean read: boolean
}>() }>()
.create(({ theme, read }) => ({ .create(({ colorScheme, read }) => ({
headerText: { headerText: {
fontWeight: theme.colorScheme === "light" && !read ? "bold" : "inherit", fontWeight: colorScheme === "light" && !read ? "bold" : "inherit",
}, },
headerSubtext: { headerSubtext: {
display: "flex", display: "flex",
@@ -27,9 +28,9 @@ const useStyles = tss
})) }))
export function FeedEntryHeader(props: FeedEntryHeaderProps) { export function FeedEntryHeader(props: FeedEntryHeaderProps) {
const theme = useMantineTheme() const colorScheme = useColorScheme()
const { classes } = useStyles({ const { classes } = useStyles({
theme, colorScheme,
read: props.entry.read, read: props.entry.read,
}) })
return ( return (
@@ -40,7 +41,7 @@ export function FeedEntryHeader(props: FeedEntryHeaderProps) {
<Box className={classes.headerSubtext}> <Box className={classes.headerSubtext}>
<FeedFavicon url={props.entry.iconUrl} /> <FeedFavicon url={props.entry.iconUrl} />
<Space w={6} /> <Space w={6} />
<Text color="dimmed"> <Text c="dimmed">
{props.entry.feedName} {props.entry.feedName}
<span> · </span> <span> · </span>
<RelativeDate date={props.entry.date} /> <RelativeDate date={props.entry.date} />
@@ -48,7 +49,7 @@ export function FeedEntryHeader(props: FeedEntryHeaderProps) {
</Box> </Box>
{props.expanded && ( {props.expanded && (
<Box className={classes.headerSubtext}> <Box className={classes.headerSubtext}>
<Text color="dimmed"> <Text c="dimmed">
{props.entry.author && <span>by {props.entry.author}</span>} {props.entry.author && <span>by {props.entry.author}</span>}
{props.entry.author && props.entry.categories && <span>&nbsp;·&nbsp;</span>} {props.entry.author && props.entry.categories && <span>&nbsp;·&nbsp;</span>}
{props.entry.categories && <span>{props.entry.categories}</span>} {props.entry.categories && <span>{props.entry.categories}</span>}

View File

@@ -1,6 +1,7 @@
import { Box, TypographyStylesProvider } from "@mantine/core" import { Box } from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { calculatePlaceholderSize } from "app/utils" import { calculatePlaceholderSize } from "app/utils"
import { BasicHtmlStyles } from "components/content/BasicHtmlStyles"
import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading" import { ImageWithPlaceholderWhileLoading } from "components/ImageWithPlaceholderWhileLoading"
import { Content } from "./Content" import { Content } from "./Content"
@@ -20,7 +21,7 @@ export function Media(props: MediaProps) {
maxWidth: Constants.layout.entryMaxWidth, maxWidth: Constants.layout.entryMaxWidth,
}) })
return ( return (
<TypographyStylesProvider> <BasicHtmlStyles>
<ImageWithPlaceholderWhileLoading <ImageWithPlaceholderWhileLoading
src={props.thumbnailUrl} src={props.thumbnailUrl}
alt="media thumbnail" alt="media thumbnail"
@@ -34,6 +35,6 @@ export function Media(props: MediaProps) {
<Content content={props.description} /> <Content content={props.description} />
</Box> </Box>
)} )}
</TypographyStylesProvider> </BasicHtmlStyles>
) )
} }

View File

@@ -2,6 +2,7 @@ import { ActionIcon, Box, type MantineTheme, SimpleGrid, useMantineTheme } from
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { useAppSelector } from "app/store" import { useAppSelector } from "app/store"
import { type SharingSettings } from "app/types" import { type SharingSettings } from "app/types"
import { useColorScheme } from "hooks/useColorScheme"
import { type IconType } from "react-icons" import { type IconType } from "react-icons"
import { tss } from "tss" import { tss } from "tss"
@@ -10,20 +11,23 @@ type Color = `#${string}`
const useStyles = tss const useStyles = tss
.withParams<{ .withParams<{
theme: MantineTheme theme: MantineTheme
colorScheme: "light" | "dark"
color: Color color: Color
}>() }>()
.create(({ theme, color }) => ({ .create(({ theme, colorScheme, color }) => ({
socialIcon: { socialIcon: {
color, color,
backgroundColor: theme.colorScheme === "dark" ? theme.colors.gray[2] : "white", backgroundColor: colorScheme === "dark" ? theme.colors.gray[2] : "white",
borderRadius: "50%", borderRadius: "50%",
}, },
})) }))
function ShareButton({ url, icon, color }: { url: string; icon: IconType; color: Color }) { function ShareButton({ url, icon, color }: { url: string; icon: IconType; color: Color }) {
const theme = useMantineTheme() const theme = useMantineTheme()
const colorScheme = useColorScheme()
const { classes } = useStyles({ const { classes } = useStyles({
theme, theme,
colorScheme,
color, color,
}) })
@@ -33,7 +37,7 @@ function ShareButton({ url, icon, color }: { url: string; icon: IconType; color:
} }
return ( return (
<ActionIcon> <ActionIcon variant="transparent">
<a href={url} target="_blank" rel="noreferrer" onClick={onClick}> <a href={url} target="_blank" rel="noreferrer" onClick={onClick}>
<Box p={6} className={classes.socialIcon}> <Box p={6} className={classes.socialIcon}>
{icon({ size: 18 })} {icon({ size: 18 })}
@@ -51,7 +55,7 @@ export function ShareButtons(props: { url: string; description: string }) {
return ( return (
<SimpleGrid cols={4}> <SimpleGrid cols={4}>
{(Object.keys(Constants.sharing) as Array<keyof SharingSettings>) {(Object.keys(Constants.sharing) as Array<keyof SharingSettings>)
.filter(site => sharingSettings && sharingSettings[site]) .filter(site => sharingSettings?.[site])
.map(site => ( .map(site => (
<ShareButton <ShareButton
key={site} key={site}

View File

@@ -35,11 +35,11 @@ export function AddCategory() {
<Stack> <Stack>
<TextInput label={<Trans>Category</Trans>} placeholder={t`Category`} {...form.getInputProps("name")} required /> <TextInput label={<Trans>Category</Trans>} placeholder={t`Category`} {...form.getInputProps("name")} required />
<CategorySelect label={<Trans>Parent</Trans>} {...form.getInputProps("parentId")} clearable /> <CategorySelect label={<Trans>Parent</Trans>} {...form.getInputProps("parentId")} clearable />
<Group position="center"> <Group justify="center">
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}> <Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Button> </Button>
<Button type="submit" leftIcon={<TbFolderPlus size={16} />} loading={addCategory.loading}> <Button type="submit" leftSection={<TbFolderPlus size={16} />} loading={addCategory.loading}>
<Trans>Add</Trans> <Trans>Add</Trans>
</Button> </Button>
</Group> </Group>

View File

@@ -1,5 +1,6 @@
import { t } from "@lingui/macro" import { t } from "@lingui/macro"
import { Select, type SelectItem, type SelectProps } from "@mantine/core" import { Select, type SelectProps } from "@mantine/core"
import { type ComboboxItem } from "@mantine/core/lib/components/Combobox/Combobox.types"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { useAppSelector } from "app/store" import { useAppSelector } from "app/store"
import { flattenCategoryTree } from "app/utils" import { flattenCategoryTree } from "app/utils"
@@ -12,9 +13,9 @@ type CategorySelectProps = Partial<SelectProps> & {
export function CategorySelect(props: CategorySelectProps) { export function CategorySelect(props: CategorySelectProps) {
const rootCategory = useAppSelector(state => state.tree.rootCategory) const rootCategory = useAppSelector(state => state.tree.rootCategory)
const categories = rootCategory && flattenCategoryTree(rootCategory) const categories = rootCategory && flattenCategoryTree(rootCategory)
const selectData: SelectItem[] | undefined = categories const selectData: ComboboxItem[] | undefined = categories
?.filter(c => c.id !== Constants.categories.all.id) ?.filter(c => c.id !== Constants.categories.all.id)
.filter(c => !props.withoutCategoryIds || !props.withoutCategoryIds.includes(c.id)) .filter(c => !props.withoutCategoryIds?.includes(c.id))
.sort((c1, c2) => c1.name.localeCompare(c2.name)) .sort((c1, c2) => c1.name.localeCompare(c2.name))
.map(c => ({ .map(c => ({
label: c.parentName ? t`${c.name} (in ${c.parentName})` : c.name, label: c.parentName ? t`${c.name} (in ${c.parentName})` : c.name,

View File

@@ -37,7 +37,7 @@ export function ImportOpml() {
<Stack> <Stack>
<FileInput <FileInput
label={<Trans>OPML file</Trans>} label={<Trans>OPML file</Trans>}
icon={<TbFileImport />} leftSection={<TbFileImport />}
// https://github.com/mantinedev/mantine/issues/5401 // https://github.com/mantinedev/mantine/issues/5401
{...{ placeholder: t`OPML file` }} {...{ placeholder: t`OPML file` }}
description={ description={
@@ -50,11 +50,11 @@ export function ImportOpml() {
required required
accept="application/xml" accept="application/xml"
/> />
<Group position="center"> <Group justify="center">
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}> <Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Button> </Button>
<Button type="submit" leftIcon={<TbFileImport size={16} />} loading={importOpml.loading}> <Button type="submit" leftSection={<TbFileImport size={16} />} loading={importOpml.loading}>
<Trans>Import</Trans> <Trans>Import</Trans>
</Button> </Button>
</Group> </Group>

View File

@@ -108,7 +108,7 @@ export function Subscribe() {
</Stepper.Step> </Stepper.Step>
</Stepper> </Stepper>
<Group position="center" mt="xl"> <Group justify="center" mt="xl">
<Button variant="default" onClick={previousStep}> <Button variant="default" onClick={previousStep}>
<Trans>Back</Trans> <Trans>Back</Trans>
</Button> </Button>
@@ -118,7 +118,7 @@ export function Subscribe() {
</Button> </Button>
)} )}
{activeStep === 1 && ( {activeStep === 1 && (
<Button type="submit" leftIcon={<TbRss size={16} />} loading={fetchFeed.loading || subscribe.loading}> <Button type="submit" leftSection={<TbRss size={16} />} loading={fetchFeed.loading || subscribe.loading}>
<Trans>Subscribe</Trans> <Trans>Subscribe</Trans>
</Button> </Button>
)} )}

View File

@@ -37,7 +37,7 @@ function HeaderToolbar(props: { children: React.ReactNode }) {
return mobile ? ( return mobile ? (
// on mobile use all available width // on mobile use all available width
<Box <Box
sx={{ style={{
width: "100%", width: "100%",
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
@@ -46,7 +46,7 @@ function HeaderToolbar(props: { children: React.ReactNode }) {
{props.children} {props.children}
</Box> </Box>
) : ( ) : (
<Group spacing={spacing}>{props.children}</Group> <Group gap={spacing}>{props.children}</Group>
) )
} }
@@ -136,7 +136,7 @@ export function Header() {
<TextInput <TextInput
placeholder={t`Search`} placeholder={t`Search`}
{...searchForm.getInputProps("search")} {...searchForm.getInputProps("search")}
icon={<TbSearch size={iconSize} />} leftSection={<TbSearch size={iconSize} />}
rightSection={ rightSection={
<ActionIcon onClick={async () => await (searchFromStore && dispatch(search("")))}> <ActionIcon onClick={async () => await (searchFromStore && dispatch(search("")))}>
<TbX /> <TbX />

View File

@@ -65,7 +65,7 @@ export function MarkAllAsReadButton(props: { iconSize: number }) {
value={threshold} value={threshold}
onChange={setThreshold} onChange={setThreshold}
/> />
<Group position="right"> <Group justify="flex-end">
<Button variant="default" onClick={() => setOpened(false)}> <Button variant="default" onClick={() => setOpened(false)}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Button> </Button>

View File

@@ -99,7 +99,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
<Menu.Dropdown> <Menu.Dropdown>
{profile && <Menu.Label>{profile.name}</Menu.Label>} {profile && <Menu.Label>{profile.name}</Menu.Label>}
<Menu.Item <Menu.Item
icon={<TbSettings size={iconSize} />} leftSection={<TbSettings size={iconSize} />}
onClick={() => { onClick={() => {
dispatch(redirectToSettings()) dispatch(redirectToSettings())
setOpened(false) setOpened(false)
@@ -108,7 +108,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
<Trans>Settings</Trans> <Trans>Settings</Trans>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
icon={<TbWorldDownload size={iconSize} />} leftSection={<TbWorldDownload size={iconSize} />}
onClick={async () => onClick={async () =>
await client.feed.refreshAll().then(() => { await client.feed.refreshAll().then(() => {
showNotification({ showNotification({
@@ -128,7 +128,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
<Menu.Label> <Menu.Label>
<Trans>Theme</Trans> <Trans>Theme</Trans>
</Menu.Label> </Menu.Label>
<Menu.Item icon={dark ? <TbSun size={iconSize} /> : <TbMoon size={iconSize} />} onClick={() => toggleColorScheme()}> <Menu.Item leftSection={dark ? <TbSun size={iconSize} /> : <TbMoon size={iconSize} />} onClick={() => toggleColorScheme()}>
{dark ? <Trans>Switch to light theme</Trans> : <Trans>Switch to dark theme</Trans>} {dark ? <Trans>Switch to light theme</Trans> : <Trans>Switch to dark theme</Trans>}
</Menu.Item> </Menu.Item>
@@ -153,7 +153,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
<Trans>Admin</Trans> <Trans>Admin</Trans>
</Menu.Label> </Menu.Label>
<Menu.Item <Menu.Item
icon={<TbUsers size={iconSize} />} leftSection={<TbUsers size={iconSize} />}
onClick={() => { onClick={() => {
dispatch(redirectToAdminUsers()) dispatch(redirectToAdminUsers())
setOpened(false) setOpened(false)
@@ -162,7 +162,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
<Trans>Manage users</Trans> <Trans>Manage users</Trans>
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
icon={<TbChartLine size={iconSize} />} leftSection={<TbChartLine size={iconSize} />}
onClick={() => { onClick={() => {
dispatch(redirectToMetrics()) dispatch(redirectToMetrics())
setOpened(false) setOpened(false)
@@ -176,7 +176,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
<Divider /> <Divider />
<Menu.Item <Menu.Item
icon={<TbHeartFilled size={iconSize} color="red" />} leftSection={<TbHeartFilled size={iconSize} color="red" />}
onClick={() => { onClick={() => {
dispatch(redirectToDonate()) dispatch(redirectToDonate())
setOpened(false) setOpened(false)
@@ -186,7 +186,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
icon={<TbHelp size={iconSize} />} leftSection={<TbHelp size={iconSize} />}
onClick={() => { onClick={() => {
dispatch(redirectToAbout()) dispatch(redirectToAbout())
setOpened(false) setOpened(false)
@@ -194,7 +194,7 @@ export function ProfileMenu(props: ProfileMenuProps) {
> >
<Trans>About</Trans> <Trans>About</Trans>
</Menu.Item> </Menu.Item>
<Menu.Item icon={<TbPower size={iconSize} />} onClick={logout}> <Menu.Item leftSection={<TbPower size={iconSize} />} onClick={logout}>
<Trans>Logout</Trans> <Trans>Logout</Trans>
</Menu.Item> </Menu.Item>
</Menu.Dropdown> </Menu.Dropdown>

View File

@@ -11,7 +11,7 @@ export function MetricAccordionItem({ metricKey, name, headerValue, children }:
return ( return (
<Accordion.Item value={metricKey} key={metricKey}> <Accordion.Item value={metricKey} key={metricKey}>
<Accordion.Control> <Accordion.Control>
<Group position="apart"> <Group justify="space-between">
<Box>{name}</Box> <Box>{name}</Box>
<Box>{headerValue}</Box> <Box>{headerValue}</Box>
</Group> </Group>

View File

@@ -72,7 +72,7 @@ export function CustomCodeSettings() {
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}> <Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Button> </Button>
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveCustomCode.loading}> <Button type="submit" leftSection={<TbDeviceFloppy size={16} />} loading={saveCustomCode.loading}>
<Trans>Save</Trans> <Trans>Save</Trans>
</Button> </Button>
</Group> </Group>

View File

@@ -81,7 +81,7 @@ export function DisplaySettings() {
<Switch <Switch
key={site} key={site}
label={Constants.sharing[site].label} label={Constants.sharing[site].label}
checked={sharingSettings && sharingSettings[site]} checked={sharingSettings?.[site]}
onChange={async e => onChange={async e =>
await dispatch( await dispatch(
changeSharingSetting({ changeSharingSetting({

View File

@@ -132,13 +132,13 @@ export function ProfileSettings() {
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}> <Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Button> </Button>
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveProfile.loading}> <Button type="submit" leftSection={<TbDeviceFloppy size={16} />} loading={saveProfile.loading}>
<Trans>Save</Trans> <Trans>Save</Trans>
</Button> </Button>
<Divider orientation="vertical" /> <Divider orientation="vertical" />
<Button <Button
color="red" color="red"
leftIcon={<TbTrash size={16} />} leftSection={<TbTrash size={16} />}
onClick={() => openDeleteProfileModal()} onClick={() => openDeleteProfileModal()}
loading={deleteProfile.loading} loading={deleteProfile.loading}
> >

View File

@@ -1,5 +1,6 @@
import { Box, Center, type MantineTheme, useMantineTheme } from "@mantine/core" import { Box, Center, type MantineTheme, useMantineTheme } from "@mantine/core"
import { FeedFavicon } from "components/content/FeedFavicon" import { FeedFavicon } from "components/content/FeedFavicon"
import { useColorScheme } from "hooks/useColorScheme"
import React, { type ReactNode } from "react" import React, { type ReactNode } from "react"
import { tss } from "tss" import { tss } from "tss"
import { UnreadCount } from "./UnreadCount" import { UnreadCount } from "./UnreadCount"
@@ -20,18 +21,19 @@ interface TreeNodeProps {
const useStyles = tss const useStyles = tss
.withParams<{ .withParams<{
theme: MantineTheme theme: MantineTheme
colorScheme: "dark" | "light"
selected: boolean selected: boolean
hasError: boolean hasError: boolean
hasUnread: boolean hasUnread: boolean
}>() }>()
.create(({ theme, selected, hasError, hasUnread }) => { .create(({ theme, colorScheme, selected, hasError, hasUnread }) => {
let backgroundColor = "inherit" let backgroundColor = "inherit"
if (selected) backgroundColor = theme.colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[3] if (selected) backgroundColor = colorScheme === "dark" ? theme.colors.dark[4] : theme.colors.gray[3]
let color let color
if (hasError) { if (hasError) {
color = theme.colors.red[6] color = theme.colors.red[6]
} else if (theme.colorScheme === "dark") { } else if (colorScheme === "dark") {
color = hasUnread ? theme.colors.dark[0] : theme.colors.dark[3] color = hasUnread ? theme.colors.dark[0] : theme.colors.dark[3]
} else { } else {
color = hasUnread ? theme.black : theme.colors.gray[6] color = hasUnread ? theme.black : theme.colors.gray[6]
@@ -45,7 +47,7 @@ const useStyles = tss
color, color,
backgroundColor, backgroundColor,
"&:hover": { "&:hover": {
backgroundColor: theme.colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[0], backgroundColor: colorScheme === "dark" ? theme.colors.dark[6] : theme.colors.gray[0],
}, },
}, },
nodeText: { nodeText: {
@@ -59,8 +61,10 @@ const useStyles = tss
export function TreeNode(props: TreeNodeProps) { export function TreeNode(props: TreeNodeProps) {
const theme = useMantineTheme() const theme = useMantineTheme()
const colorScheme = useColorScheme()
const { classes } = useStyles({ const { classes } = useStyles({
theme, theme,
colorScheme,
selected: props.selected, selected: props.selected,
hasError: props.hasError, hasError: props.hasError,
hasUnread: props.unread > 0, hasUnread: props.unread > 0,

View File

@@ -1,6 +1,6 @@
import { t, Trans } from "@lingui/macro" import { t, Trans } from "@lingui/macro"
import { Box, Center, Kbd, TextInput } from "@mantine/core" import { Box, Center, Kbd, TextInput } from "@mantine/core"
import { openSpotlight, type SpotlightAction, SpotlightProvider } from "@mantine/spotlight" import { Spotlight, spotlight, type SpotlightActionData } from "@mantine/spotlight"
import { redirectToFeed } from "app/redirect/thunks" import { redirectToFeed } from "app/redirect/thunks"
import { useAppDispatch } from "app/store" import { useAppDispatch } from "app/store"
import { type Subscription } from "app/types" import { type Subscription } from "app/types"
@@ -15,17 +15,18 @@ export interface TreeSearchProps {
export function TreeSearch(props: TreeSearchProps) { export function TreeSearch(props: TreeSearchProps) {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const actions: SpotlightAction[] = props.feeds const actions: SpotlightActionData[] = props.feeds
.sort((f1, f2) => f1.name.localeCompare(f2.name)) .toSorted((f1, f2) => f1.name.localeCompare(f2.name))
.map(f => ({ .map(f => ({
title: f.name, id: `${f.id}`,
icon: <FeedFavicon url={f.iconUrl} />, label: f.name,
onTrigger: async () => await dispatch(redirectToFeed(f.id)), leftSection: <FeedFavicon url={f.iconUrl} />,
onClick: async () => await dispatch(redirectToFeed(f.id)),
})) }))
const searchIcon = <TbSearch size={18} /> const searchIcon = <TbSearch size={18} />
const rightSection = ( const rightSection = (
<Center> <Center style={{ cursor: "pointer" }} onClick={() => spotlight.open()}>
<Kbd>Ctrl</Kbd> <Kbd>Ctrl</Kbd>
<Box mx={5}>+</Box> <Box mx={5}>+</Box>
<Kbd>K</Kbd> <Kbd>K</Kbd>
@@ -33,30 +34,35 @@ export function TreeSearch(props: TreeSearchProps) {
) )
// additional keyboard shortcut used by commafeed v1 // additional keyboard shortcut used by commafeed v1
useMousetrap("g u", () => openSpotlight()) useMousetrap("g u", () => spotlight.open())
return ( return (
<SpotlightProvider <>
actions={actions}
searchIcon={searchIcon}
searchPlaceholder={t`Search`}
shortcut="ctrl+k"
nothingFoundMessage={<Trans>Nothing found</Trans>}
>
<TextInput <TextInput
placeholder={t`Search`} placeholder={t`Search`}
icon={searchIcon} leftSection={searchIcon}
rightSectionWidth={100} rightSectionWidth={100}
rightSection={rightSection} rightSection={rightSection}
styles={{ styles={{
input: { cursor: "pointer" }, input: {
rightSection: { pointerEvents: "none" }, cursor: "pointer",
},
}} }}
onClick={() => openSpotlight()} onClick={() => spotlight.open()}
// prevent focus // prevent focus
onFocus={e => e.target.blur()} onFocus={e => e.target.blur()}
readOnly readOnly
/> />
</SpotlightProvider> <Spotlight
actions={actions}
limit={10}
shortcut="ctrl+k"
searchProps={{
leftSection: searchIcon,
placeholder: t`Search`,
}}
nothingFound={<Trans>Nothing found</Trans>}
></Spotlight>
</>
) )
} }

View File

@@ -17,7 +17,9 @@ export function UnreadCount(props: { unreadCount: number }) {
const count = props.unreadCount >= 10000 ? "10k+" : props.unreadCount const count = props.unreadCount >= 10000 ? "10k+" : props.unreadCount
return ( return (
<Tooltip label={props.unreadCount} disabled={props.unreadCount === count}> <Tooltip label={props.unreadCount} disabled={props.unreadCount === count}>
<Badge className={classes.badge}>{count}</Badge> <Badge className={classes.badge} variant="light">
{count}
</Badge>
</Tooltip> </Tooltip>
) )
} }

View File

@@ -0,0 +1,4 @@
import { useComputedColorScheme } from "@mantine/core"
// the color scheme to use to render components
export const useColorScheme = () => useComputedColorScheme("light")

View File

@@ -1,7 +1,9 @@
import { useMediaQuery } from "@mantine/hooks" import { useMediaQuery } from "@mantine/hooks"
import { Constants } from "app/constants" import { Constants } from "app/constants"
export const useMobile = (breakpoint: string = Constants.layout.mobileBreakpoint) => export const useMobile = (breakpoint: string | number = Constants.layout.mobileBreakpoint) => {
!useMediaQuery(`(min-width: ${breakpoint})`, undefined, { const bp = typeof breakpoint === "number" ? `${breakpoint}px` : breakpoint
return !useMediaQuery(`(min-width: ${bp})`, undefined, {
getInitialValueInEffect: false, getInitialValueInEffect: false,
}) })
}

View File

@@ -1,10 +1,13 @@
import "@fontsource/open-sans" import "@fontsource/open-sans"
import "@mantine/core/styles.css"
import "@mantine/notifications/styles.css"
import "@mantine/spotlight/styles.css"
import "react-contexify/ReactContexify.css"
import "main.css"
import { App } from "App" import { App } from "App"
import { store } from "app/store" import { store } from "app/store"
import dayjs from "dayjs" import dayjs from "dayjs"
import relativeTime from "dayjs/plugin/relativeTime" import relativeTime from "dayjs/plugin/relativeTime"
import "main.css"
import "react-contexify/ReactContexify.css"
import ReactDOM from "react-dom/client" import ReactDOM from "react-dom/client"
import { Provider } from "react-redux" import { Provider } from "react-redux"

View File

@@ -50,11 +50,11 @@ export function ErrorPage(props: { error: Error }) {
<Title className={classes.title}> <Title className={classes.title}>
<Trans>Something bad just happened...</Trans> <Trans>Something bad just happened...</Trans>
</Title> </Title>
<Text size="lg" align="center" className={classes.description}> <Text size="lg" ta="center" className={classes.description}>
{props.error.message} {props.error.message}
</Text> </Text>
<Group position="center"> <Group justify="center">
<Button size="md" onClick={() => window.location.reload()} leftIcon={<TbRefresh size={18} />}> <Button size="md" onClick={() => window.location.reload()} leftSection={<TbRefresh size={18} />}>
Refresh the page Refresh the page
</Button> </Button>
</Group> </Group>

View File

@@ -14,7 +14,7 @@ export function LoadingPage() {
<RingProgress <RingProgress
sections={[{ value: loadingPercentage, color: theme.primaryColor }]} sections={[{ value: loadingPercentage, color: theme.primaryColor }]}
label={ label={
<Text weight="bold" align="center" size="xl"> <Text fw="bold" ta="center" size="xl">
{loadingPercentage}% {loadingPercentage}%
</Text> </Text>
} }

View File

@@ -1,5 +1,5 @@
import { Trans } from "@lingui/macro" import { Trans } from "@lingui/macro"
import { Anchor, Box, Center, Container, Divider, Group, Image, Title, useMantineColorScheme } from "@mantine/core" import { Anchor, Box, Center, Container, Divider, Group, Image, Space, Title, useMantineColorScheme } from "@mantine/core"
import { client } from "app/client" import { client } from "app/client"
import { redirectToApiDocumentation, redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/redirect/thunks" import { redirectToApiDocumentation, redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
@@ -31,7 +31,7 @@ export function WelcomePage() {
<Container> <Container>
<Header /> <Header />
<Center my="xl"> <Center my="lg">
<Title order={3}>Bloat-free feed reader</Title> <Title order={3}>Bloat-free feed reader</Title>
</Center> </Center>
@@ -47,13 +47,15 @@ export function WelcomePage() {
</Center> </Center>
)} )}
<Divider my="xl" /> <Divider my="lg" />
<Image src={image} /> <Image src={image} />
<Divider my="xl" /> <Divider my="lg" />
<Footer /> <Footer />
<Space h="lg" />
</Container> </Container>
) )
} }
@@ -73,9 +75,13 @@ function Header() {
} }
return ( return (
<Group position="apart"> <Group justify="space-between">
<PageTitle /> <Box>
<Buttons /> <PageTitle />
</Box>
<Box>
<Buttons />
</Box>
</Group> </Group>
) )
} }
@@ -88,7 +94,7 @@ function Buttons() {
const dark = colorScheme === "dark" const dark = colorScheme === "dark"
return ( return (
<Group spacing={14}> <Group gap={14}>
<ActionButton <ActionButton
label={<Trans>Log in</Trans>} label={<Trans>Log in</Trans>}
icon={<TbKey size={iconSize} />} icon={<TbKey size={iconSize} />}
@@ -128,7 +134,7 @@ function Buttons() {
function Footer() { function Footer() {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
return ( return (
<Group position="apart"> <Group justify="space-between">
<Group> <Group>
<span>© CommaFeed</span> <span>© CommaFeed</span>
<Anchor variant="text" href="https://github.com/Athou/commafeed/" target="_blank" rel="noreferrer"> <Anchor variant="text" href="https://github.com/Athou/commafeed/" target="_blank" rel="noreferrer">

View File

@@ -66,7 +66,7 @@ export function AdminUsersPage() {
<Title order={3} mb="md"> <Title order={3} mb="md">
<Group> <Group>
<Trans>Manage users</Trans> <Trans>Manage users</Trans>
<ActionIcon color={theme.primaryColor} onClick={() => openUserEditModal(<Trans>Add user</Trans>)}> <ActionIcon color={theme.primaryColor} variant="subtle" onClick={() => openUserEditModal(<Trans>Add user</Trans>)}>
<TbPlus size={20} /> <TbPlus size={20} />
</ActionIcon> </ActionIcon>
</Group> </Group>
@@ -79,69 +79,74 @@ export function AdminUsersPage() {
)} )}
<Table striped highlightOnHover> <Table striped highlightOnHover>
<thead> <Table.Thead>
<tr> <Table.Tr>
<th> <Table.Th>
<Trans>Id</Trans> <Trans>Id</Trans>
</th> </Table.Th>
<th> <Table.Th>
<Trans>Name</Trans> <Trans>Name</Trans>
</th> </Table.Th>
<th> <Table.Th>
<Trans>E-mail</Trans> <Trans>E-mail</Trans>
</th> </Table.Th>
<th> <Table.Th>
<Trans>Date created</Trans> <Trans>Date created</Trans>
</th> </Table.Th>
<th> <Table.Th>
<Trans>Last login date</Trans> <Trans>Last login date</Trans>
</th> </Table.Th>
<th> <Table.Th>
<Trans>Admin</Trans> <Trans>Admin</Trans>
</th> </Table.Th>
<th> <Table.Th>
<Trans>Enabled</Trans> <Trans>Enabled</Trans>
</th> </Table.Th>
<th> <Table.Th>
<Trans>Actions</Trans> <Trans>Actions</Trans>
</th> </Table.Th>
</tr> </Table.Tr>
</thead> </Table.Thead>
<tbody> <Table.Tbody>
{users?.map(u => ( {users?.map(u => (
<tr key={u.id}> <Table.Tr key={u.id}>
<td>{u.id}</td> <Table.Td>{u.id}</Table.Td>
<td>{u.name}</td> <Table.Td>{u.name}</Table.Td>
<td>{u.email}</td> <Table.Td>{u.email}</Table.Td>
<td> <Table.Td>
<RelativeDate date={u.created} /> <RelativeDate date={u.created} />
</td> </Table.Td>
<td> <Table.Td>
<RelativeDate date={u.lastLogin} /> <RelativeDate date={u.lastLogin} />
</td> </Table.Td>
<td> <Table.Td>
<BooleanIcon value={u.admin} /> <BooleanIcon value={u.admin} />
</td> </Table.Td>
<td> <Table.Td>
<BooleanIcon value={u.enabled} /> <BooleanIcon value={u.enabled} />
</td> </Table.Td>
<td> <Table.Td>
<Group> <Group>
<ActionIcon color={theme.primaryColor} onClick={() => openUserEditModal(<Trans>Edit user</Trans>, u)}> <ActionIcon
color={theme.primaryColor}
variant="subtle"
onClick={() => openUserEditModal(<Trans>Edit user</Trans>, u)}
>
<TbPencil size={18} /> <TbPencil size={18} />
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon
color={theme.primaryColor} color={theme.primaryColor}
variant="subtle"
onClick={() => openUserDeleteModal(u)} onClick={() => openUserDeleteModal(u)}
loading={deleteUser.loading} loading={deleteUser.loading}
> >
<TbTrash size={18} /> <TbTrash size={18} />
</ActionIcon> </ActionIcon>
</Group> </Group>
</td> </Table.Td>
</tr> </Table.Tr>
))} ))}
</tbody> </Table.Tbody>
</Table> </Table>
</Container> </Container>
) )

View File

@@ -33,10 +33,10 @@ export function MetricsPage() {
return ( return (
<Tabs defaultValue="stats"> <Tabs defaultValue="stats">
<Tabs.List> <Tabs.List>
<Tabs.Tab value="stats" icon={<TbChartAreaLine size={14} />}> <Tabs.Tab value="stats" leftSection={<TbChartAreaLine size={14} />}>
Stats Stats
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="timers" icon={<TbClock size={14} />}> <Tabs.Tab value="timers" leftSection={<TbClock size={14} />}>
Timers Timers
</Tabs.Tab> </Tabs.Tab>
</Tabs.List> </Tabs.List>

View File

@@ -67,7 +67,7 @@ export function AboutPage() {
return ( return (
<Container size="xl"> <Container size="xl">
<SimpleGrid cols={2} breakpoints={[{ maxWidth: Constants.layout.mobileBreakpoint, cols: 1 }]}> <SimpleGrid cols={{ base: 1, [Constants.layout.mobileBreakpointName]: 2 }}>
<Section title={<Trans>About</Trans>} icon={<TbHelp size={24} />}> <Section title={<Trans>About</Trans>} icon={<TbHelp size={24} />}>
<Box> <Box>
<Trans> <Trans>

View File

@@ -10,13 +10,13 @@ export function AddPage() {
<Container size="sm" px={0}> <Container size="sm" px={0}>
<Tabs defaultValue="subscribe"> <Tabs defaultValue="subscribe">
<Tabs.List> <Tabs.List>
<Tabs.Tab value="subscribe" icon={<TbRss size={16} />}> <Tabs.Tab value="subscribe" leftSection={<TbRss size={16} />}>
<Trans>Subscribe</Trans> <Trans>Subscribe</Trans>
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="category" icon={<TbFolderPlus size={16} />}> <Tabs.Tab value="category" leftSection={<TbFolderPlus size={16} />}>
<Trans>Add category</Trans> <Trans>Add category</Trans>
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="opml" icon={<TbFileImport size={16} />}> <Tabs.Tab value="opml" leftSection={<TbFileImport size={16} />}>
<Trans>OPML</Trans> <Trans>OPML</Trans>
</Tabs.Tab> </Tabs.Tab>
</Tabs.List> </Tabs.List>

View File

@@ -127,13 +127,13 @@ export function CategoryDetailsPage() {
</Button> </Button>
{editable && ( {editable && (
<> <>
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={modifyCategory.loading}> <Button type="submit" leftSection={<TbDeviceFloppy size={16} />} loading={modifyCategory.loading}>
<Trans>Save</Trans> <Trans>Save</Trans>
</Button> </Button>
<Divider orientation="vertical" /> <Divider orientation="vertical" />
<Button <Button
color="red" color="red"
leftIcon={<TbTrash size={16} />} leftSection={<TbTrash size={16} />}
onClick={() => openDeleteCategoryModal()} onClick={() => openDeleteCategoryModal()}
loading={deleteCategory.loading} loading={deleteCategory.loading}
> >

View File

@@ -166,13 +166,13 @@ export function FeedDetailsPage() {
<Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}> <Button variant="default" onClick={async () => await dispatch(redirectToSelectedSource())}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Button> </Button>
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={modifyFeed.loading}> <Button type="submit" leftSection={<TbDeviceFloppy size={16} />} loading={modifyFeed.loading}>
<Trans>Save</Trans> <Trans>Save</Trans>
</Button> </Button>
<Divider orientation="vertical" /> <Divider orientation="vertical" />
<Button <Button
color="red" color="red"
leftIcon={<TbTrash size={16} />} leftSection={<TbTrash size={16} />}
onClick={() => openUnsubscribeModal()} onClick={() => openUnsubscribeModal()}
loading={unsubscribe.loading} loading={unsubscribe.loading}
> >

View File

@@ -73,7 +73,7 @@ export function FeedEntriesPage(props: FeedEntriesPageProps) {
return ( return (
// add some room at the bottom of the page in order to be able to scroll the current entry at the top of the page when expanding // add some room at the bottom of the page in order to be able to scroll the current entry at the top of the page when expanding
<Box mb={viewport.height - Constants.layout.headerHeight - 210}> <Box mb={viewport.height - Constants.layout.headerHeight - 210}>
<Group spacing="xl"> <Group gap="xl">
{sourceWebsiteUrl && ( {sourceWebsiteUrl && (
<a href={sourceWebsiteUrl} target="_blank" rel="noreferrer" className={classes.sourceWebsiteLink}> <a href={sourceWebsiteUrl} target="_blank" rel="noreferrer" className={classes.sourceWebsiteLink}>
<Title order={3}>{sourceLabel}</Title> <Title order={3}>{sourceLabel}</Title>

View File

@@ -1,88 +1,32 @@
import { import { Trans } from "@lingui/macro"
ActionIcon, import { ActionIcon, AppShell, Box, Center, Group, ScrollArea, Title, useMantineTheme } from "@mantine/core"
AppShell,
Box,
Burger,
Center,
DEFAULT_THEME,
Group,
Header,
type MantineTheme,
Navbar,
ScrollArea,
Title,
useMantineTheme,
} from "@mantine/core"
import { Constants } from "app/constants" import { Constants } from "app/constants"
import { redirectToAdd, redirectToRootCategory } from "app/redirect/thunks" import { redirectToAdd, redirectToRootCategory } from "app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "app/store" import { useAppDispatch, useAppSelector } from "app/store"
import { setMobileMenuOpen, setSidebarWidth } from "app/tree/slice" import { setMobileMenuOpen, setSidebarWidth } from "app/tree/slice"
import { reloadTree } from "app/tree/thunks" import { reloadTree } from "app/tree/thunks"
import { reloadProfile, reloadSettings, reloadTags } from "app/user/thunks" import { reloadProfile, reloadSettings, reloadTags } from "app/user/thunks"
import { ActionButton } from "components/ActionButton"
import { AnnouncementDialog } from "components/AnnouncementDialog" import { AnnouncementDialog } from "components/AnnouncementDialog"
import { Loader } from "components/Loader" import { Loader } from "components/Loader"
import { Logo } from "components/Logo" import { Logo } from "components/Logo"
import { OnDesktop } from "components/responsive/OnDesktop" import { OnDesktop } from "components/responsive/OnDesktop"
import { OnMobile } from "components/responsive/OnMobile" import { OnMobile } from "components/responsive/OnMobile"
import { useAppLoading } from "hooks/useAppLoading" import { useAppLoading } from "hooks/useAppLoading"
import { useMobile } from "hooks/useMobile"
import { useWebSocket } from "hooks/useWebSocket" import { useWebSocket } from "hooks/useWebSocket"
import { LoadingPage } from "pages/LoadingPage" import { LoadingPage } from "pages/LoadingPage"
import { Resizable } from "re-resizable"
import { type ReactNode, Suspense, useEffect } from "react" import { type ReactNode, Suspense, useEffect } from "react"
import { TbPlus } from "react-icons/tb" import Draggable from "react-draggable"
import { TbMenu2, TbPlus, TbX } from "react-icons/tb"
import { Outlet } from "react-router-dom" import { Outlet } from "react-router-dom"
import { tss } from "tss"
interface LayoutProps { interface LayoutProps {
sidebar: ReactNode sidebar: ReactNode
sidebarWidth: number sidebarWidth: number
sidebarVisible: boolean
header: ReactNode header: ReactNode
} }
const sidebarPadding = DEFAULT_THEME.spacing.xs
const sidebarRightBorderWidth = "1px"
const useStyles = tss
.withParams<{
theme: MantineTheme
sidebarWidth: number
}>()
.create(({ theme, sidebarWidth }) => ({
sidebar: {
"& .mantine-ScrollArea-scrollbar[data-orientation='horizontal']": {
display: "none",
},
},
sidebarContentResizeWrapper: {
padding: sidebarPadding,
minHeight: `calc(100vh - ${Constants.layout.headerHeight}px)`,
},
sidebarContent: {
maxWidth: `calc(${sidebarWidth}px - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`,
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
maxWidth: `calc(100vw - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`,
},
},
mainContentWrapper: {
paddingTop: Constants.layout.headerHeight,
paddingLeft: sidebarWidth,
paddingRight: 0,
paddingBottom: 0,
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
paddingLeft: 0,
},
},
mainContent: {
maxWidth: `calc(100vw - ${sidebarWidth}px)`,
padding: theme.spacing.md,
[theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: {
maxWidth: "100vw",
padding: "6px",
},
},
}))
function LogoAndTitle() { function LogoAndTitle() {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
return ( return (
@@ -97,21 +41,13 @@ function LogoAndTitle() {
export default function Layout(props: LayoutProps) { export default function Layout(props: LayoutProps) {
const theme = useMantineTheme() const theme = useMantineTheme()
const { classes } = useStyles({
theme,
sidebarWidth: props.sidebarWidth,
})
const { loading } = useAppLoading() const { loading } = useAppLoading()
const mobile = useMobile()
const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen) const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen)
const webSocketConnected = useAppSelector(state => state.server.webSocketConnected) const webSocketConnected = useAppSelector(state => state.server.webSocketConnected)
const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval) const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval)
const sidebarHidden = props.sidebarWidth === 0
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
useWebSocket() useWebSocket()
const handleResize = (element: HTMLElement) => dispatch(setSidebarWidth(element.offsetWidth))
useEffect(() => { useEffect(() => {
// load initial data // load initial data
dispatch(reloadSettings()) dispatch(reloadSettings())
@@ -132,18 +68,20 @@ export default function Layout(props: LayoutProps) {
}, [dispatch, webSocketConnected, treeReloadInterval]) }, [dispatch, webSocketConnected, treeReloadInterval])
const burger = ( const burger = (
<Center> <ActionButton
<Burger label={mobileMenuOpen ? <Trans>Open menu</Trans> : <Trans>Close menu</Trans>}
color={theme.fn.variant({ color: theme.primaryColor, variant: "subtle" }).color} icon={mobileMenuOpen ? <TbX size={18} /> : <TbMenu2 size={18} />}
opened={mobileMenuOpen} onClick={() => dispatch(setMobileMenuOpen(!mobileMenuOpen))}
onClick={() => dispatch(setMobileMenuOpen(!mobileMenuOpen))} ></ActionButton>
size="sm"
/>
</Center>
) )
const addButton = ( const addButton = (
<ActionIcon color={theme.primaryColor} onClick={async () => await dispatch(redirectToAdd())} aria-label="Subscribe"> <ActionIcon
color={theme.primaryColor}
variant="subtle"
onClick={async () => await dispatch(redirectToAdd())}
aria-label="Subscribe"
>
<TbPlus size={18} /> <TbPlus size={18} />
</ActionIcon> </ActionIcon>
) )
@@ -151,77 +89,80 @@ export default function Layout(props: LayoutProps) {
if (loading) return <LoadingPage /> if (loading) return <LoadingPage />
return ( return (
<AppShell <AppShell
fixed header={{ height: Constants.layout.headerHeight }}
navbarOffsetBreakpoint={Constants.layout.mobileBreakpoint} navbar={{
classNames={{ main: classes.mainContentWrapper }} width: props.sidebarWidth,
navbar={ breakpoint: Constants.layout.mobileBreakpoint,
<Navbar collapsed: { mobile: !mobileMenuOpen, desktop: !props.sidebarVisible },
id="sidebar" }}
hiddenBreakpoint={sidebarHidden ? 99999999 : Constants.layout.mobileBreakpoint} padding={{ base: 6, [Constants.layout.mobileBreakpointName]: "md" }}
hidden={sidebarHidden || !mobileMenuOpen}
width={{ md: props.sidebarWidth }}
className={classes.sidebar}
>
<Navbar.Section grow component={ScrollArea} mx={mobile ? 0 : "-sm"} px={mobile ? 0 : "sm"}>
<Resizable
enable={{
top: false,
right: !mobile,
bottom: false,
left: false,
topRight: false,
bottomRight: false,
bottomLeft: false,
topLeft: false,
}}
onResize={(e, dir, el) => handleResize(el)}
minWidth={120}
className={classes.sidebarContentResizeWrapper}
>
<Box className={classes.sidebarContent}>{props.sidebar}</Box>
</Resizable>
</Navbar.Section>
</Navbar>
}
header={
<Header id="header" height={Constants.layout.headerHeight} p="md">
<OnMobile>
{mobileMenuOpen && (
<Group position="apart">
<Box>{burger}</Box>
<Box>
<LogoAndTitle />
</Box>
<Box>{addButton}</Box>
</Group>
)}
{!mobileMenuOpen && (
<Group>
<Box>{burger}</Box>
<Box sx={{ flexGrow: 1 }}>{props.header}</Box>
</Group>
)}
</OnMobile>
<OnDesktop>
<Group>
<Group position="apart" sx={{ width: props.sidebarWidth - 16 }}>
<Box>
<LogoAndTitle />
</Box>
<Box>{addButton}</Box>
</Group>
<Box sx={{ flexGrow: 1 }}>{props.header}</Box>
</Group>
</OnDesktop>
</Header>
}
> >
<Box id="content" className={classes.mainContent}> <AppShell.Header id="header">
<OnMobile>
{mobileMenuOpen && (
<Group justify="space-between" p="md">
<Box>{burger}</Box>
<Box>
<LogoAndTitle />
</Box>
<Box>{addButton}</Box>
</Group>
)}
{!mobileMenuOpen && (
<Group p="md">
<Box>{burger}</Box>
<Box style={{ flexGrow: 1 }}>{props.header}</Box>
</Group>
)}
</OnMobile>
<OnDesktop>
<Group p="md">
<Group justify="space-between" style={{ width: props.sidebarWidth - 16 }}>
<Box>
<LogoAndTitle />
</Box>
<Box>{addButton}</Box>
</Group>
<Box style={{ flexGrow: 1 }}>{props.header}</Box>
</Group>
</OnDesktop>
</AppShell.Header>
<AppShell.Navbar id="sidebar" p="xs">
<AppShell.Section grow component={ScrollArea} mx="-sm" px="sm">
<Box>{props.sidebar}</Box>
</AppShell.Section>
</AppShell.Navbar>
<Draggable
axis="x"
defaultPosition={{
x: props.sidebarWidth,
y: Constants.layout.headerHeight,
}}
bounds={{
left: 120,
right: 1000,
}}
grid={[30, 30]}
onDrag={(_e, data) => {
dispatch(setSidebarWidth(data.x))
}}
>
<Box
style={{
position: "fixed",
height: "100%",
width: "10px",
cursor: "ew-resize",
}}
></Box>
</Draggable>
<AppShell.Main id="content">
<Suspense fallback={<Loader />}> <Suspense fallback={<Loader />}>
<AnnouncementDialog /> <AnnouncementDialog />
<Outlet /> <Outlet />
</Suspense> </Suspense>
</Box> </AppShell.Main>
</AppShell> </AppShell>
) )
} }

View File

@@ -10,13 +10,13 @@ export function SettingsPage() {
<Container size="sm" px={0}> <Container size="sm" px={0}>
<Tabs defaultValue="display" keepMounted={false}> <Tabs defaultValue="display" keepMounted={false}>
<Tabs.List> <Tabs.List>
<Tabs.Tab value="display" icon={<TbPhoto size={16} />}> <Tabs.Tab value="display" leftSection={<TbPhoto size={16} />}>
<Trans>Display</Trans> <Trans>Display</Trans>
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="customCode" icon={<TbCode size={16} />}> <Tabs.Tab value="customCode" leftSection={<TbCode size={16} />}>
<Trans>Custom code</Trans> <Trans>Custom code</Trans>
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="profile" icon={<TbUser size={16} />}> <Tabs.Tab value="profile" leftSection={<TbUser size={16} />}>
<Trans>Profile</Trans> <Trans>Profile</Trans>
</Tabs.Tab> </Tabs.Tab>
</Tabs.List> </Tabs.List>

View File

@@ -60,7 +60,7 @@ export function LoginPage() {
/> />
{serverInfos?.smtpEnabled && ( {serverInfos?.smtpEnabled && (
<Anchor component={Link} to="/passwordRecovery" color="dimmed"> <Anchor component={Link} to="/passwordRecovery" c="dimmed">
<Trans>Forgot password?</Trans> <Trans>Forgot password?</Trans>
</Anchor> </Anchor>
)} )}