Author SHA1 Message Date
Paul Makles 21175dffda fix: check if content actually exists
chore: update lang submodule
2023-02-24 13:53:56 +01:00
Paul Makles 5b31ce494e merge: remote-tracking branch 'origin/production' 2023-02-23 20:08:51 +01:00
Paul Makles 056ec623cb chore: move date forwards 2023-02-23 19:12:26 +01:00
Paul Makles ff0330ec1b feat(modal): add report success modal (also allows blocking user) 2023-02-23 17:52:47 +01:00
Paul Makles 58f35a2813 feat: add changelog entry for in-app reporting 2023-02-22 18:34:35 +01:00
Paul Makles a00d554f80 feat: render shadows and markdown in changelogs 2023-02-22 18:33:38 +01:00
Paul Makles 7f911f5d2c chore: always build highmem 2023-02-22 17:59:38 +01:00
Paul Makles 122f047c85 fix(modal): trigger on keydown instead of keyup to prevent interference 2023-02-22 17:59:26 +01:00
Paul Makles 7d544c82ab feat: add reporting UI 2023-02-22 17:13:43 +01:00
Paul Makles 46eec5a132 fix: do not trigger modal submission on <select> 2023-02-22 17:00:47 +01:00
Paul Makles de85527cd8 chore: bump submodules 2023-02-22 15:01:17 +01:00
kate 6062a361f1 fix: allow dash char in autocorrect content validation (#838) 2023-02-05 13:45:32 +00:00
Paul Makles 95d4f61d7f feat: support Streamable embeds 2023-01-29 17:34:07 +00:00
Paul Makles 34ce1d1a86 chore: bump submodule dependencies 2023-01-29 17:33:42 +00:00
insertish a9f23fe0e3 ci: synced local '.github/workflows/triage_pr.yml' with remote 'workflows/triage_pr.yml'
[skip ci]
2023-01-24 19:43:32 +00:00
insertish 6c08ccff52 ci: synced local '.github/workflows/triage_issue.yml' with remote 'workflows/triage_issue.yml'
[skip ci]
2023-01-24 19:43:32 +00:00
Leda 2f43a2c32d fix: category title input reverts to span on empty string (#699)
Fixes https://github.com/revoltchat/revite/issues/693
2023-01-24 17:59:31 +00:00
cheneyni-451andCheney Ni e34c5c99fe fix(ui): add margin to delete role button (#802)
Co-authored-by: Cheney Ni <cheneyni@umich.edu>
2023-01-24 17:56:50 +00:00
kate 89b3c9c098 fix: dash ("-" char) in emoji names (#816) 2023-01-24 17:55:17 +00:00
Sophie L dadf0b6329 fix: update draft check (#830) 2023-01-24 17:55:04 +00:00
Lea fcf6812151 feat: allow rolt.chat for relative navigation (#814) 2022-12-05 14:47:04 +00:00
Lea 09be8c9e4f feat: list custom emojis in autocomplete (#809)
* feat: list custom emojis in autocomplete

* fix: properly align emoji name in autocompletion
2022-12-05 14:44:47 +00:00
kateandSophie L 6767ea1853 fix(ci): typing issues; broken submodules (#826)
* Remove broken submodules

* Fix yarn typecheck

* Add build:deps before typecheck to fix missing dependencies

* fix: minor linting nitpick

Co-authored-by: Sophie L <beartechtalks@gmail.com>
2022-12-05 14:42:43 +00:00
Paul Makles 9be4afe241 fix: allow setting custom remote for publish 2022-11-06 13:26:41 +00:00
Paul Makles 7e89dcfb13 chore: add notice for iCloud users 2022-11-06 13:21:06 +00:00
Paul Makles 3836156f3d merge: branch 'master' into production 2022-10-23 21:09:49 +01:00
Paul Makles 9f3e47d327 chore: remove captcha from login form 2022-10-23 21:08:36 +01:00
Paul Makles e29678a6a3 fix: add load suspense on master branch 2022-10-23 21:06:53 +01:00
Paul Makles 40d3356c1b merge: branch 'master' into production 2022-09-20 19:12:25 +01:00
Paul Makles eb4670ec43 revert: "fix: display server identity (if present) in typing indicator" (#789) 2022-09-20 19:11:45 +01:00
Paul Makles df20ab7407 merge: branch 'master' into production 2022-09-20 18:44:51 +01:00
Paul Makles 22b2bc0a5e fix: use new lightspeed embed URL 2022-09-20 18:31:12 +01:00
Paul Makles 2a12b79ef7 chore: bump lang submodule 2022-09-20 18:31:05 +01:00
4444dogsandSophie L 9b6abe374a feat: add seasonal halloween theme (#784)
Co-authored-by: Sophie L <beartechtalks@gmail.com>
2022-09-20 18:26:20 +01:00
Compey b649b2a923 Use rvlt.gg instead of app.revolt.chat/invite for invites (#783) 2022-09-20 18:25:14 +01:00
Ed L b64c316dc9 chore: lay groundwork for masquerades 2022-09-20 18:24:59 +01:00
Ed L bfeefb4c73 feat: use Button component for dev page, linting 2022-09-20 18:24:59 +01:00
Ed L 734fa06425 chore: use ios icon for sessions, update links 2022-09-20 18:24:59 +01:00
Ed L 5a738b7c68 fix: grammar, update command for dev mode 2022-09-20 18:24:59 +01:00
Ed L f03a88bd78 fix: move leave/delete server option to the bottom 2022-09-20 18:24:59 +01:00
Ed L e53059ee08 fix: let users message/hide mutuals tab for bots 2022-09-20 18:24:59 +01:00
Ed L 099f7a3116 feat: show role id 2022-09-20 18:24:59 +01:00
Ed L d2264a2a43 fix: use --monospace-font 2022-09-20 18:24:59 +01:00
Ed L ef26f67c8e chore: deduplicate imports 2022-09-20 18:24:59 +01:00
Ed L 5b7ec655eb chore: bump deps, remove old login background 2022-09-20 18:24:59 +01:00
Ed L a381ba6320 feat: revamp onboarding 2022-09-20 18:24:59 +01:00
Jan 4f2fc267f9 feat: display account age next to join messages (#750) 2022-09-20 18:23:06 +01:00
Leda 2687f98952 fix: display server identity (if present) in typing indicator (#703) 2022-09-20 18:22:25 +01:00
Paul Makles 0a27632f05 chore: bump components dep 2022-09-19 10:53:36 +01:00
Paul Makles 1621e3b17d merge: branch 'master' into production 2022-09-18 12:32:36 +01:00
Paul Makles 6b9106c975 fix: correctly match protocols 2022-09-18 12:32:29 +01:00
Paul Makles e6ad6a552e feat: add focus mode 2022-09-18 12:05:48 +01:00
Paul Makles 7ba2388de4 merge: branch 'master' into production 2022-09-18 10:24:23 +01:00
Paul Makles 47bfaad508 fix: sanitise links passed to react-router
fix: flip protocol sanitisation to use a whitelist
2022-09-18 10:24:15 +01:00
Paul Makles 1a9a2786bb merge: branch 'master' into production 2022-09-17 13:02:15 +01:00
Paul Makles 61a06c3f1a fix: re-write blockquote regex to include lists 2022-09-17 13:01:02 +01:00
Paul Makles b42c24295f fix: null assertion has a chance of throwing error here 2022-09-17 10:50:26 +01:00
Paul Makles 8fd6963f38 merge: branch 'master' into production 2022-09-16 18:47:09 +01:00
Paul Makles 71e689ece3 chore: update language defns 2022-09-16 18:46:54 +01:00
Paul Makles 7bc806ec63 feat: add indication if language is not fully translated
chore: update language definitions
2022-09-16 14:55:55 +01:00
Paul Makles 1016d80e56 merge: branch 'master' into production 2022-09-08 21:28:25 +01:00
Paul Makles eab5ed033f merge: branch 'master' of https://github.com/revoltchat/revite 2022-09-08 21:24:29 +01:00
Paul Makles 263d97853a merge: branch 'master' into production 2022-09-08 17:22:00 +01:00
Paul Makles ff57dbddba fix: correctly specify headers when removing MFA 2022-09-08 11:19:26 +01:00
Paul Makles de207f0fa7 chore: add revolt.js as a submodule 2022-09-03 14:06:29 +01:00
Paul Makles 1c69756383 merge: branch 'master' into production 2022-09-03 09:19:04 +01:00
Paul Makles 02eb7d83f6 feat: use wxp logo for Windows 7 2022-09-03 09:18:56 +01:00
Paul Makles 14037ef0c7 merge: branch 'master' into production 2022-09-02 16:29:27 +01:00
Paul Makles 2c50d9be6b chore: disable list behaviour if line starts with plus 2022-09-02 16:29:20 +01:00
Paul Makles 594ef00d09 feat: add ability to leave groups / servers silently 2022-09-02 16:20:25 +01:00
Paul Makles 8608257066 merge: branch 'master' into production 2022-09-02 14:42:47 +01:00
Paul Makles 7626a1f461 fix: remove stray text 2022-09-02 14:40:17 +01:00
Paul Makles 83ca6f489e feat: add new reaction button to list 2022-09-02 14:35:16 +01:00
Paul Makles b7a10bb9ab fix: ensure blockquotes are broken 2022-09-02 14:17:56 +01:00
Paul Makles f6be6d7cf8 feat: change emojis page from grid to list 2022-09-02 14:17:42 +01:00
Paul Makles dfbba41be4 feat: redesign emoji uploader 2022-09-02 13:42:34 +01:00
Paul Makles 88bc8f93b6 merge: branch 'master' into production 2022-09-01 14:04:27 +01:00
Paul Makles 024fc6853b chore: bump language definitions 2022-09-01 14:04:09 +01:00
Paul Makles 5bd2d24c56 feat: display timeout status on client 2022-09-01 14:00:43 +01:00
Paul Makles 5c50bed33d chore: update language definitions 2022-08-18 12:56:26 +02:00
Paul Makles 07fd598bf9 fix: don't collapse whitespace / newlines 2022-08-18 12:56:21 +02:00
Paul Makles a234e3a582 chore: update form components to be more reliable 2022-08-15 20:20:25 +02:00
Paul Makles a77f2f9b4d chore: remove stray log 2022-08-15 19:37:29 +02:00
Paul Makles 787d5840d2 chore: hide discovery button if not pointing to Revolt 2022-08-15 19:32:02 +02:00
Paul Makles 353507e17a fix: build components during build 2022-08-09 12:52:20 +02:00
Paul Makles 1b41ca03d9 chore: revert back to yarn portals 2022-08-09 12:40:03 +02:00
Paul Makles 3ca2b12dfc chore: add @revoltchat/ui as a submodule 2022-08-09 12:36:16 +02:00
Paul Makles ff41219cf4 Merge branch 'master' into production 2022-08-08 16:08:12 +01:00
Sophie L 7cf7402cea fix: styling fixes (#721) 2022-08-08 16:06:21 +01:00
Leda c2a729a5e0 fix: render markdown inside/after HTML tags
fixes #733
2022-08-08 16:02:57 +01:00
Alice Harris e9977b2a76 fix: large images in website previews escape embed box 2022-08-08 16:02:04 +01:00
Paul Makles f76e53649b Merge branch 'master' into production 2022-08-08 15:30:21 +01:00
Paul Makles a4b8fb5fc2 chore: update strings and permission lists 2022-08-08 15:30:10 +01:00
Paul Makles c9264e1a49 Merge branch 'master' into production 2022-08-08 15:22:17 +01:00
Paul Makles 9a5653bc02 chore: update lang submodule 2022-08-08 15:22:03 +01:00
Paul Makles 50fd3b2d11 chore: clean up repository 2022-08-08 15:21:46 +01:00
Paul Makles 58f294b790 feat: add reaction button to overlay 2022-08-08 15:15:20 +01:00
Paul Makles e1d3ad1675 chore: update language submodule 2022-08-08 14:36:34 +01:00
Paul Makles b2e0b5a035 merge: branch 'master' into production 2022-07-31 12:20:41 +02:00
Paul Makles 11a17feaae chore: handle all updates to messages 2022-07-31 12:20:25 +02:00
Paul Makles ca69a0b4c5 fix: remove eye_speech_bubble (cursed) 2022-07-31 11:38:22 +02:00
Paul Makles 11e8cd652d merge: branch 'master' into production 2022-07-30 12:30:07 +02:00
Paul Makles 6e70b55d02 chore: update lang 2022-07-30 12:29:50 +02:00
Paul Makles dedc1e0666 feat: add corresponding UI for interactions reactions 2022-07-30 12:23:56 +02:00
Paul Makles 9a70bb0eff merge: branch 'master' into production 2022-07-18 13:07:04 +01:00
Paul Makles 791f4dfd89 chore: bump lang submodule 2022-07-18 13:04:03 +01:00
Paul Makles 3fbf315587 fix: add LoadSuspense around verification page 2022-07-18 13:02:40 +01:00
Paul Makles 9807ef9c9a fix: match optional whitespace in quote regex 2022-07-18 12:59:17 +01:00
Paul Makles 96017c5f33 merge: branch 'master' into production 2022-07-16 15:18:06 +01:00
Paul Makles 084c90613f feat: add reactions 2022-07-16 15:17:02 +01:00
Paul Makles dbe8a64ffc fix: pass-through to color / unset if no gradient 2022-07-16 12:59:59 +01:00
Paul Makles 176c7883c8 merge: branch 'master' into production 2022-07-15 21:51:24 +01:00
Paul Makles 73fd35bf46 feat: add change group ownership / text system msg 2022-07-15 21:47:32 +01:00
Paul Makles 1a0b4b5703 feat: render masquerade colour 2022-07-15 21:38:11 +01:00
Paul Makles 62a427b7a7 feat: add separator to recovery codes when copying 2022-07-15 16:41:02 +01:00
Paul Makles 64e7038532 chore: bump revolt.js dependency 2022-07-15 16:25:51 +01:00
Paul Makles 7e88e733d5 feat: change colour rendering 2022-07-15 16:17:15 +01:00
Paul Makles 70bda88383 merge: branch 'master' into production 2022-07-14 17:14:12 +01:00
Paul Makles f3822b625d feat: make emoji picker close on select / interact elsewhere 2022-07-14 17:13:51 +01:00
Paul Makles df1b39256d feat: show message that user can't message another 2022-07-14 15:06:37 +01:00
Paul Makles c9066aba2d merge: branch 'master' into production 2022-07-13 13:06:47 +01:00
Paul Makles fc8cfa5419 fix: actually log out invalidate sessions 2022-07-13 13:05:43 +01:00
Paul Makles 3204d176de merge: branch 'master' into production 2022-07-13 12:57:07 +01:00
Paul Makles e3a526e2d7 fix: type errors with markdown content 2022-07-13 12:57:01 +01:00
Paul Makles 57887dc86f merge: branch 'master' into production 2022-07-13 12:53:58 +01:00
Paul Makles 4f3f6e26cf feat: convert html AST nodes to text 2022-07-13 12:32:39 +01:00
Paul Makles 2214efe1bc chore: hide emoji settings if no perm 2022-07-12 14:21:44 +01:00
Paul Makles 030c211230 fix: internal links would not redirect properly 2022-07-12 14:15:53 +01:00
Paul Makles 7f6db77c4f chore: change quote depth limit to 5 from 3 2022-07-11 15:33:57 +01:00
Paul Makles 924448dc2c fix: remove html entities using AST plugin 2022-07-11 15:33:34 +01:00
Paul Makles 77c3f8d1bc chore: bump @revoltchat/ui to 1.0.76 2022-07-10 15:30:34 +01:00
Paul Makles 80943afcf6 fix: don't hide settings button behind bottom nav
fixes #691
2022-07-10 14:37:11 +01:00
Paul Makles f792888268 chore: bump UI library 2022-07-10 14:27:00 +01:00
Paul Makles aad9a30266 fix: correctly pass-through preview URLs 2022-07-10 13:57:47 +01:00
Paul Makles 0ec7e5e116 feat: try to load any 'valid' emoji 2022-07-10 13:53:19 +01:00
Paul Makles 2b65e98cd3 merge: pull request #730 from revoltchat/feat/emojis 2022-07-09 17:56:49 +01:00
Paul Makles 448722225e fix: change quote matching Regex 2022-07-09 17:55:13 +01:00
Paul Makles cb6d5a3828 feat: update emoji picker; move settings bhnd expr 2022-07-09 17:53:40 +01:00
Paul Makles 354c22108e feat: it's morbing time 2022-07-08 18:38:39 +01:00
Paul Makles ec96dde694 feat: render custom emoji 2022-07-08 17:14:15 +01:00
Paul Makles 445e9537d4 merge: remote-tracking branch 'origin/master' into feat/emojis 2022-07-08 17:02:39 +01:00
Paul Makles c12d40d0da fix: correct mention styling 2022-07-08 16:58:21 +01:00
Paul Makles 7e20d5029e fix: underline anchor; prevent jitter on render 2022-07-08 15:45:16 +01:00
Paul Makles 262b931810 fix: actually render HTML out instead of obliterating it 2022-07-08 15:36:18 +01:00
Paul Makles 4a85dd69cf fix: limit maximum quote depth to 3 2022-07-08 15:19:16 +01:00
Paul Makles 34bb2bbc13 feat: switch to remark from markdown-it (big)
* replaces old mentions with avatar and display name
* renders things directly through React
* replaces most of the markdown hacks with custom AST components
* adds a tooltip to codeblock "copy to clipboard"
2022-07-08 14:24:48 +01:00
Paul Makles b541301cb1 feat: add test emoji page 2022-07-07 17:33:33 +01:00
Paul Makles a766183f01 feat: port CreateBot to modal form
fixes #723
2022-07-06 13:15:33 +01:00
Paul Makles 47e3d0bdb5 chore: update README to reflect project changes [skip ci] 2022-07-06 13:09:17 +01:00
Paul Makles c51b024329 chore(refactor): delete context/revoltjs folder 2022-07-06 13:08:03 +01:00
Paul Makles e37140dcd0 chore: minor styling change for clipboard modal 2022-07-06 12:50:38 +01:00
Paul Makles 705dcd001b feat: add correct submit buttons to form modals 2022-07-06 12:49:24 +01:00
Paul Makles f9c6f5cd9d chore: delete intermediate 2022-07-05 21:13:42 +01:00
Paul Makles f7ff7d0dfe feat: port CreateCategory / fix Channel 2022-07-05 20:55:24 +01:00
Paul Makles 29fb8e0064 feat: port CreateChannel modal 2022-07-05 20:37:40 +01:00
Paul Makles 4009b19f9c feat: port BanMember, KickMember modals 2022-07-05 20:25:00 +01:00
Paul Makles ec347f585d feat: port CreateInvite 2022-07-05 20:11:32 +01:00
Paul Makles 160d71684f feat: port DeleteMessage and Confirmation 2022-07-05 18:46:13 +01:00
Paul Makles 79c90c1b00 feat: port input modals to new system 2022-07-05 17:53:41 +01:00
Paul Makles 23dec32476 feat(refactor): partially rewrite appearance settings 2022-07-05 15:49:08 +01:00
Paul Makles a24e027a48 chore: clean up picker / prism imports 2022-07-04 19:04:27 +01:00
Paul Makles 6e70937825 chore: turn emoji picker into an experiment 2022-07-01 19:32:27 +01:00
Paul Makles e4b61819d3 feat: mogus vented 2022-07-01 19:23:50 +01:00
Paul Makles 6bf58e8379 feat: test emoji picker design 2022-07-01 18:09:53 +01:00
Paul Makles 5dfe72c093 merge: pull request #717 from revoltchat/chore/client-fsm 2022-07-01 15:12:04 +01:00
Paul Makles c4ac7a1b6d Merge branch 'master' into chore/client-fsm 2022-07-01 15:11:39 +01:00
Paul Makles 401b2d4990 feat: port UserProfile, Onboarding, CreateBot to legacy 2022-06-30 20:39:00 +01:00
Paul Makles 0d3f29515e feat: port ImageViewer 2022-06-30 19:34:04 +01:00
Paul Makles 1664aaee15 feat: add ServerInfo, port ChannelInfo 2022-06-30 19:06:49 +01:00
Paul Makles 8501e33103 chore(refactor): move RequiresOnline into controllers 2022-06-29 17:33:23 +01:00
Paul Makles a2a52e237d chore(refactor): remove Notifications component 2022-06-29 17:31:59 +01:00
Paul Makles 05516c5823 fix: hide push notifications on electron app 2022-06-29 16:46:25 +01:00
Paul Makles 45692999bf chore(refactor): remove SyncManager 2022-06-29 16:41:26 +01:00
Paul Makles 1fcb3cedc1 feat: consistent authentication flow
fix: missing suspense on login
feat: re-prompt MFA if fail on login
2022-06-29 16:27:57 +01:00
Paul Makles 0261fec676 chore: deprecate RevoltClient context 2022-06-29 16:02:35 +01:00
Paul Makles 0e86f19da2 chore(doc): document client controller 2022-06-29 14:49:48 +01:00
Paul Makles 31220db8fe feat: fully working onboarding on login 2022-06-29 11:48:48 +01:00
Paul Makles 66ae518e51 feat: make login functional again 2022-06-29 10:52:42 +01:00
Paul Makles 8d505c9564 chore: clean up FSM code 2022-06-29 10:28:24 +01:00
Paul Makles 5f2311b09c feat: implement useClient from client controller 2022-06-28 19:59:58 +01:00
Paul Makles ce88fab714 feat: get fsm to a working testing state 2022-06-28 13:49:50 +01:00
Paul Makles b53d3bce13 fix: don't display date on ("new") message divider 2022-06-28 13:23:45 +01:00
Paul Makles c997286340 patch: temporarily fix issue with public invites 2022-06-28 13:22:10 +01:00
Paul Makles 80f4bb3d98 feat: build finite state machine for sessions 2022-06-28 13:20:08 +01:00
Paul Makles 1cfcb20d4d chore(refactor): rename context/modals to controllers/modals 2022-06-27 17:56:06 +01:00
Paul Makles 95ebd935ed fix: duct-tape fix the bot edit issues
fixes #629
2022-06-21 11:14:51 +01:00
Paul Makles 3b7c1cbe20 chore(ci): allow any tag [skip ci] 2022-06-21 11:14:04 +01:00
Paul Makles cb0a521473 fix: no client context on ModifyAccount
fix: no reactivity on account settings

closes #706
fixes #683
fixes #702
2022-06-21 10:57:58 +01:00
Paul Makles aa9974149c fix: bump @revoltchat/ui 2022-06-18 17:13:00 +01:00
Paul Makles 569864167e fix: consider whether alert is present in height calc 2022-06-18 17:05:05 +01:00
Paul Makles f185dec461 feat: handle system alerts and poll rate changes 2022-06-18 17:03:04 +01:00
Paul Makles 03e177f865 feat(modal): implement new server identity modal
closes #172
2022-06-18 15:54:17 +01:00
Paul Makles f685352963 fix: temporarily hack in mention to profile flow 2022-06-18 15:06:24 +01:00
Paul Makles 6755217ad2 feat(modal): port ModifyAccount and PendingRequest 2022-06-18 15:02:59 +01:00
Paul Makles 211ff2058a chore: remove legacy Redux migration 2022-06-18 14:49:59 +01:00
Paul Makles b7be9f8c03 feat(modal): port LinkWarning 2022-06-18 14:19:31 +01:00
Paul Makles 241b9cd27b feat(modal): port Clipboard 2022-06-18 12:33:22 +01:00
Paul Makles d10bd96900 feat(modal): port Error and ShowToken 2022-06-18 12:25:56 +01:00
Paul Makles 374be319c4 feat(modals): port SignedOut and SignOutSessions 2022-06-18 11:56:05 +01:00
Paul Makles 0ee7b73d61 feat: re-work modal behaviour to be more natural 2022-06-18 11:22:37 +01:00
Paul Makles 63d5f6bb7d fix: show update button on native if frame off 2022-06-17 16:57:13 +01:00
Leda 34e6995d86 fix: modify account, and create bot forms not being submitted (#697) 2022-06-17 16:51:10 +01:00
Paul Makles a1ef1dce5e fix: remove status request
closes #685
closes #643
2022-06-17 16:50:48 +01:00
Paul Makles a190a51d0b fix(qr): render the QR code consistently 2022-06-14 18:01:19 +01:00
Paul Makles c9127d6cf3 fix(css): please let the torture stop 2022-06-14 17:21:52 +01:00
f0d2e31b17 fix: bottom nav alignment is wrong (#684)
Co-authored-by: div2005 <div2005@tuta.io>
Co-authored-by: Paul Makles <paulmakles@gmail.com>
2022-06-14 17:19:18 +01:00
Paul Makles 220a28a151 fix: bottom nav button alignment
fixes #679
2022-06-14 17:16:18 +01:00
Paul Makles b44779c89a fix: user picker checkbox + width
fixes #657
fixes #681
2022-06-14 17:10:59 +01:00
Paul Makles 5835064219 chore: fix merge conflicts 2022-06-14 16:30:42 +01:00
Paul Makles b9da79bc11 chore: bump preact-context-menu 2022-06-14 16:27:46 +01:00
Leda ba99cbaf2a fix: bug in user/channel mention when query text is empty (#659) 2022-06-14 15:24:13 +01:00
LedaandPaul Makles fc0c7611d4 fix: bug where channel icon scales with channel name (#661)
Co-authored-by: Paul Makles <paulmakles@gmail.com>
2022-06-14 15:23:58 +01:00
Leda 2e9c013ed8 fix: display voice channel as link in messages (#658)
* fix: display voice channel as link in messages

* chore: format
2022-06-14 15:17:00 +01:00
Leda 3e40a61624 fix: bug in join server button when in light theme (#660) 2022-06-14 15:13:31 +01:00
Paul Makles 4e22ccb2f7 chore: duct-tape fix for semver library breaking 2022-06-12 22:24:29 +01:00
Paul Makles 00718245f9 fix: make the changelog button in settings work 2022-06-12 22:21:23 +01:00
Paul Makles cd8ab6739b feat: add changelog modal 2022-06-12 22:19:41 +01:00
Paul Makles 5eabd2861f chore: unlink components 2022-06-12 21:19:27 +01:00
Paul Makles a404ff7fe0 feat: add auto-update and out-of-date indicator 2022-06-12 21:16:42 +01:00
Paul Makles 7680931f5f chore: i18n 2022-06-12 21:11:23 +01:00
Paul Makles c1324108e3 fix(eslint): rules included deprecated plugin 2022-06-12 19:38:29 +01:00
Paul Makles 56770d40df chore: bump language submodule 2022-06-12 19:29:17 +01:00
Paul Makles 64f19ec2c0 chore: display error on load if present 2022-06-12 19:27:18 +01:00
Paul Makles dbb1c1e8fa feat: finalise 2FA login 2022-06-12 19:24:59 +01:00
Paul Makles c686e85d37 feat: add MFA recovery codes 2022-06-12 16:30:37 +01:00
Paul Makles 8eefc87b05 chore(refactor): clean up component folder structure 2022-06-12 15:24:00 +01:00
Paul Makles 645e1af6db chore: refactor account UI 2022-06-12 15:07:30 +01:00
Paul Makles 8103cc03cf chore: move "resend verification" button 2022-06-12 12:16:15 +01:00
Paul Makles bdc527ebbe chore(ci): re-enable mirroring 2022-06-10 18:50:14 +01:00
Paul Makles 8a2826da91 fix: use class in markdown rendering 2022-06-10 17:36:59 +01:00
Paul Makles bd50378234 feat: add account deletion confirmation route 2022-06-10 17:20:31 +01:00
Paul Makles 71f8fc86a4 chore: fix build errors 2022-06-10 17:00:37 +01:00
Paul Makles 277eaa685d chore(ci): stop mirroring to GitLab [skip ci] 2022-06-10 16:53:02 +01:00
Paul Makles e81b8ed472 feat: new modal renderer + mfa flow modal 2022-06-10 16:52:12 +01:00
Paul Makles 6be0807433 feat: add disable / delete funct; bump revolt-api 2022-06-10 14:32:21 +01:00
Paul Makles e0ca1681bd chore(refactor): move and re-organise types folder 2022-06-10 14:11:38 +01:00
Paul Makles ebcbe4bd4b chore: read version from package.json 2022-06-10 12:18:02 +01:00
Paul Makles ec8b51f559 chore: repository clean-up 2022-06-09 14:58:39 +01:00
Paul Makles 72e60b7528 chore: clean up build errors 2022-06-09 14:50:05 +01:00
Paul Makles 3399991824 merge: pull request #650 from revoltchat/chore/component-migration 2022-06-09 14:49:17 +01:00
Paul Makles 6497e11fb0 Merge branch 'master' into chore/component-migration 2022-06-09 14:48:45 +01:00
Paul Makles 7544c78360 chore: prepare account management buttons 2022-06-09 14:47:53 +01:00
Paul Makles 040c4367f7 chore: clean up code 2022-06-09 14:47:33 +01:00
Paul Makles ee80dfd3c8 fix: pull Client into state early
(should fix empty server list)
2022-06-09 14:40:54 +01:00
Paul Makles 67c8418c31 fix: actually resolve the error from requests 2022-06-07 17:00:11 +01:00
Paul Makles 2056232759 fix: build errors 2022-05-30 16:46:07 +01:00
Paul Makles 81bf325990 feat: add column element 2022-05-30 16:15:52 +01:00
Paul Makles 906f15f103 fix: use shape="circle" for friend component 2022-05-30 15:54:55 +01:00
Paul Makles ea106a3902 chore: bump @revoltchat/ui 2022-05-30 15:48:33 +01:00
Paul Makles 41e533ab59 feat(@ui): port Modal component 2022-05-30 15:45:14 +01:00
Paul Makles 68b9d5ea79 feat(@ui): migrate category / overline and header 2022-05-30 14:42:09 +01:00
Paul Makles 673efc0586 fix: make context menu line divider compact 2022-05-30 12:57:30 +01:00
Paul Makles 74f8c552ed feat(@ui): port category button 2022-05-30 12:56:47 +01:00
Paul Makles f3bdbe52d9 feat(@ui): migrate textarea and tip 2022-05-30 12:47:13 +01:00
Paul Makles b4777e9816 feat(@ui): migrate line divider, preloader and save status 2022-05-30 12:40:01 +01:00
Paul Makles ab77d4a812 feat(@ui): migrate radio 2022-05-30 12:29:56 +01:00
Paul Makles 1d243d4762 feat(@ui): migrate input box 2022-05-30 12:26:16 +01:00
Paul Makles 2f9bfbf83f chore: remove Masks component 2022-05-30 12:19:32 +01:00
Paul Makles c2547b3ead feat(@ui): migrate icon button 2022-05-30 12:01:47 +01:00
Paul Makles a64fe61199 feat(@ui): migrate date divider and details 2022-05-29 16:40:02 +01:00
Paul Makles 1bd138d6ef feat(@ui): migrate colour swatches and combo box 2022-05-29 16:38:09 +01:00
Paul Makles 4bcfa601a5 feat(@ui): migrate checkbox component 2022-05-29 16:34:54 +01:00
Paul Makles 20d31babce feat(@ui): migrate Banner component 2022-05-29 15:43:36 +01:00
Paul Makles 12b9716043 fix: open last opened server channel instead of first 2022-05-27 22:32:06 +01:00
Paul Makles e2d9e41a58 chore: bump revolt.js and @revoltchat/ui 2022-05-27 22:31:12 +01:00
Paul Makles bdf741e0ee feat: add "ordering" data store 2022-05-27 21:21:42 +01:00
Paul Makles 588cb7c019 feat: finish reimplementation of server list 2022-05-27 21:21:42 +01:00
Paul Makles 94dd4b464a chore: server list integration test 2022-05-27 21:21:42 +01:00
Paul Makles 1e3fe45075 fix: unlink @revoltchat/ui 2022-05-25 16:47:30 +01:00
Paul Makles 9e948175b3 chore: update and add new languages 2022-05-25 16:43:59 +01:00
Paul Makles 6eee71ddab chore: add config.yml [skip ci] 2022-05-24 16:18:38 +01:00
Paul Makles 7e245df179 fix: correct naming error [skip ci] 2022-05-24 13:18:47 +01:00
Paul Makles 2e13685998 chore: add build / publish script [skip ci] 2022-05-24 13:17:30 +01:00
Paul Makles 0f3b1b0491 chore: bump language submodule 2022-05-24 12:53:51 +01:00
Jan 8efe3ecbc0 feat: add warning on self-hosted instances (#645)
* feat: add warning on self-hosted instances

* update text
2022-05-24 12:53:06 +01:00
Paul Makles a35e68799a chore: update language submodule 2022-05-23 21:22:22 +01:00
Paul MaklesandEd L be12c6da20 chore: migrate Button to @revoltchat/ui (#617)
* chore: start moving Button over

* fix: convert ServerIdentityModal

* fix: modal button styling

* fix: popover styles

* fix: clean up references to ui/*.*

* fix: button sizing

Co-authored-by: Ed L <beartechtalks@gmail.com>
2022-05-23 21:21:29 +01:00
Jan 6fdd701b38 feat: Add option to show Send button on Desktop (#628) 2022-05-23 21:19:55 +01:00
kaname-png e46a6d55d6 chore: minor ui fixes for mybots page 2022-05-23 21:14:56 +01:00
kaname-png 32d37777f2 fix(components): wrong calculation of dimensions of embeds layouts 2022-05-23 21:14:56 +01:00
Paul Makles 6fcdbd1cef fix: remove explicit cast to string for msg.content 2022-05-21 16:59:34 +01:00
Paul Makles 73d51a3e29 fix: edit bot av / bg 2022-05-19 13:49:38 +01:00
Paul Makles 4541a34cef Merge branch 'master' into production 2022-05-19 13:42:15 +01:00
Paul Makles 5d7b8f1851 fix: add overflow to attachment grid 2022-05-19 13:42:06 +01:00
Paul Makles dc110ca082 chore: bump deps. 2022-05-19 13:37:33 +01:00
Paul Makles 97ab6c075d feat: add lightspeed.tv support 2022-05-19 13:37:24 +01:00
Paul Makles 744afe2f6a feat: GIF and Video embed support 2022-05-19 13:34:19 +01:00
Paul Makles e74c3a5f80 fix: return correct error 2022-05-17 21:10:47 +01:00
Paul Makles f8e67af962 fix: allow viewing text files > 100kB
closes #35
2022-05-16 19:32:58 +01:00
Paul Makles 0e5f64afd6 revert: "chore(deps-dev): bump prismjs from 1.24.1 to 1.27.0" (#625) 2022-05-11 12:11:13 +01:00
dependabot[bot] ee899e795c chore(deps-dev): bump prismjs from 1.24.1 to 1.27.0 (#620)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-10 12:30:36 +01:00
Paul Makles 8f0c69f528 chore: remove super and subtext (closes #438) 2022-05-10 12:27:44 +01:00
Paul Makles e1e3e55dc7 feat: add minimise to tray option 2022-05-10 12:26:57 +01:00
Paul Makles cca97f9bb9 fix: remove un-used dep 2022-05-09 22:14:13 +01:00
Paul Makles 9e90ce6854 fix: override backdrop-filter if transparency effects are off
closes #550
2022-05-09 22:05:57 +01:00
Paul Makles 81e9774cc0 fix: re-sort member sidebar on channel change
closes #593
2022-05-09 22:00:17 +01:00
Paul Makles e9cba5b4e2 fix: allow selecting text on profiles
closes #433
2022-05-09 21:58:08 +01:00
Jan e9258d7c23 fix: #614 breaks in desktop client :trollface: (#616) 2022-05-08 22:17:36 +01:00
Jan 7345172271 fix: don't open new tab to download attachment (#614) 2022-05-08 21:53:07 +01:00
Paul Makles 4f48e3d176 chore: remove files that are being moved [skip ci] 2022-05-08 12:53:22 +01:00
Paul Makles eb1bd27ec0 feat: add ban list search (closes #579) 2022-05-08 10:40:45 +01:00
Paul Makles 0ce128b108 fix: handle null relationship; fixes #607
not sure if this was intended from the API side
2022-05-08 10:19:33 +01:00
DaesunandPaul Makles ac0c100846 feat(sessions): Add confirmation prompt (#575)
Reopening of issue #572
Fixes issue #42
Translations added on PR 31 of revolt/translations

Co-authored-by: Paul Makles <paulmakles@gmail.com>
2022-05-07 21:31:53 +01:00
Jan 93ba922536 fix: permission lists show wrong permissions (#603) 2022-05-07 21:31:18 +01:00
Paul Makles 1590c5abab chore: use conventional issue title 2022-05-07 20:35:44 +01:00
Paul Makles dbbe66def6 chore: use conventional issue title 2022-05-07 20:35:35 +01:00
Paul Makles 2d5128fca8 chore: bump lang 2022-05-07 16:58:01 +01:00
Paul Makles 7ffa0744c0 fix: don't cast message.content 2022-05-07 16:55:46 +01:00
Paul Makles ce5dedcecf chore: bump revolt.js 2022-05-07 16:41:53 +01:00
Paul Makles 0818c32895 chore: add defaults 2022-05-07 15:42:00 +01:00
Paul Makles 567888a223 chore: update lang 2022-05-07 15:24:37 +01:00
Paul Makles 0de941394d feat: show 'bridge' badge for masqueraded bot messages 2022-05-07 15:24:12 +01:00
Paul Makles 4c08483848 feat: add message / block for bots 2022-05-07 15:24:00 +01:00
Paul Makles 83a3585940 chore: add notice 2022-05-07 10:45:18 +01:00
Paul Makles 08e1db6d35 feat: show that permissions / roles are not editable
closes #594
2022-04-30 15:54:27 +01:00
Paul Makles 4ccfaea973 feat: add a preloader for members list 2022-04-30 15:22:06 +01:00
Paul Makles a66e96677f revert: #588 2022-04-30 14:45:57 +01:00
Paul Makles b2a9cfd987 feat: "modernise" Docker image and use alpine 2022-04-29 16:17:31 +01:00
Paul Makles f3cf4f01ca chore(ci): go in the opposite direction 2022-04-29 15:49:05 +01:00
Paul Makles 37a5e3af5f chore(ci): try to fix memory issues 2022-04-29 15:32:19 +01:00
Evilhotdog 4c87bc54a8 feat: add sounds when user joins/leaves call (#587) 2022-04-29 15:22:18 +01:00
Freeplay 2b8b9c959f fix: bottom padding in settings (#588) 2022-04-29 15:21:53 +01:00
Omkaar 988a703837 fix: typo #570 (#576)
This commit fixes a typo.
2022-04-29 13:58:18 +01:00
Paul Makles 37d5ba24c5 merge: branch 'quark/permissions' 2022-04-29 13:48:38 +01:00
Paul Makles d876f52346 fix: bump revolt.js to fix perm calculation 2022-04-28 17:53:27 +01:00
Paul Makles 14a2f4882e chore: refactor permission checks 2022-04-28 15:02:22 +01:00
Paul Makles b217571325 chore: translations for role page 2022-04-28 14:51:34 +01:00
Paul Makles c20f630d10 chore: bump lang submodule 2022-04-27 20:19:11 +01:00
Paul Makles d31237e9e1 chore: clean up 2022-04-27 20:18:51 +01:00
Paul Makles 890cb94acb fix: use working esm package for UI 2022-04-24 14:10:23 +01:00
Paul Makles 147880a447 chore: update README 2022-04-23 17:25:33 +01:00
Paul Makles eee1d4060f feat: new permissions menus
closes #322
2022-04-22 21:06:12 +01:00
Paul Makles 4aad0493ae fix: add padding to message box when no file perm
closes #84
2022-04-21 19:35:45 +01:00
Paul Makles 36051b3374 chore: bump revolt.js 2022-04-21 19:14:05 +01:00
Paul Makles 48558dd5e4 chore: split content into content, system 2022-04-21 17:46:14 +01:00
Paul Makles d129d9816d fix(invites): use new invite handling 2022-04-09 19:02:41 +01:00
Paul Makles 7b7f3c3af8 fix(state): save state on reset 2022-04-09 19:02:33 +01:00
Paul Makles 6915186738 fix: actually close error modal 2022-04-09 19:02:22 +01:00
Paul Makles 56f59d132b fix: don't paste file anyways if too large 2022-04-09 19:02:10 +01:00
Paul Makles b2f4411850 feat: switch to revolt.js@6.0.0 + new revolt-api 2022-04-09 15:47:04 +01:00
Daesun 002cb5e6c1 fix(login): Fix render of welcome and subtitle text per page (#573) 2022-04-05 23:42:32 +01:00
Daesun 777d06f469 fix(voice channel): Fix overflow and scrolling (#574) 2022-04-05 23:42:15 +01:00
Ed L 3f9e6fc2e2 feat: notify user of aup when creating server (#495) 2022-04-05 23:38:59 +01:00
Paul Makles 5d4ef8b178 chore: update language definitions 2022-03-29 21:23:34 +01:00
Alyxia Sother c3f4ad8f8d improvement(plugins-page) Use more i18 components 2022-03-29 20:50:43 +01:00
Alyxia Sother cdc077ffa1 improvement(plugins-page): Refactor using card
I've (hackily so) written my own plugin card to replace the previous
usage of the `Checkbox` component.
Beware, ye who tread these lands, for they are decrepid.
2022-03-29 20:50:43 +01:00
Alyxia Sother 0d6720e3b1 improvement(plugins-page): Use i18n Text
Note that, since the translation PR has not been made yet, this patch
was used to test the changes: https://public.alyxia.dev/revolt/lang.diff
2022-03-29 20:50:43 +01:00
Alyxia Sother 07439bd5f6 feat: Implement a plugins page 2022-03-29 20:50:43 +01:00
Reinis Mazeiks 8021169131 feat: prevent accidental send while in code block (#557) 2022-03-29 20:49:25 +01:00
Avis OrsettiandEd L b4c7fe543f chore: add "OpenDyslexic" font (#529)
Co-authored-by: Ed L <beartechtalks@gmail.com>
2022-03-29 20:49:16 +01:00
Ed L 28efdd0ea4 fix: fix iOS regex (#519) 2022-03-29 20:47:54 +01:00
Ed L c8c470d383 chore: specify yarn 1 for corepack (#551) 2022-03-29 20:47:35 +01:00
Ed L 83c9c36e16 chore: remove highlight.js, use node 16 for ci (#511) 2022-03-29 20:47:19 +01:00
Paul Makles ca240fb6d7 chore: clean up the patch from yesterday 2022-03-29 19:23:46 +01:00
Paul Makles cf049bd4ee fix: don't delete, set undefined 2022-03-28 09:51:35 +01:00
Paul Makles b3be822568 feat(experiment): basic plugin API 2022-03-27 21:45:54 +01:00
Paul Makles f9f0d5c55a fix: pre-cache to not include extra files 2022-03-25 11:17:04 +00:00
Paul Makles 3a12f92867 fix(ci): running out of memory, need to remove ARM 2022-03-24 18:23:53 +00:00
Reinis Mazeiks e93db2345f feat: add Latvian to the language list (#556) 2022-03-16 15:00:41 +00:00
Paul Makles ce643a5a09 chore: remove commit section from template 2022-03-09 21:36:47 +00:00
Paul Makles d01b9cae40 fix: re-enable callouts 2022-03-06 19:52:24 +00:00
Paul Makles 8e1031cf91 fix(iOS): disable touch callouts globally 2022-03-06 19:39:21 +00:00
Paul Makles f65c9bdd5e fix: context menus are now bound by spread 2022-03-06 19:13:12 +00:00
Paul Makles 6a465c1c08 fix: bump preact-context-menu 2022-03-05 15:06:18 +00:00
Paul Makles 009565f0cc fix(iOS): fix image / video uploads from iOS
closes #477
2022-03-05 14:56:19 +00:00
Paul Makles 18761e2181 fix(iOS): actually fix context menus
closes #138
2022-03-05 14:41:41 +00:00
Paul Makles 3e045cf8a8 fix(ios): try to accept all content types
for #477
2022-03-05 12:07:57 +00:00
Paul Makles 4692dda8f3 fix(iOS): bump preact-context-menu
closes #138
2022-03-05 12:07:13 +00:00
Paul Makles 20060a029d feat(vite): support legacy browsers 2022-03-04 23:42:20 +00:00
Paul Makles 741c937bb7 chore: add our signing key to assetlinks 2022-03-04 18:28:53 +00:00
Paul Makles dc3925c003 fix(invites): handle invites correctly 2022-03-04 18:26:36 +00:00
Paul Makles d95070d8c7 fix(modals): make text input full width 2022-03-04 16:53:10 +00:00
Paul Makles 498e76b535 fix(home): fix the create group button
closes #546
2022-03-04 16:46:50 +00:00
Paul Makles 0f8be1767d fix: delete singleton renderer on channel delete 2022-03-04 16:45:57 +00:00
Paul Makles 542c0482c5 fix: improve handling of inactive channels
(closes #432)
2022-03-04 16:45:44 +00:00
Paul Makles d83455c759 chore: outage over, flick the switch 2022-03-01 19:44:38 +00:00
Paul Makles d3220d7c70 fix: fix the link 2022-03-01 10:21:28 +00:00
Paul Makles 094be08bbf fix(status bar): calculate height correctly 2022-03-01 10:15:57 +00:00
Paul Makles b1a7a94d26 feat: push temporary outage information 2022-03-01 10:12:45 +00:00
Paul Makles 041c039827 feat(permission): implement new server / channel permission menus 2022-02-27 23:44:29 +00:00
Paul Makles 3632b6b351 fix(theme): better still just, handle it optionally 2022-01-31 01:05:06 +00:00
Paul Makles edaf2256d5 fix(theme): don't crash the app on invalid fonts 2022-01-31 01:04:13 +00:00
Paul Makles 334e54146b chore: clean up stray console.log 2022-01-29 11:45:56 +00:00
Paul Makles b4642b1efe chore: bump revolt.js
fixes #528
2022-01-29 11:11:19 +00:00
Paul Makles 27f6420da2 fix: just re-enable context menu for now 2022-01-29 10:25:57 +00:00
Paul Makles c5286071ff chore: update language definitions 2022-01-29 09:42:15 +00:00
Paul Makles 1089651cb0 fix(member sidebar): more aggressively check if we need to update 2022-01-29 09:42:04 +00:00
trashtemp 6020d94c3a fix: changed anim name 2022-01-22 17:13:06 +01:00
Paul Makles a81645090e chore: update language definitions 2022-01-21 23:18:10 +00:00
Paul Makles e4b1be591c fix(theme): use alternative contrast algorithm 2022-01-21 23:17:55 +00:00
trashtemp 50b0e0f5f2 fix: added flex shrink to mention 2022-01-21 09:47:18 +01:00
trashtemp 2c321bf0f1 fix: added flex shrink to message reply 2022-01-21 09:46:11 +01:00
trashtemp 074057b481 fix: added animation to jumpbar 2022-01-21 09:15:31 +01:00
trashtemp 4d769f42e1 fix(settings): added bezier curve to animations 2022-01-21 09:06:55 +01:00
trashtemp 622f2b2bca fix(messagebox): fixed z-index 2022-01-21 08:30:54 +01:00
trashtemp e959f29eae fix(reply): used var instead of fixed hex 2022-01-21 00:26:06 +01:00
trashtemp 344519de00 fix: added string to toggle 2022-01-21 00:13:13 +01:00
trashtemp ad40197df9 fix: flex shrink 2022-01-21 00:04:44 +01:00
trashtemp 0de078a794 feat: new replies 2022-01-21 00:02:55 +01:00
trashtemp 6e89e10461 fix(jumpbar): fixed collision on mobile 2022-01-20 15:26:58 +01:00
trashtemp 68acfe26de fix(messagebox): mobile button increased width 2022-01-20 11:38:12 +01:00
trashtemp 13f119942c fix(settings): fixed small issues with mobile 2022-01-20 11:03:47 +01:00
trashtemp 4ea97e0209 chore: updated vite-plugin-pwa, fixes 2022-01-16 17:22:06 +01:00
trashtemp da5d82537d fix(voice): fixed header style 2022-01-16 13:44:00 +01:00
trashtemp 59fb8a3c0f fix(userprofile): entry style touch up 2022-01-15 23:00:12 +01:00
trashtemp e6684b8f78 fix(message): re-added click to usericon 2022-01-15 22:50:29 +01:00
trashtemp 2bef311f9a fix(modals): finally fixed width 2022-01-15 22:19:29 +01:00
trashtemp cc458453d3 fix(button): removed z-index 2022-01-15 20:59:14 +01:00
trashtemp 0dec90f53d chore: added fonts and weights, removed font
+ added new font-weight for Open Sans
+ added "Bitter" font
- bye bye Bree Serif :'(
2022-01-15 20:56:00 +01:00
trashtemp 79114d72b8 fix(serverheader): fixed positioning of header 2022-01-15 17:40:34 +01:00
trashtemp 8fa6f87dc2 fix(messagebox): fixed padding 2 2022-01-15 17:37:56 +01:00
trashtemp c5461f3134 fix(messagebox): fixed padding 2022-01-15 17:31:57 +01:00
Paul Makles 196bbe2634 fix(embed): exclusively use markdown renderer for text embeds 2022-01-15 16:06:41 +00:00
Paul Makles 8eb2f4e7e3 fix(messaging): bring cursor pointer back for av. 2022-01-15 15:59:25 +00:00
Paul Makles 271a126a3f fix(bottom nav): replace path instead of going back 2022-01-15 15:46:48 +00:00
Paul Makles 05266790ae fix(state): sync locale correctly 2022-01-15 15:43:32 +00:00
Paul Makles 3ce04f543f chore: update language definitions 2022-01-15 15:23:12 +00:00
Paul Makles dc527467d5 chore(locale): add languages (Bengali, Catalonian, Danish, Esperanto) 2022-01-15 15:23:03 +00:00
Paul Makles 45b9c355a7 chore: update language definitions 2022-01-15 14:47:41 +00:00
Paul Makles 60cd17d673 feat(context menu): add "mark as unread" 2022-01-15 14:46:19 +00:00
Paul Makles a58372fc42 chore: add sippy (emote) 2022-01-15 14:43:59 +00:00
Paul Makles 4996a4d785 feat(fonts): add Lexend 2022-01-15 14:42:35 +00:00
Paul Makles 56f2411652 feat(messaging): add basic support for text embeds 2022-01-15 14:11:08 +00:00
Paul Makles 4526a696c6 chore: get rid of scss warnings for snow 2022-01-15 13:49:43 +00:00
Paul Makles b5189a769f fix(build): type errors with revolt-api 2022-01-15 13:41:02 +00:00
ayntgl 2c5467c189 fix(components): convert .systemIcon to non-clickable (#498) 2022-01-15 13:37:08 +00:00
trashtemp afa76f0623 Merge branch 'master' of https://github.com/revoltchat/revite 2022-01-15 14:33:26 +01:00
trashtemp 8566cd613d fix(messagebox): fixed broken padding 2022-01-15 14:33:14 +01:00
Paul Makles e67f8f95cd fix(messaging): prevent message overlay from sticking after finishing edit 2022-01-15 13:00:27 +00:00
Paul Makles bb5f940ad1 chore: bump revolt.js 2022-01-15 12:54:18 +00:00
Paul Makles ba40da2a15 fix: don't show unread indicator on active channel 2022-01-15 12:54:18 +00:00
Paul Makles 2c8bbe7d1f feat(messaging): add message overlay logic 2022-01-15 12:54:16 +00:00
Paul Makles 7e4f4cf001 fix(messaging): hide overlay on mobile 2022-01-15 12:53:15 +00:00
Paul Makles 8bda3123da fix(unreads): allow dynamic unread changes 2022-01-15 12:53:15 +00:00
trashtemp 200db35c25 fix(msgbar): removed IconButton dependency 2022-01-15 13:33:04 +01:00
trashtemp b4555fd028 fix(import): used root path instead 2022-01-15 13:22:20 +01:00
trashtemp a3005cfcd8 fix(msgbar): use vertical dots 2022-01-15 13:16:27 +01:00
trashtemp 4a58a4082b fix(msgbar): added support for keyboard focus 2022-01-15 12:55:23 +01:00
trashtemp 79550cb2ad fix(sidebar): changed server sidebar width 2022-01-15 12:36:50 +01:00
trashtemp 8029f39c4e fix(discover): height 2022-01-15 10:56:02 +01:00
trashtemp 62335e4902 fix: added shadow on hover 2022-01-14 23:06:47 +01:00
trashtemp 0c9c712ee0 fix(msgbar): improved design, added actions 2022-01-14 22:52:19 +01:00
Paul Makles ddbef13a12 feat(tooltip): add new tooltip animation 2022-01-14 21:04:29 +00:00
f509acbe80 feat(messaging): quick action bar (#493)
Co-authored-by: Kuhn Chris <kuhnchris+github@kuhnchris.eu>
Co-authored-by: Paul Makles <paulmakles@gmail.com>
2022-01-14 21:03:52 +00:00
trashtemp fe382f9532 fix: rtl padding 2022-01-14 21:16:37 +01:00
trashtemp f01943c681 fix(typindicator): some design tweaks 2022-01-14 20:53:09 +01:00
Paul Makles 3fd56e4fdc feat: add sc minification + display class names 2022-01-14 18:51:03 +00:00
Paul Makles 8dd18c9fb4 fix: enable tooltip on status in user buttons 2022-01-14 18:51:02 +00:00
trashtemp 2dcfc340fa fix: increased margin between username and bot tag 2022-01-14 19:17:17 +01:00
trashtemp c77213f02f fix(markdown): used padding-inline-left for RTL 2022-01-14 18:43:32 +01:00
trashtemp 9c8de2b454 fix(discover): changed background color on load 2022-01-14 18:32:03 +01:00
trashtemp d6a541f380 Merge branch 'master' of https://github.com/revoltchat/revite 2022-01-14 18:14:05 +01:00
trashtemp 43c66a4e22 fix(discover): fixed discover background on load 2022-01-14 18:13:52 +01:00
Jan dd39e13623 fix: make member search case insensitive (#506) 2022-01-14 16:54:34 +00:00
Ed L 5a46c50b81 fix: properly detect ipados (#504) 2022-01-14 16:53:53 +00:00
ayntgl eea4d49743 fix(components): add padding-right to markdown code element (#499) 2022-01-14 16:49:32 +00:00
4224f83035 chore(project management): Pull Request Template (#491)
Co-authored-by: Ed L <beartechtalks@gmail.com>
Co-authored-by: Kuhn Chris <kuhnchris+github@kuhnchris.eu>
2022-01-14 16:37:40 +00:00
trashtemp 2415e98fbe fix(appearance): new strings for discover button 2022-01-14 17:16:11 +01:00
trashtemp d3ffe8e6f6 fix(sidebar): removed top margin 2022-01-14 17:04:14 +01:00
trashtemp b93f274db5 fix(embed): resolved embed width 2022-01-13 22:52:52 +01:00
trashtemp 6aaabb4815 fix(app): font sizing, better system msgs 2022-01-13 22:31:35 +01:00
Paul Makles 5a49bc7d20 fix: fix server last section state 2022-01-13 19:44:02 +00:00
trashtemp 07870c849d fix(tooltips): fixed light mode tooltip 2022-01-13 20:02:30 +01:00
trashtemp 59685a961a fix: fixed positioning 2022-01-13 20:00:53 +01:00
trashtemp a21436479c fix(mention): bolder text on mention badge 2022-01-13 19:56:49 +01:00
trashtemp 6c244d2ae7 fix(sidebar): added new settings button 2022-01-13 19:50:15 +01:00
trashtemp 8a1f20615e fix(sidebar): decreased vertical spacing 2022-01-13 19:30:42 +01:00
trashtemp a7c63c639e chore(tooltips): reworked tooltip design 2022-01-13 17:31:15 +01:00
trashtemp 3b4227d4ba fix(header): fixed comp name 2022-01-11 23:17:57 +01:00
trashtemp 3d68dab0b4 feat(search): started work on new search 2022-01-11 23:15:18 +01:00
trashtemp 7da7bc8ef7 fix(discover): fixed border radius 2022-01-11 21:26:22 +01:00
trashtemp 1cdc6b51df fix(sidebar): hide discover button on mobile 2022-01-11 21:24:02 +01:00
Paul Makles 8fcda5a6de feat: add a way to toggle offline users list 2022-01-10 23:40:47 +00:00
Paul Makles c6cc1f3672 chore: disable offline users in lounge server 2022-01-10 23:25:48 +00:00
Paul Makles af8cc984a9 fix: temporary fix for bad member list performance 2022-01-10 23:02:00 +00:00
Paul Makles 90a53452e8 fix: ignore bot and server invites in layout paths 2022-01-10 20:38:13 +00:00
Paul Makles 5768ff7c2f fix: remove theme shop from settings 2022-01-10 20:37:20 +00:00
Paul Makles 645221b384 fix(invite): fix invite embed logic 2022-01-10 20:23:06 +00:00
Paul Makles 0255c3167a feat: clean up invite bot page 2022-01-10 20:16:06 +00:00
Paul Makles bf82c24281 fix: add description to settings button 2022-01-10 20:00:29 +00:00
Paul Makles 599aab9a31 chore: update home buttons 2022-01-10 18:53:10 +00:00
Paul Makles 450b404ec5 feat: handle discover last path properly 2022-01-10 18:43:27 +00:00
Paul Makles 1d26beee10 fix: change Discover remote 2022-01-10 18:28:23 +00:00
Paul Makles 9e48c8f022 chore: update language defintions 2022-01-10 18:18:52 +00:00
Paul Makles 540c851d8f feat: full discovery integration 2022-01-10 18:18:43 +00:00
trashtemp 600ea42c0f fix(tooltip): added new tag to discover 2022-01-10 17:56:48 +01:00
trashtemp 733926dfd3 fix(discovery): added border radius 2022-01-10 10:02:17 +01:00
Paul Makles 03f44c5bed chore: bump language definitions 2022-01-10 00:57:15 +00:00
Paul Makles abecd0ec11 feat: add Revolt discover (note: has CORS issues) 2022-01-10 00:57:00 +00:00
Paul Makles 56925b3ea2 fix: use config for client in login 2022-01-08 16:51:34 +00:00
Paul Makles fc046936aa fix: use config for initial configuration fetch 2022-01-08 16:38:38 +00:00
Paul Makles 8d1e26d03e fix: fix build errors 2022-01-07 21:43:00 +00:00
Paul Makles 81af91fa1f fix: enable auto reconnect 2022-01-06 17:06:04 +00:00
Ryan AlexanderandPaul Makles cf55921077 fix: App soft crash when deleting servers (#492)
Co-authored-by: Paul Makles <paulmakles@gmail.com>
2022-01-06 10:35:07 +00:00
Paul Makles 8c1f5dfb68 chore: important fix 2022-01-05 22:58:37 +00:00
Paul Makles 188f682094 chore: add gigachad 2022-01-05 22:47:31 +00:00
Paul Makles 76c1f78ba6 chore: fix emoji path 2022-01-05 22:45:26 +00:00
Ed L 28c07e7046 meta: add issue forms (#414) 2022-01-05 21:49:09 +00:00
wefnib3rygewundib8g374vando8z e40420b7e9 chore: remove duplicate email provider (#368)
Co-authored-by: o8z <88407406+o8z@users.noreply.github.com>
2022-01-05 21:43:30 +00:00
Paul Makles e13edcbb76 fix: contain emojis; also add more 2022-01-05 15:05:29 +00:00
Ryan Alexander 6c6f6396e4 Merge pull request #487 from ryanalexander/dont-embed-discovery
fix: Don't embed discovery links
2022-01-05 17:38:03 +10:00
Ryan c28c6134c1 fix: Don't embed discovery links 2022-01-05 10:28:30 +10:00
Paul Makles 2edf122b34 fix(settings): fix mobile scrollbar padding 2022-01-04 23:11:58 +00:00
Paul Makles cad1c2fc3a chore: bump language definitions 2022-01-04 22:56:27 +00:00
Paul Makles a5a6833c2c fix: bump revolt.js and add pong timeout 2022-01-04 22:56:20 +00:00
trashtemp dfd9043ca0 fix(app): tweaked glass value for few components 2022-01-04 20:00:06 +01:00
trashtemp 62b7ba2e4d fix(settings): fixed mobile settings padding 2022-01-04 19:49:37 +01:00
Paul Makles dbf681c2e9 fix: fix again the thing above i cant be asked to write commit messages anymore 2022-01-04 15:07:46 +00:00
Paul Makles a09fbe685d fix: finally fix the members sidebar (real) 2022-01-04 15:01:53 +00:00
Paul Makles 00764b8cc3 fix(sidebar): use autorun instead of reaction 2022-01-04 14:47:39 +00:00
Paul Makles b745cdd4d7 fix: show full member list on update 2022-01-04 14:40:03 +00:00
Paul Makles 270624d5ec patch: prevent re-render on member list render 2022-01-04 14:35:08 +00:00
Paul Makles 0543e56678 fix(context menu): adjust transparency to account for Firefox 2022-01-04 14:02:07 +00:00
Paul Makles e7e8dbf23d fix: fixed jump to bottom bar positioning 2022-01-04 14:01:45 +00:00
Paul Makles 136faadcec fix: try to avoid freezing UI due to members sidebar (at least after first load) 2022-01-04 13:58:24 +00:00
Paul Makles 0609b40f81 fix: assume attachment URL is volatile 2022-01-04 13:40:52 +00:00
Paul Makles fc91a75acb fix(sidebar): don't keep refetching server members 2022-01-04 13:37:19 +00:00
Paul Makles 3baf82ca94 fix: don't assume roles exist when rendering 2022-01-04 13:35:55 +00:00
Ed L d328c9e61b feat(profile): add link to username settings (#364) 2022-01-04 13:26:42 +00:00
Paul Makles 21cd3f4558 fix: add hover effects to system message usernames
closes #411
closes #463
2022-01-04 11:12:57 +00:00
Ayyan 202a70b91f feat(UserProfile): add button to cancel outgoing friend request (#461) 2022-01-04 11:11:11 +00:00
3nt3andPaul Makles 42771f7137 feat: make kick and ban red in the context menu (#350)
Co-authored-by: Paul Makles <paulmakles@gmail.com>
2022-01-04 11:03:39 +00:00
Ryan Alexander 5735020013 feat: add markdown formatting for messages containing timestamps (#482) 2022-01-04 10:53:12 +00:00
Paul Makles 69b430d886 chore: update language definitions 2022-01-02 23:48:28 +00:00
Paul Makles c26ef4c2b2 chore: merge branch 'ui/glass-header' 2022-01-02 23:48:19 +00:00
Paul Makles f3c3017e0c fix(vite): remove "second" compression pass 2022-01-02 16:08:26 +00:00
Paul Makles 67936f53ed revert: "fix(vite): remove "second" compression pass"
This reverts commit 096e0c84c1.
2022-01-02 16:07:47 +00:00
Paul Makles 1d4eed872b chore: clean up code 2022-01-02 15:46:51 +00:00
Paul Makles 1b6275d1b5 fix(mobx): save stores on first load 2022-01-02 15:43:13 +00:00
Paul Makles d92c50fdb8 fix(mobx): ignore lack of sync object 2022-01-02 15:41:43 +00:00
Paul Makles a688063d96 feat: add better debugging flags 2022-01-02 15:33:58 +00:00
Paul Makles bdb1d939e7 chore: fix build errors 2022-01-02 13:12:22 +00:00
Paul Makles 096e0c84c1 fix(vite): remove "second" compression pass 2022-01-02 13:09:03 +00:00
insertish 668963e7a0 ci: Synced local '.github/workflows/triage_pr.yml' with remote 'workflows/triage_pr.yml' 2021-12-31 17:32:42 +00:00
insertish 0a1deb5289 ci: Synced local '.github/workflows/triage_pr.yml' with remote 'workflows/triage_pr.yml' 2021-12-31 14:50:30 +00:00
Paul Makles 73dfc0ace6 feat: add stop-gap emoji solution 😎 2021-12-31 00:49:59 +00:00
Paul Makles 7c120685d4 fix: change header borders depending on whether in server 2021-12-30 19:56:41 +00:00
Paul Makles 4773b76ea9 fix(channel header): fix action callbacks 2021-12-30 19:44:15 +00:00
Paul Makles b065fba6a4 fix: change conditions for corners on header 2021-12-30 19:14:21 +00:00
Paul Makles 85bd84713e fix: allow home button to be flush with background by removing border when not in server 2021-12-30 19:00:41 +00:00
trashtemp 504d882df8 fix(settings): removed obscure string 2021-12-30 19:53:52 +01:00
trashtemp b936d4462d Merge branch 'ui/glass-header' of https://github.com/revoltchat/revite into ui/glass-header 2021-12-30 19:42:23 +01:00
trashtemp 14ee357ac8 fix(serverlist): adjusted design 2021-12-30 19:42:12 +01:00
Paul Makles a921e8ed0d fix: apply border when sidebar collapsed 2021-12-30 18:35:13 +00:00
Paul Makles b5789126f8 chore: clean up server banner code 2021-12-30 18:27:22 +00:00
Paul Makles bad7458560 feat: provide and consume scroll offsets 2021-12-30 18:15:31 +00:00
Paul Makles 9387575372 fix(bottom navigation): change logic to avoid getting trapped in heck (friends list) 2021-12-30 18:04:47 +00:00
Paul Makles e758b23ac3 feat: implement logic for status changer in mobile settings 2021-12-30 18:04:08 +00:00
Paul Makles 66d3e7c616 fix(context menu): position above all elements 2021-12-30 17:23:24 +00:00
Paul Makles 6693f826fd fix: apply reasonable defaults to all scrollbar thumbs 2021-12-30 15:33:13 +00:00
Paul Makles 7496f484e1 fix: add template to new msg bar translation 2021-12-30 15:12:38 +00:00
trashtemp 5417632f87 fix(mobile): hide indexheader 2021-12-30 14:53:51 +01:00
trashtemp 4034e17e0d fix(mobile): cleanup 2021-12-30 14:48:13 +01:00
trashtemp bf6b996c09 fix(mobile): send button fixed 2021-12-30 14:47:32 +01:00
trashtemp b7ec4a8b78 fix(settings): scrollbar 2021-12-30 14:44:33 +01:00
trashtemp fdbd931ea7 fix(app): fixed broken scrollbar on mobile 2021-12-30 14:37:56 +01:00
trashtemp c0bf3fbc0b feat(home): added temporary text 2021-12-30 14:35:44 +01:00
trashtemp ee3f6bede7 fix(settings): fixed scrollbar for mobile 2021-12-30 14:16:31 +01:00
trashtemp 672b657f58 fix(details): fixed for glass header 2021-12-30 14:13:25 +01:00
trashtemp 675992bbc1 fix(scrollbar): fixed scrollbar 2021-12-30 14:07:41 +01:00
trashtemp fc0224562f fix(friends): fixed friends tab
* you can deny requests on mobile now
* fixed menu to work with glass header
2021-12-30 14:02:00 +01:00
trashtemp b34ed4c24c fix(friends): small fixes 2021-12-29 23:58:36 +01:00
trashtemp 9052ada1c7 chore: removed mention 2021-12-29 23:57:41 +01:00
trashtemp dec4315892 feat(emoji): added funny short codes 2021-12-29 23:04:42 +01:00
trashtemp 9c78e0912d fix(friends): fix for friends menu 2021-12-29 16:46:57 +01:00
trashtemp 086ab7b3bc fix(channel): last update before master merge 2021-12-29 16:39:35 +01:00
Paul Makles cc531705b4 feat(theme): add transparency effects toggle 2021-12-29 00:18:00 +00:00
trashtemp 5974a2b83b fix(typindicator): fixed var color 2021-12-29 00:35:26 +01:00
trashtemp 94520bf064 feat(typindicator): added glass effect 2021-12-29 00:34:02 +01:00
trashtemp 45762e4bf6 feat(contextmenu): glass effect on context menu 2021-12-29 00:10:17 +01:00
Paul Makles a4155a1153 fix: apparently fix app jumping around 2021-12-28 22:58:19 +00:00
trashtemp d20fa9e3ea fix(jumpbar): started css rewrite 2021-12-28 23:42:50 +01:00
trashtemp e4159e1c6d hotfix(jumpbar): jump bar fixed on mobile 2021-12-28 23:40:07 +01:00
Paul Makles 17a2ca773b feat: dynamically calculate header translucency and clamp minimum opacity 2021-12-28 21:59:09 +00:00
trashtemp d3b78ebc48 fix(messagebox): fixed padding on action button 2021-12-28 21:42:09 +01:00
trashtemp 03ec5275ee fix(header): padding 2021-12-28 21:04:18 +01:00
trashtemp e8784946ae chore(header): deprecated old header code 2021-12-28 20:43:42 +01:00
trashtemp 37c15eaa3e fix(header): fixed header background 2021-12-28 20:38:57 +01:00
trashtemp e814064001 fix(developer): fixed height issue in dev tab 2021-12-28 17:06:04 +01:00
trashtemp ef0644074c fix(group): fixed height in members list 2021-12-28 16:30:25 +01:00
trashtemp 614ec68622 fix(settings): added design for account header 2021-12-28 16:08:05 +01:00
Paul Makles b5077b62ff fix(invite): allow logged out users to view invites 2021-12-28 12:24:35 +00:00
trashtemp 855b3739e6 fix(homescreen): fixed height 2021-12-27 16:07:13 +01:00
trashtemp bbe1e0d178 fix(header): fixed header in settings 2021-12-27 16:05:33 +01:00
trashtemp 32182b1803 fix(settings): fixing header in mobile settings 2021-12-27 15:58:30 +01:00
trashtemp 356291cc4f fix(header): added mobile support 2021-12-27 15:48:31 +01:00
trashtemp 850d685175 fix(header): header fixes 2021-12-27 15:47:06 +01:00
trashtemp 93925834cc fix(header): header rewrite complete 2021-12-27 15:38:49 +01:00
trashtemp a396e511ec fix(header): changed class names 2021-12-27 15:15:37 +01:00
trashtemp c655a59979 fix(header): server header rewrite 2021-12-27 15:13:36 +01:00
trashtemp 3a92cf06c3 fix(header): fixed default header color 2021-12-27 15:11:37 +01:00
trashtemp 746d017142 fix(header): rewrite of server header 2021-12-27 15:10:02 +01:00
trashtemp 7241498f87 fix(header): added text overflow 2021-12-27 14:44:02 +01:00
trashtemp 2ce4136a52 fix(sidebars): fixed sidebar margins 2021-12-27 14:20:17 +01:00
Paul Makles ce3be17b74 fix(login): make login form an observer of server config 2021-12-26 23:22:15 +00:00
Paul Makles 3999dbbe66 fix: hydrating server config should always be null if not present 2021-12-26 23:12:49 +00:00
Paul Makles 336e2ddeb7 chore: allow state to be visible globally 2021-12-26 23:04:14 +00:00
trashtemp 63e52fe69c fix(friends): respect header padding 2021-12-26 23:57:09 +01:00
trashtemp 64df7edf9b fix(header): new unread bar fixed on mobile 2021-12-26 23:43:07 +01:00
trashtemp 4d59bb96b2 fix(header): fixed voice dialog 2021-12-26 23:38:01 +01:00
trashtemp 2a4d0c3311 fix(header): commented out server banner until fix 2021-12-26 23:35:05 +01:00
trashtemp 83f38d71ce feat(header): new glass header on channel view 2021-12-26 22:26:27 +01:00
Paul Makles f0c2829c58 fix: change invite routing order 2021-12-26 19:56:46 +00:00
trashtemp e20c3ee518 fix(home): fixed grid on home screen 2021-12-26 20:47:34 +01:00
trashtemp 716edb3318 feat(servers): added new icons to server sidebar 2021-12-26 20:28:48 +01:00
Paul 5797eb3686 fix(home / seasonal): correctly read state 2021-12-26 15:04:35 +00:00
Paul bb707fb287 fix(settings): persist false-y values 2021-12-26 15:03:44 +00:00
Paul 301070a3cb fix(reply): mention toggle would not persist 2021-12-26 15:00:50 +00:00
Paul 5737b32271 fix(mobx): use correct key for saving sync options 2021-12-26 14:45:18 +00:00
Paul d73772929d feat(native): overlay titlebar on login srceen 2021-12-25 23:38:21 +00:00
Paul 84c5c3abe9 chore: bump language definitions 2021-12-25 23:18:56 +00:00
Paul Makles e4440f127a feat(login): new login screen design #466 2021-12-25 23:18:37 +00:00
Paul 55930d30e3 chore: pre-merge submodule update 2021-12-25 23:17:59 +00:00
trashtemp ba82ff9f39 feat(login): animated hand wave 2021-12-25 22:48:00 +01:00
Paul 1b644d450d fix: allow invite screen to properly load while signed out 2021-12-25 20:43:54 +00:00
trashtemp d439d63a91 fix(login): remove bold text 2021-12-25 21:29:44 +01:00
trashtemp 029bff3d68 fix(login): added polish 2021-12-25 21:21:29 +01:00
Paul 7f586e6f8c chore: bump revolt.js to fix issue 2021-12-25 20:12:51 +00:00
trashtemp b14ea73663 fix(login): general cleanup 2021-12-25 20:51:11 +01:00
trashtemp 68e83a4e47 fix(login): added accent color links 2021-12-25 19:59:30 +01:00
trashtemp a07a17d892 fix(login): finished new login screen 2021-12-25 19:48:48 +01:00
Paul 896cc13812 fix: verification / password reset broken
force create new client
2021-12-25 16:44:29 +00:00
Paul a23230850a feat(error): add button to reset app data 2021-12-25 16:41:57 +00:00
Paul 064f223c78 fix: catch errors from redux migration 2021-12-25 16:37:39 +00:00
Paul 028a8660c1 feat: handle errors for message renderer separately 2021-12-25 16:03:05 +00:00
Paul c96b09bb81 fix: pre-emptive render without valid user 2021-12-25 15:56:47 +00:00
Paul b7429e47b0 chore: update language definitions 2021-12-25 15:42:59 +00:00
Paul 318177b414 fix: don't fail if channel is null on reply 2021-12-25 15:42:13 +00:00
trashtemp 7844c84fbe fix(login): changed logo back to wideSVG 2021-12-25 16:40:27 +01:00
trashtemp 5c59f187bd feat(login): new login screen design 2021-12-25 16:01:19 +01:00
Paul b955f2820c fix: hide new message bar on jump to bottom 2021-12-25 14:49:31 +00:00
Paul de1b947b7d feat: add global error handling and reporting 2021-12-25 14:32:28 +00:00
Paul 56126fa303 fix: handle invalid channels even when sidebar is closed 2021-12-25 13:20:20 +00:00
Paul d7f52fb6f4 fix(sidebar): force open on mobile devices 2021-12-25 11:00:51 +00:00
Paul 8a0033699d chore(dev): setup chrome user data directory 2021-12-25 10:37:48 +00:00
trashtemp d1e1449f99 fix(channel): mobile jump bar fixed 2021-12-25 11:33:30 +01:00
trashtemp 7e02979560 fix(channel): jump to beginning bar 2021-12-25 11:20:44 +01:00
Paul 56ab2a2917 feat(sidebar): show DMs under home button 2021-12-24 23:53:26 +00:00
trashtemp 3b3ec69edd fix(header): fixed icons cutting off in header 2021-12-25 00:47:42 +01:00
trashtemp 6b11248364 fix(servers): fixed server text font size 2021-12-25 00:01:15 +01:00
trashtemp 245c7a46ed fix(settings): more work on server settings pages 2021-12-24 23:27:35 +01:00
trashtemp 9b4619cbd1 fix(settings): improved server settings tabs 2021-12-24 22:54:46 +01:00
Paul 5169066772 chore: bump revolt.js to fix bug with empty servers 2021-12-24 20:44:24 +00:00
trashtemp 6bed278fe8 chore(settings): added new translation strings 2021-12-24 21:22:01 +01:00
trashtemp 73b6da4e1e fix(channel): added string to new message bar 2021-12-24 19:01:55 +01:00
trashtemp c53f74abbf fix(modals): fixed design of modals 2021-12-24 18:55:39 +01:00
trashtemp 18f7f91ff0 fix(settings): more settings fixes pt. 2 2021-12-24 18:36:40 +01:00
Paul af3eb754f7 feat(messaging): hide new message bar on esc or sent message 2021-12-24 17:20:05 +00:00
Paul b2c24d33ed fix: make the create group button do the correct thing 2021-12-24 17:15:39 +00:00
trashtemp bdc84b1d98 fix(settings): started settings cleanup 2021-12-24 17:39:25 +01:00
Paul 739dd53637 fix: add margin to theme selector 2021-12-24 15:51:11 +00:00
Paul c64906051d fix: hide new messages bar on click 2021-12-24 14:49:21 +00:00
Paul f1a9c889b8 fix(messaging): only show date if date changes 2021-12-24 14:41:33 +00:00
Paul 3b6519c530 fix: use contrasting colour for new message divider 2021-12-24 14:35:37 +00:00
Paul 3d723574f4 feat(messaging): show last read message divider 2021-12-24 14:32:04 +00:00
Paul 46c652d54c chore: update language definitions 2021-12-24 14:15:56 +00:00
Paul fee56d8f54 chore: clean up contrasting colours code 2021-12-24 14:13:10 +00:00
Paul a46fbcf409 feat(sidebar): change swoosh colour depending on sidebar collapse 2021-12-24 13:06:52 +00:00
Paul d8d002cc4a feat(header): add chevron / unified sidebar collapse 2021-12-24 13:02:49 +00:00
Paul e263b627aa fix(settings): display index header only on mobile and show user 2021-12-24 12:45:28 +00:00
Paul 63164fe2d0 feat(theme): add toggle for seasonal theme 2021-12-24 12:41:07 +00:00
Paul 5029d0ac86 fix: avoid writing mobx data to localforage 2021-12-24 12:06:11 +00:00
Paul 189d0b5ff9 fix(mobx): properly persist login state 2021-12-24 12:00:24 +00:00
Paul ef1ddb7771 chore: bump language defintions 2021-12-24 11:46:12 +00:00
Paul e380534d2a Merge branch 'mobx' 2021-12-24 11:45:49 +00:00
Paul c31bcd0200 fix(auth): block render while logging in 2021-12-24 11:32:59 +00:00
Paul 74430b1a8f feat(mobx): migrate legacy data 2021-12-24 11:19:02 +00:00
Paul e89bbb7455 feat(mobx): add sync back (do not look at the code) 2021-12-24 02:05:18 +00:00
Paul cc0e45526c feat(mobx): refactor and remove (react-)redux 2021-12-23 21:43:11 +00:00
Paul 6e1bcab92b feat(mobx): migrate unreads to revolt.js 2021-12-23 19:37:19 +00:00
trashtemp cc76e78db8 fix(settings): started work on settings cleanup 2021-12-23 17:35:58 +00:00
trashtemp d2d7083542 fix(home,settings): updated homepage, settings
* new home screen
* cleaned up settings
2021-12-23 17:29:35 +00:00
Paul 73d99e4518 feat(home): add snow 2021-12-23 13:16:43 +00:00
Paul 136238f62e chore: exclude revolt.js from pre bundling 2021-12-23 12:21:00 +00:00
Paul f14ef2b78f chore: pull languages 2021-12-22 22:11:32 +00:00
Paul 4719150368 feat(renderer): add additional languages to cb
Closes #28
2021-12-22 22:11:25 +00:00
Paul 3d73834bef fix(server settings): unrestrict height 2021-12-22 10:36:23 +00:00
Paul Makles 1aff8ef516 chore(ci): only mirror master and production 2021-12-21 17:50:46 +00:00
Paul Makles b80a6a9c3e chore(ci): remove preview PR workflow (#452) 2021-12-21 17:50:08 +00:00
Paul Makles 1780d06c97 chore(ci): triage PR on synchronize 2021-12-21 17:47:14 +00:00
Paul Makles c29efc155b chore(ci): update CI tasks to move PRs when updated 2021-12-21 17:37:59 +00:00
Paul 71c0a782c1 fix(notifications): use effective name and avatar for notif 2021-12-21 12:38:53 +00:00
Paul f7be9df980 chore(mobx): add legacy redux migations 2021-12-21 12:31:14 +00:00
Paul Makles 68578d2620 feat(mobx): start work on migrations 2021-12-20 12:01:45 +00:00
Paul Makles 89dda8fe82 feat(mobx): migrate trusted links 2021-12-17 10:20:55 +00:00
Paul Makles 120e6a35d8 feat(mobx): migrate audio settings 2021-12-16 22:05:31 +00:00
Paul c7df0088fc feat(mobx): rewrite appearance menu 2021-12-15 18:23:05 +00:00
Paul Makles 65be047dc6 feat(mobx): continue implementing themes; performance work on settings 2021-12-13 17:27:30 +00:00
Paul Makles bd4369cf29 feat(mobx): start implementing theme store 2021-12-13 17:27:06 +00:00
Paul 26a34032f9 feat(mobx): start work on settings store 2021-12-12 23:55:58 +00:00
Paul fef2c5997f chore(mobx): write jsdoc for auth / mqueue 2021-12-12 15:47:15 +00:00
Paul faca4ac32b feat(mobx): add message queue store 2021-12-12 15:33:47 +00:00
Paul ec83230c59 chore(mobx): write jsdoc for notif opt. 2021-12-12 12:26:45 +00:00
Paul 413bf6949b feat(mobx): server notification options + data store 2021-12-11 23:34:46 +00:00
Paul f8b8d96d3d feat(mobx): migrate auth and config 2021-12-11 21:04:12 +00:00
Paul bc799931a8 feat(mobx): add persistence 2021-12-11 16:24:23 +00:00
Paul 2b55770ecc chore(mobx): refactor into interfaces 2021-12-11 14:36:26 +00:00
Paul a8491267a4 feat(mobx): add layout (paths + sections) 2021-12-11 14:34:12 +00:00
Paul f87ecfcbd7 feat(mobx): add experiments store 2021-12-11 13:23:01 +00:00
Paul 830b24a393 chore(mobx): clean up documentation 2021-12-11 12:08:43 +00:00
Paul 49f45aa5aa chore(mobx): remove extra util class 2021-12-11 11:59:26 +00:00
Paul b36cde771e feat(mobx): expose application state to window 2021-12-11 11:58:07 +00:00
Paul 87a9841885 feat(mobx): implement locale options 2021-12-11 11:56:33 +00:00
Paul Makles 89748d7044 feat(mobx): start working on notif options, create blank files 2021-12-10 17:00:34 +00:00
Paul Makles 185f76d850 feat(mobx): write experiments, lastOpened and localeOptions stores 2021-12-10 13:55:05 +00:00
Paul Makles 5a41c25e3c feat(mobx): add drafts and state context 2021-12-10 12:53:41 +00:00
Paul Makles 66bfc658c3 chore: notes 2021-12-08 20:42:20 +00:00
402 changed files with 29158 additions and 17227 deletions
+1
View File
@@ -0,0 +1 @@
{ "plugins": [["@babel/plugin-proposal-decorators", { "legacy": true }]] }
+3
View File
@@ -5,3 +5,6 @@ dist_injected
node_modules
.env
.env.local
Dockerfile
.dockerignore
-2
View File
@@ -1,2 +0,0 @@
ko_fi: insertish
custom: https://insrt.uk/donate
+66
View File
@@ -0,0 +1,66 @@
name: Bug report
description: File a bug report
title: "bug: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: what-happened
attributes:
label: What happened?
description: What did you expect to happen?
validations:
required: true
- type: dropdown
id: branch
attributes:
label: Branch
description: What branch of Revolt are you using?
options:
- Production (app.revolt.chat)
- Nightly (nightly.revolt.chat)
validations:
required: true
- type: textarea
id: commit-hash
attributes:
label: Commit hash
description: What is your commit hash? You can find this at the bottom of Settings, next to the branch name.
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: What browsers are you seeing the problem on?
multiple: true
options:
- Firefox
- Chrome
- Safari
- Microsoft Edge
- Other (please specify in the "What happened" form)
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. (To get this, press `CTRL`- or `CMD`-`SHIFT`-`I` and navigate to the "Console" tab.)
render: shell
- type: checkboxes
id: desktop
attributes:
label: Desktop
description: Is this bug specific to [the desktop client](https://github.com/revoltchat/desktop)? (If not, leave this unchecked.)
options:
- label: Yes, this bug is specific to Revolt Desktop and is *not* an issue with Revolt Desktop itself.
required: false
- type: checkboxes
id: pwa
attributes:
label: PWA
description: Is this bug specific to the PWA (i.e. "installing" the web app on iOS or Android)? (If not, leave this unchecked.)
options:
- label: Yes, this bug is specific to the PWA.
required: false
+7
View File
@@ -0,0 +1,7 @@
contact_links:
- name: Lounge Chat
url: https://rvlt.gg/Testers
about: Ask questions and discuss with others.
- name: Discussions
url: https://github.com/orgs/revoltchat/discussions
about: For larger feature requests and general question & answer.
+24
View File
@@ -0,0 +1,24 @@
name: Feature request
description: Make a feature request
title: "feature request: "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Before you start, a lot of bigger features may be better suited as [API issues](https://github.com/revoltchat/delta/issues/new) or [centralised discussions](https://github.com/revoltchat/revolt/discussions/new).
- type: textarea
id: your-idea
attributes:
label: What do you want to see?
description: Describe your idea in as much detail as possible - if applicable, screenshots/mockups are really useful.
validations:
required: true
- type: checkboxes
id: pwa
attributes:
label: PWA
description: Is this feature request specific to the PWA (i.e. "installing" the web app on iOS or Android)? (If not, leave this unchecked.)
options:
- label: Yes, this feature request is specific to the PWA.
required: false
-24
View File
@@ -1,24 +0,0 @@
# Security
## Reporting a Vulnerability
If you would like to report a security vulnerability,
please email **[security@revolt.chat](mailto:security@revolt.chat)**,
this will open a new ticket in ticket system, you should receive a response
within the next couple of days, potentially within a few minutes if someone
is currently active.
To help us best triage the issue, please provide:
- The type of issue at hand
- The name of the relevant project affected
- Reproduction steps
- Reference to any relevant source file(s) that you may suspect are causing the issue
- Any extra information about your configuration.
- Description of potential ways this can be exploited, if you can list any
For revoltchat/revite in particular:
- Please include the commit hash of the client, it is visible in settings under the log out button.
Thank you for helping Revolt.
+1 -1
View File
@@ -19,7 +19,7 @@ runs:
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 15
node-version: 16
cache: "yarn"
- name: Install Dependencies and Build
+7
View File
@@ -0,0 +1,7 @@
## Please make sure to check the following tasks before opening and submitting a PR
* [ ] I understand and have followed the [contribution guide](https://github.com/revoltchat/revolt/discussions/282)
* [ ] I have tested my changes locally and they are working as intended
* [ ] These changes do not have any notable side effects on other Revolt projects
* [ ] (optional) I have opened a pull request on [the translation repository](https://github.com/revoltchat/translations)
* [ ] I have included screenshots to demonstrate my changes
+2 -2
View File
@@ -5,7 +5,7 @@ on:
branches:
- "master"
tags:
- "v*"
- "*"
paths-ignore:
- ".github/**"
- "!.github/workflows/docker.yml"
@@ -100,7 +100,7 @@ jobs:
with:
context: .
push: true
platforms: linux/amd64,linux/arm64,linux/arm/v7
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=local,src=/tmp/.buildx-cache/linux/amd64
+4 -1
View File
@@ -1,6 +1,9 @@
name: Mirroring
on: [push, delete]
on:
push:
branches:
- "master"
jobs:
to_gitlab:
-39
View File
@@ -1,39 +0,0 @@
name: Clean Preview
#! Safety:
#! this workflow should not execute any untrusted input at all
#! see the docs on `pull_request_target` for more
on:
pull_request_target:
types: [unlabeled]
jobs:
clean:
runs-on: ubuntu-latest
if: github.event.label.name == 'use-preview'
env:
BASE: refs/pull/${{ github.event.pull_request.number }}
steps:
- uses: actions/checkout@v2
with:
ref: build-previews
persist-credentials: false
- name: clean previews
run: rm -rf "$BASE"
- name: publish cleaned previews
uses: JamesIves/github-pages-deploy-action@4.1.5
with:
folder: .
branch: build-previews
commit-message: "Cleaning up build result for #${{ github.event.pull_request.number }}"
- name: send comment
uses: marocchino/sticky-pull-request-comment@v2
with:
header: Preview environment
message: |
## Preview environment
There is no longer a preview enviroment for this pull request due to the `use-preview` label being removed
@@ -1,52 +0,0 @@
name: Preview Pull Request
#! Safety:
#! this workflow should not execute any untrusted input at all
#! see the docs on `pull_request_target` for more
on:
pull_request_target:
types: [synchronize, reopened, labeled]
jobs:
preview:
runs-on: ubuntu-latest
# make sure the pull request is labeled with 'use-preview'
if: github.event.label.name == 'use-preview' || contains(github.event.pull_request.labels.*.name, 'use-preview')
env:
BASE: refs/pull/${{ github.event.pull_request.number }}/merge
REPO: ${{ github.event.repository.name }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v2
with:
# Head commit of the pull request
ref: ${{ github.event.pull_request.head.sha }}
path: pull
submodules: recursive
- name: build
uses: ./.github/actions/build
with:
base: /${{ env.REPO }}/${{ env.BASE }}/
folder: pull
- name: publish preview
uses: JamesIves/github-pages-deploy-action@4.1.5
with:
folder: pull/dist
branch: build-previews
target-folder: ${{ env.BASE }}
single-commit: true
commit-message: "Publishing build result from #${{ github.event.pull_request.number }}"
- name: send comment
uses: marocchino/sticky-pull-request-comment@v2
with:
header: Preview environment
message: |
## Preview environment
https://${{ github.repository_owner }}.github.io/${{ env.REPO }}/${{ env.BASE }}/
This link will remain active until the `use-preview` label is removed.
+16 -11
View File
@@ -15,22 +15,27 @@ jobs:
gh api graphql -f query='
query {
organization(login: "revoltchat"){
projectNext(number: 3) {
projectV2(number: 3) {
id
fields(first:20) {
nodes {
id
name
settings
... on ProjectV2SingleSelectField {
id
name
options {
id
name
}
}
}
}
}
}
}' > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'TODO_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV
echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'TODO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV
- name: Add issue to project
env:
@@ -39,11 +44,11 @@ jobs:
run: |
item_id="$( gh api graphql -f query='
mutation($project:ID!, $issue:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $issue}) {
projectNextItem {
addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) {
item {
id
}
}
}' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
}' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectV2ItemById.item.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
+23 -16
View File
@@ -2,7 +2,7 @@ name: Add PR to Board
on:
pull_request_target:
types: [opened]
types: [opened, synchronize, ready_for_review, review_requested]
jobs:
track_pr:
@@ -15,22 +15,27 @@ jobs:
gh api graphql -f query='
query {
organization(login: "revoltchat"){
projectNext(number: 3) {
projectV2(number: 3) {
id
fields(first:20) {
nodes {
id
name
settings
... on ProjectV2SingleSelectField {
id
name
options {
id
name
}
}
}
}
}
}
}' > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'INCOMING_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Incoming PRs") |.id' project_data.json) >> $GITHUB_ENV
echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'INCOMING_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="Incoming PRs") |.id' project_data.json) >> $GITHUB_ENV
- name: Add PR to project
env:
@@ -39,13 +44,13 @@ jobs:
run: |
item_id="$( gh api graphql -f query='
mutation($project:ID!, $pr:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $pr}) {
projectNextItem {
addProjectV2ItemById(input: {projectId: $project, contentId: $pr}) {
item {
id
}
}
}' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
}' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectV2ItemById.item.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
- name: Set fields
@@ -59,14 +64,16 @@ jobs:
$status_field: ID!
$status_value: String!
) {
set_status: updateProjectNextItemField(input: {
set_status: updateProjectV2ItemFieldValue(input: {
projectId: $project
itemId: $item
fieldId: $status_field
value: $status_value
value: {
singleSelectOptionId: $status_value
}
}) {
projectNextItem {
projectV2Item {
id
}
}
}
}' -f project=$PROJECT_ID -f item=$ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.INCOMING_OPTION_ID }} --silent
+5
View File
@@ -7,6 +7,11 @@ dist-ssr
*.log
/.idea
.yarn/cache
.yarn/install-state.gz
public/assets
public/assets_*
!public/assets_default
.vscode/chrome_data
-40
View File
@@ -1,40 +0,0 @@
image: node:14-buster
variables:
GIT_SUBMODULE_STRATEGY: recursive
cache:
paths:
- node_modules
# Fetch dependencies and setup project for compilation.
install:
stage: prepare
script:
- yarn
# Type check the project
typecheck:
stage: test
needs:
- install
dependencies:
- install
script:
- yarn typecheck
# Lint the project and check prettier output.
lint:
stage: test
allow_failure: true
needs:
- install
dependencies:
- install
script:
- yarn lint
- yarn --check 'src/**/*.{js,jsx,ts,tsx}'
stages:
- prepare
- test
+6
View File
@@ -1,3 +1,9 @@
[submodule "external/lang"]
path = external/lang
url = https://github.com/revoltchat/translations
[submodule "external/components"]
path = external/components
url = https://github.com/revoltchat/components
[submodule "external/revolt.js"]
path = external/revolt.js
url = https://github.com/revoltchat/revolt.js
+1
View File
@@ -0,0 +1 @@
src/components/markdown/prism.ts
+3 -1
View File
@@ -4,10 +4,12 @@ module.exports = {
jsxBracketSameLine: true,
importOrder: [
"preact|classnames|.scss$",
"^@revoltchat",
"/(lib)",
"/(redux|mobx)",
"/(context)",
"/(ui|common)|.svg|.webp|.png|.jpg$",
"/(ui|common)$",
".svg|.webp|.png|.jpg$",
"^[./]",
],
importOrderSeparation: true,
+17
View File
@@ -0,0 +1,17 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://local.revolt.chat:3000",
"webRoot": "${workspaceFolder}",
"runtimeExecutable": "/usr/bin/chromium",
"userDataDir": "${workspaceFolder}/.vscode/chrome_data"
}
]
}
+1 -2
View File
@@ -1,5 +1,4 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"compile-hero.disable-compile-files-on-did-save-code": true
"editor.formatOnSave": true
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+785
View File
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"
yarnPath: .yarn/releases/yarn-3.2.0.cjs
+7 -9
View File
@@ -1,18 +1,16 @@
FROM node:16-buster AS builder
WORKDIR /usr/src/app
COPY package*.json ./
RUN yarn --no-cache
COPY . .
COPY .env.build .env
RUN yarn add --dev @babel/plugin-proposal-decorators
RUN yarn typecheck
RUN yarn build
RUN npm prune --production
FROM node:16-buster
RUN yarn install --frozen-lockfile
RUN yarn build:deps
RUN yarn typecheck
RUN yarn build:highmem
RUN yarn workspaces focus --production --all
FROM node:16-alpine
WORKDIR /usr/src/app
COPY --from=builder /usr/src/app .
+24 -11
View File
@@ -4,6 +4,14 @@
This is the web client for Revolt, which is also available live at [app.revolt.chat](https://app.revolt.chat).
## Pending Rewrite
The following code is pending a partial or full rewrite:
- `src/components`: components are being migrated to [revoltchat/components](https://github.com/revoltchat/components)
- `src/styles`: needs to be migrated to [revoltchat/components](https://github.com/revoltchat/components)
- `src/lib`: this needs to be organised
## Stack
- [Preact](https://preactjs.com/)
@@ -35,22 +43,27 @@ Get revite up and running locally.
git clone --recursive https://github.com/revoltchat/revite
cd revite
yarn
yarn build:deps
yarn dev
```
You can now access the client at http://local.revolt.chat:3000.
## CLI Commands
| Command | Description |
| ------------------- | -------------------------------------------- |
| `yarn pull` | Setup assets required for Revite. |
| `yarn dev` | Start the Revolt client in development mode. |
| `yarn build` | Build the Revolt client. |
| `yarn preview` | Start a local server with the built client. |
| `yarn lint` | Run ESLint on the client. |
| `yarn fmt` | Run Prettier on the client. |
| `yarn typecheck` | Run TypeScript type checking on the client. |
| `yarn start` | Start a local sirv server with built client. |
| `yarn start:inject` | Inject a given API URL and start server. |
| Command | Description |
| --------------------------------------- | -------------------------------------------- |
| `yarn pull` | Setup assets required for Revite. |
| `yarn dev` | Start the Revolt client in development mode. |
| `yarn build` | Build the Revolt client. |
| `yarn build:deps` | Build external dependencies. |
| `yarn preview` | Start a local server with the built client. |
| `yarn lint` | Run ESLint on the client. |
| `yarn fmt` | Run Prettier on the client. |
| `yarn typecheck` | Run TypeScript type checking on the client. |
| `yarn start` | Start a local sirv server with built client. |
| `yarn start:inject` | Inject a given API URL and start server. |
| `yarn lint \| egrep "no-literals" -B 1` | Scan for untranslated strings. |
## License
-1
View File
@@ -1 +0,0 @@
0.5.3-1
+6
View File
@@ -0,0 +1,6 @@
<svg width="225" height="161" viewBox="0 0 225 161" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M69.6951 15.0002L20.9951 63.7002L69.6951 112.4L81.7601 100.325L45.1301 63.6952L81.7601 27.0652L69.6951 15.0002ZM132.955 112.39L181.655 63.6902L132.955 14.9902L120.89 27.0652L157.52 63.6952L120.89 100.325L132.955 112.39Z" fill="white"/>
<path d="M197 73H137C133.686 73 131 75.6863 131 79V139C131 142.314 133.686 145 137 145H197C200.314 145 203 142.314 203 139V79C203 75.6863 200.314 73 197 73Z" fill="#242424"/>
<path d="M191 79H143C139.686 79 137 81.6863 137 85V133C137 136.314 139.686 139 143 139H191C194.314 139 197 136.314 197 133V85C197 81.6863 194.314 79 191 79Z" fill="#D14F4F"/>
<path d="M181.5 95.5L153.5 123.5M153.5 95.5L181.5 123.5L153.5 95.5Z" stroke="white" stroke-width="5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 828 B

Vendored Submodule
+1
Submodule external/components added at e79862b597
+1 -1
Vendored Submodule
+1
Submodule external/revolt.js added at 29df60f782
+68 -7
View File
@@ -1,10 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" background="#191919">
<head>
<meta charset="UTF-8" />
<!--App Title-->
<title>Revolt</title>
<meta name="apple-mobile-web-app-title" content="Revolt" />
<!--App Scaling-->
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no"
@@ -69,14 +72,72 @@
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2)"
rel="apple-touch-startup-image"
/>
<!--CSS for noscript screen-->
<style>
noscript {
background: #242424;
color: white;
position: fixed;
top: 0;
left: 0;
width: 100vw;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
}
noscript > div {
padding: 12px;
display: flex;
font-family: "Open Sans", sans-serif;
flex-direction: column;
justify-content: center;
text-align: center;
}
noscript > div > h1 {
margin: 8px 0;
text-transform: uppercase;
font-size: 20px;
font-weight: 700;
}
noscript > div > p {
margin: 4px 0;
font-size: 14px;
}
noscript > div > a {
align-self: center;
margin-top: 20px;
padding: 8px 10px;
font-size: 14px;
width: 80px;
font-weight: 600;
background: #ed5151;
border-radius: 4px;
text-decoration: none;
color: white;
transition: background-color 0.2s;
}
noscript > div > a:hover {
background-color: #cf4848;
}
noscript > div > a:active {
background-color: #b64141;
}
</style>
</head>
<body onContextMenu="return false" ontouchstart="">
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
<noscript>
<div>
<img src="disabled-js.svg" />
<h1>Well, this is really awkward...</h1>
<p>Seems like your JavaScript is disabled.</p>
<p>You'll need to enable JavaScript to run this app.</p>
<a href="https://app.revolt.chat" target="_blank">Reload</a>
</div>
</noscript>
</body>
<style>
html {
background-color: #191919;
}
</style>
</html>
+68 -37
View File
@@ -1,11 +1,13 @@
{
"version": "0.0.0",
"version": "1.0.1",
"scripts": {
"dev": "node scripts/setup_assets.js --check && vite",
"pull": "node scripts/setup_assets.js",
"build": "rimraf build && node scripts/setup_assets.js --check && vite build",
"build:deps": "cd external && cd components && yarn && yarn build:esm && cd .. && cd revolt.js && yarn && yarn build",
"build": "yarn && rimraf build && node scripts/setup_assets.js --check && yarn build:deps && vite build",
"build:highmem": "NODE_OPTIONS='--max-old-space-size=4096' yarn build",
"preview": "vite preview",
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
"lint": "eslint src/**/*.{js,jsx,ts,tsx}",
"fmt": "prettier --write 'src/**/*.{js,jsx,ts,tsx}'",
"typecheck": "tsc --noEmit",
"start": "sirv dist --cors --single --host",
@@ -37,27 +39,32 @@
{
"varsIgnorePattern": "^_"
}
]
],
"react/jsx-no-literals": "warn"
}
},
"dependencies": {
"fs-extra": "^10.0.0",
"klaw": "^3.0.0",
"react-beautiful-dnd": "^13.1.0",
"sirv-cli": "^1.0.14",
"vite": "^2.6.14"
"vite": "^3.0.5"
},
"devDependencies": {
"@babel/plugin-proposal-decorators": "^7.17.9",
"@floating-ui/react-dom": "^1.0.0",
"@floating-ui/react-dom-interactions": "^0.9.1",
"@fontsource/atkinson-hyperlegible": "^4.4.5",
"@fontsource/bree-serif": "^4.4.5",
"@fontsource/bitter": "^4.5.7",
"@fontsource/comic-neue": "^4.4.5",
"@fontsource/fira-code": "^4.4.5",
"@fontsource/inter": "^4.4.5",
"@fontsource/jetbrains-mono": "^4.4.5",
"@fontsource/lato": "^4.4.5",
"@fontsource/lexend": "^4.5.2",
"@fontsource/montserrat": "^4.4.5",
"@fontsource/noto-sans": "^4.4.5",
"@fontsource/open-sans": "^4.4.5",
"@fontsource/open-sans": "^4.5.2",
"@fontsource/opendyslexic": "^4.5.2",
"@fontsource/poppins": "^4.4.5",
"@fontsource/raleway": "^4.4.5",
"@fontsource/roboto": "^4.4.5",
@@ -66,79 +73,103 @@
"@fontsource/space-mono": "^4.4.5",
"@fontsource/ubuntu": "^4.4.5",
"@fontsource/ubuntu-mono": "^4.4.5",
"@hcaptcha/react-hcaptcha": "^0.3.6",
"@hcaptcha/react-hcaptcha": "^1.4.4",
"@insertish/vite-plugin-babel-macros": "^1.0.5",
"@preact/preset-vite": "^2.0.0",
"@revoltchat/ui": "^1.0.77",
"@rollup/plugin-replace": "^2.4.2",
"@styled-icons/boxicons-logos": "^10.34.0",
"@styled-icons/boxicons-regular": "^10.34.0",
"@styled-icons/boxicons-solid": "^10.37.0",
"@styled-icons/simple-icons": "^10.33.0",
"@tippyjs/react": "^4.2.5",
"@styled-icons/boxicons-logos": "^10.38.0",
"@styled-icons/boxicons-regular": "^10.38.0",
"@styled-icons/boxicons-solid": "^10.38.0",
"@styled-icons/simple-icons": "^10.45.0",
"@tippyjs/react": "4.2.6",
"@traptitech/markdown-it-katex": "^3.4.3",
"@traptitech/markdown-it-spoiler": "^1.1.6",
"@trivago/prettier-plugin-sort-imports": "^2.0.2",
"@types/lodash": "^4",
"@types/lodash.defaultsdeep": "^4.6.6",
"@types/lodash.isequal": "^4.5.5",
"@types/markdown-it": "^12.0.2",
"@types/node": "^15.12.4",
"@types/node": "^15.14.9",
"@types/preact-i18n": "^2.3.0",
"@types/prismjs": "^1.16.5",
"@types/react-beautiful-dnd": "^13.1.2",
"@types/prismjs": "^1.26.0",
"@types/react-beautiful-dnd": "^13",
"@types/react-helmet": "^6.1.1",
"@types/react-router-dom": "^5.1.7",
"@types/react-scroll": "^1.8.2",
"@types/react-virtualized-auto-sizer": "^1.0.1",
"@types/semver": "^7",
"@types/styled-components": "^5.1.10",
"@types/twemoji": "^12.1.1",
"@typescript-eslint/eslint-plugin": "^4.27.0",
"@typescript-eslint/parser": "^4.27.0",
"@vitejs/plugin-legacy": "^1.7.1",
"classnames": "^2.3.1",
"color-rgba": "^2.4.0",
"dayjs": "^1.10.6",
"detect-browser": "^5.2.0",
"eslint": "^7.28.0",
"eslint-config-preact": "^1.1.4",
"eslint-plugin-jsdoc": "^39.3.2",
"eslint-plugin-mobx": "^0.0.8",
"eventemitter3": "^4.0.7",
"highlight.js": "^11.0.1",
"history": "4",
"json-stringify-deterministic": "^1.0.2",
"localforage": "^1.9.0",
"lodash": "^4.17.21",
"lodash.defaultsdeep": "^4.6.1",
"lodash.isequal": "^4.5.0",
"markdown-it": "^12.0.6",
"markdown-it-emoji": "^2.0.0",
"markdown-it-sub": "^1.0.0",
"markdown-it-sup": "^1.0.0",
"long": "^5.2.0",
"mdast-util-to-hast": "^12.1.2",
"mediasoup-client": "npm:@insertish/mediasoup-client@3.6.36-esnext",
"mobx": "^6.3.2",
"mobx-react-lite": "^3.2.0",
"mobx": "^6.6.0",
"mobx-react-lite": "3.4.0",
"preact": "^10.5.14",
"preact-context-menu": "^0.2.1",
"preact-context-menu": "0.4.1",
"preact-i18n": "^2.4.0-preactx",
"prettier": "^2.3.1",
"prismjs": "^1.23.0",
"react-device-detect": "^1.17.0",
"prismjs": "^1.28.0",
"qrcode.react": "^3.0.2",
"react-beautiful-dnd": "^13.1.0",
"react-device-detect": "2.2.2",
"react-helmet": "^6.1.0",
"react-hook-form": "6.3.0",
"react-overlapping-panels": "1.2.2",
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0",
"react-scroll": "^1.8.2",
"react-virtualized-auto-sizer": "^1.0.5",
"react-virtuoso": "^1.10.4",
"redux": "^4.1.0",
"revolt-api": "0.5.3-alpha.10",
"revolt.js": "^5.1.0-alpha.10",
"react-virtuoso": "^2.12.0",
"rehype-katex": "^6.0.2",
"rehype-prism": "^2.1.3",
"rehype-react": "^7.1.1",
"remark-breaks": "^3.0.2",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"remark-parse": "^10.0.1",
"remark-rehype": "^10.1.0",
"revolt.js": "6.0.17",
"rimraf": "^3.0.2",
"sass": "^1.35.1",
"semver": "^7.3.7",
"shade-blend-color": "^1.0.0",
"slate": "^0.81.1",
"slate-history": "^0.66.0",
"slate-react": "^0.81.0",
"stacktrace-js": "^2.0.2",
"styled-components": "^5.3.0",
"typescript": "^4.4.2",
"ulid": "^2.3.0",
"unified": "^10.1.2",
"unist-util-visit": "^4.1.0",
"use-resize-observer": "^7.0.0",
"vite-plugin-pwa": "^0.8.1",
"vite-plugin-pwa": "^0.12.3",
"workbox-precaching": "^6.1.5"
},
"name": "client",
"main": "index.js",
"repository": "https://github.com/revoltchat/revite.git",
"author": "Paul <paulmakles@gmail.com>",
"license": "MIT"
"license": "MIT",
"packageManager": "yarn@3.2.0",
"resolutions": {
"@revoltchat/ui": "portal:external/components",
"revolt.js": "portal:external/revolt.js"
}
}
+21 -9
View File
@@ -1,10 +1,22 @@
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "chat.revolt.app.twa",
"sha256_cert_fingerprints": [
"6E:62:C1:BF:5A:2D:11:31:A3:22:91:8D:22:2B:2C:49:D3:70:F3:A1:45:DF:11:6A:97:DC:4C:A9:3B:C3:AA:FB"
]
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "chat.revolt.app.twa",
"sha256_cert_fingerprints": [
"6E:62:C1:BF:5A:2D:11:31:A3:22:91:8D:22:2B:2C:49:D3:70:F3:A1:45:DF:11:6A:97:DC:4C:A9:3B:C3:AA:FB"
]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "chat.revolt.app.twa",
"sha256_cert_fingerprints": [
"2B:C7:89:87:BD:62:88:38:7B:C0:D7:5F:D1:10:F4:91:D5:24:A6:B3:25:3A:75:C2:3A:91:07:1B:63:C0:98:67"
]
}
}
}]
]
+1
View File
@@ -0,0 +1 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="64.00001046823172" xmlns="http://www.w3.org/2000/svg" id="screenshot" version="1.1" viewBox="-0.000008942940667111543 -0.0000033745862566547657 64.00001046823172 64.00000545874563" height="64.00000545874563" style="-webkit-print-color-adjust: exact;"><g id="shape-d9b11490-3403-11ec-bc16-7b519797d558"><rect rx="0" ry="0" x="0" y="0" transform="matrix(1.0000000000000007,-8.726646259971662e-8,-1.5707963280665485e-7,1.0000000000000124,0.000005026548230091521,0.0000027925264056705146)" width="64" height="64" style="fill: rgb(255, 255, 255); fill-opacity: 1;"/></g></svg>

After

Width:  |  Height:  |  Size: 626 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="64.00001046823172" xmlns="http://www.w3.org/2000/svg" id="screenshot" version="1.1" viewBox="-0.000008942940667111543 -0.0000033745862566547657 64.00001046823172 64.00000545874563" height="64.00000545874563" style="-webkit-print-color-adjust: exact;"><g id="shape-d9b11490-3403-11ec-bc16-7b519797d558"><rect rx="0" ry="0" x="0" y="0" transform="matrix(1.0000000000000007,-8.726646259971662e-8,-1.5707963280665485e-7,1.0000000000000124,0.000005026548230091521,0.0000027925264056705146)" width="64" height="64" style="fill: rgb(255, 255, 255); fill-opacity: 1;"/></g></svg>

After

Width:  |  Height:  |  Size: 626 B

@@ -0,0 +1 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="64.00001046823172" xmlns="http://www.w3.org/2000/svg" id="screenshot" version="1.1" viewBox="-0.000008942940667111543 -0.0000033745862566547657 64.00001046823172 64.00000545874563" height="64.00000545874563" style="-webkit-print-color-adjust: exact;"><g id="shape-d9b11490-3403-11ec-bc16-7b519797d558"><rect rx="0" ry="0" x="0" y="0" transform="matrix(1.0000000000000007,-8.726646259971662e-8,-1.5707963280665485e-7,1.0000000000000124,0.000005026548230091521,0.0000027925264056705146)" width="64" height="64" style="fill: rgb(255, 255, 255); fill-opacity: 1;"/></g></svg>

After

Width:  |  Height:  |  Size: 626 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="64.00001046823172" xmlns="http://www.w3.org/2000/svg" id="screenshot" version="1.1" viewBox="-0.000008942940667111543 -0.0000033745862566547657 64.00001046823172 64.00000545874563" height="64.00000545874563" style="-webkit-print-color-adjust: exact;"><g id="shape-d9b11490-3403-11ec-bc16-7b519797d558"><rect rx="0" ry="0" x="0" y="0" transform="matrix(1.0000000000000007,-8.726646259971662e-8,-1.5707963280665485e-7,1.0000000000000124,0.000005026548230091521,0.0000027925264056705146)" width="64" height="64" style="fill: rgb(255, 255, 255); fill-opacity: 1;"/></g></svg>

After

Width:  |  Height:  |  Size: 626 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="64.00001046823172" xmlns="http://www.w3.org/2000/svg" id="screenshot" version="1.1" viewBox="-0.000008942940667111543 -0.0000033745862566547657 64.00001046823172 64.00000545874563" height="64.00000545874563" style="-webkit-print-color-adjust: exact;"><g id="shape-d9b11490-3403-11ec-bc16-7b519797d558"><rect rx="0" ry="0" x="0" y="0" transform="matrix(1.0000000000000007,-8.726646259971662e-8,-1.5707963280665485e-7,1.0000000000000124,0.000005026548230091521,0.0000027925264056705146)" width="64" height="64" style="fill: rgb(255, 255, 255); fill-opacity: 1;"/></g></svg>

After

Width:  |  Height:  |  Size: 626 B

@@ -0,0 +1 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="64.00001046823172" xmlns="http://www.w3.org/2000/svg" id="screenshot" version="1.1" viewBox="-0.000008942940667111543 -0.0000033745862566547657 64.00001046823172 64.00000545874563" height="64.00000545874563" style="-webkit-print-color-adjust: exact;"><g id="shape-d9b11490-3403-11ec-bc16-7b519797d558"><rect rx="0" ry="0" x="0" y="0" transform="matrix(1.0000000000000007,-8.726646259971662e-8,-1.5707963280665485e-7,1.0000000000000124,0.000005026548230091521,0.0000027925264056705146)" width="64" height="64" style="fill: rgb(255, 255, 255); fill-opacity: 1;"/></g></svg>

After

Width:  |  Height:  |  Size: 626 B

+22
View File
@@ -0,0 +1,22 @@
const { readdirSync } = require("fs");
console.log(
"var locale_keys = " +
JSON.stringify([
...readdirSync("node_modules/dayjs/locale")
.filter((x) => x.endsWith(".js"))
.map((x) => {
v = x.split(".");
v.pop();
return v.join(".");
}),
...readdirSync("external/lang")
.filter((x) => x.endsWith(".json"))
.map((x) => {
v = x.split(".");
v.pop();
return v.join(".");
}),
]) +
";",
);
+33 -5
View File
@@ -1,7 +1,35 @@
#!/bin/bash
version=$(cat VERSION)
# Build and publish release to production server
# Remote Server
if [ -z "$REMOTE" ]; then
echo "Please set REMOTE!"
exit
fi
# Remote Directory
REMOTE_DIR=/root/revite
# Post-install script
POST_INSTALL="pm2 restart revite"
# Assets
export REVOLT_SAAS=https://github.com/revoltchat/assets
# Exit when any command fails
set -e
# 1. Build Revite
yarn build:highmem
# 2. Archive built files
tar -czvf build.tar.gz dist
# 3. Upload built files
scp build.tar.gz $REMOTE:$REMOTE_DIR/build.tar.gz
rm build.tar.gz
# 4. Apply changes
ssh $REMOTE "cd $REMOTE_DIR; tar -xvzf build.tar.gz; rm build.tar.gz; $POST_INSTALL"
docker build -t revoltchat/client:${version} . &&
docker tag revoltchat/client:${version} revoltchat/client:latest &&
docker push revoltchat/client:${version} &&
docker push revoltchat/client:latest
+2 -2
View File
@@ -3,8 +3,8 @@ const { copy, remove, access } = require("fs-extra");
const { exec: cexec } = require("child_process");
const { resolve } = require("path");
let target = process.env.REVOLT_SASS;
let branch = process.env.REVOLT_SASS_BRANCH;
let target = process.env.REVOLT_SAAS;
let branch = process.env.REVOLT_SAAS_BRANCH;
let DEFAULT_DIRECTORY = "public/assets_default";
let OUT_DIRECTORY = "public/assets";
+52
View File
@@ -0,0 +1,52 @@
type Element =
| string
| {
type: "image";
src: string;
shadow?: boolean;
};
export interface ChangelogPost {
date: Date;
title: string;
content: Element[];
}
export const changelogEntries: Record<number, ChangelogPost> = {
1: {
date: new Date("2022-06-12T20:39:16.674Z"),
title: "Secure your account with 2FA",
content: [
"Two-factor authentication is now available to all users, you can now head over to settings to enable recovery codes and an authenticator app.",
{
type: "image",
src: "https://autumn.revolt.chat/attachments/E21kwmuJGcASgkVLiSIW0wV3ggcaOWjW0TQF7cdFNY/image.png",
},
"Once enabled, you will be prompted on login.",
{
type: "image",
src: "https://autumn.revolt.chat/attachments/LWRYoKR2tE1ggW_Lzm547P1pnrkNgmBaoCAfWvHE74/image.png",
},
"Other authentication methods coming later, stay tuned!",
],
},
2: {
date: new Date("2023-02-23T20:00:00.000Z"),
title: "In-App Reporting Is Here",
content: [
"You can now report any user, server, or message directly from the app.",
{
type: "image",
src: "https://autumn.revolt.chat/attachments/ZuDVIjGiCl61Pk9XGk5qfc8-idN9EnFAk55DUQp713/the.png",
shadow: true,
},
"If you want to learn more about how we're making Revolt safer for you, check out our new blog post :point_right: [https://revolt.chat/posts/improving-user-safety](https://revolt.chat/posts/improving-user-safety)",
],
},
};
export const changelogEntryArray = Object.keys(changelogEntries).map(
(index) => changelogEntries[index as unknown as number],
);
export const latestChangelog = changelogEntryArray.length;
+107 -1
View File
@@ -151,7 +151,6 @@ export const emojiDictionary = {
hole: "🕳️",
bomb: "💣",
speech_balloon: "💬",
eye_speech_bubble: "👁️‍🗨️",
left_speech_bubble: "🗨️",
right_anger_bubble: "🗯️",
thought_balloon: "💭",
@@ -673,6 +672,7 @@ export const emojiDictionary = {
mandarin: "🍊",
lemon: "🍋",
banana: "🍌",
nanner: "🍌",
pineapple: "🍍",
mango: "🥭",
apple: "🍎",
@@ -876,6 +876,7 @@ export const emojiDictionary = {
train: "🚋",
bus: "🚌",
oncoming_bus: "🚍",
trolley: "🚎",
trolleybus: "🚎",
minibus: "🚐",
ambulance: "🚑",
@@ -1847,4 +1848,109 @@ export const emojiDictionary = {
england: "🏴󠁧󠁢󠁥󠁮󠁧󠁿",
scotland: "🏴󠁧󠁢󠁳󠁣󠁴󠁿",
wales: "🏴󠁧󠁢󠁷󠁬󠁳󠁿",
...{
1984: "custom:1984.gif",
KekW: "custom:KekW.png",
amogus: "custom:amogus.gif",
awaa: "custom:awaa.png",
boohoo: "custom:boohoo.png",
boohoo_goes_hard: "custom:boohoo_goes_hard.png",
boohoo_shaken: "custom:boohoo_shaken.png",
cat_arrival: "custom:cat_arrival.gif",
cat_awson: "custom:cat_awson.png",
cat_blob: "custom:cat_blob.png",
cat_bonk: "custom:cat_bonk.png",
cat_concern: "custom:cat_concern.png",
cat_fast: "custom:cat_fast.gif",
cat_kitty: "custom:cat_kitty.png",
cat_lick: "custom:cat_lick.gif",
cat_not_like: "custom:cat_not_like.png",
cat_put: "custom:cat_put.gif",
cat_pwease: "custom:cat_pwease.png",
cat_rage: "custom:cat_rage.png",
cat_sad: "custom:cat_sad.png",
cat_snuff: "custom:cat_snuff.gif",
cat_spin: "custom:cat_spin.gif",
cat_squish: "custom:cat_squish.gif",
cat_stare: "custom:cat_stare.gif",
cat_steal: "custom:cat_steal.gif",
cat_sussy: "custom:cat_sussy.gif",
clueless: "custom:clueless.png",
death: "custom:death.gif",
developers: "custom:developers.gif",
fastwawa: "custom:fastwawa.gif",
ferris: "custom:ferris.png",
ferris_bongo: "custom:ferris_bongo.gif",
ferris_nom: "custom:ferris_nom.png",
ferris_pensive: "custom:ferris_pensive.png",
ferris_unsafe: "custom:ferris_unsafe.png",
flesh: "custom:flesh.png",
flooshed: "custom:flooshed.png",
flosh: "custom:flosh.png",
flushee: "custom:flushee.png",
forgor: "custom:forgor.png",
hollow: "custom:hollow.png",
john: "custom:john.png",
lightspeed: "custom:lightspeed.png",
little_guy: "custom:little_guy.png",
lmaoooo: "custom:lmaoooo.gif",
lol: "custom:lol.png",
looking: "custom:looking.gif",
marie: "custom:marie.png",
marie_furret: "custom:marie_furret.gif",
marie_smug: "custom:marie_smug.png",
megumin: "custom:megumin.png",
michi_above: "custom:michi_above.png",
michi_awww: "custom:michi_awww.gif",
michi_drag: "custom:michi_drag.gif",
michi_flustered: "custom:michi_flustered.png",
michi_glare: "custom:michi_glare.png",
michi_sus: "custom:michi_sus.png",
monkaS: "custom:monkaS.png",
monkaStare: "custom:monkaStare.png",
monkey_grr: "custom:monkey_grr.png",
monkey_pensive: "custom:monkey_pensive.png",
monkey_zany: "custom:monkey_zany.png",
nazu_sit: "custom:nazu_sit.png",
nazu_sus: "custom:nazu_sus.png",
ok_and: "custom:ok_and.gif",
owo: "custom:owo.png",
pat: "custom:pat.png",
pointThink: "custom:pointThink.png",
rainbowHype: "custom:rainbowHype.gif",
rawr: "custom:rawr.png",
rember: "custom:rember.png",
revolt: "custom:revolt.png",
sickly: "custom:sickly.png",
stare: "custom:stare.png",
tfyoulookingat: "custom:tfyoulookingat.png",
thanks: "custom:thanks.png",
thonk: "custom:thonk.png",
trol: "custom:trol.png",
troll_smile: "custom:troll_smile.gif",
uber: "custom:uber.png",
ubertroll: "custom:ubertroll.png",
verycool: "custom:verycool.png",
verygood: "custom:verygood.png",
wawafast: "custom:wawafast.gif",
wawastance: "custom:wawastance.png",
yeahokayyy: "custom:yeahokayyy.png",
yed: "custom:yed.png",
yems: "custom:yems.png",
michael: "custom:michael.gif",
charle: "custom:charle.gif",
sadge: "custom:sadge.webp",
sus: "custom:sus.webp",
chade: "custom:chade.gif",
gigachad: "custom:gigachad.webp",
sippy: "custom:sippy.webp",
ayame_heart: "custom:ayame_heart.png",
catgirl_peek: "custom:catgirl_peek.png",
girl_happy: "custom:girl_happy.png",
hug_plushie: "custom:hug_plushie.png",
huggies: "custom:huggies.png",
noted: "custom:noted.gif",
waving: "custom:waving.png",
mogusvented: "custom:mogusvented.png",
},
};
-29
View File
@@ -1,29 +0,0 @@
import call_join from "./call_join.mp3";
import call_leave from "./call_leave.mp3";
import message from "./message.mp3";
import outbound from "./outbound.mp3";
const SoundMap: { [key in Sounds]: string } = {
message,
outbound,
call_join,
call_leave,
};
export type Sounds = "message" | "outbound" | "call_join" | "call_leave";
export const SOUNDS_ARRAY: Sounds[] = [
"message",
"outbound",
"call_join",
"call_leave",
];
export function playSound(sound: Sounds) {
const file = SoundMap[sound];
const el = new Audio(file);
try {
el.play();
} catch (err) {
console.error("Failed to play audio file", file, err);
}
}
+14
View File
@@ -0,0 +1,14 @@
The following folders should not be added to or modified:
- `common`
- `markdown`
- `native`
- `ui`
The following are part-legacy, will remain in place and will be rewritten to some degree still:
- `navigation`
The following are mostly good to go:
- `settings`
+16 -27
View File
@@ -1,17 +1,15 @@
import { observer } from "mobx-react-lite";
import { useHistory } from "react-router-dom";
import { Channel } from "revolt.js/dist/maps/Channels";
import styled from "styled-components";
import { Channel } from "revolt.js";
import styled from "styled-components/macro";
import { Text } from "preact-i18n";
import { useState } from "preact/hooks";
import { dispatch, getState } from "../../redux";
import { Button, Checkbox } from "@revoltchat/ui";
import Button from "../ui/Button";
import Checkbox from "../ui/Checkbox";
import { Children } from "../../types/Preact";
import { useApplicationState } from "../../mobx/State";
import { SECTION_NSFW } from "../../mobx/stores/Layout";
const Base = styled.div`
display: flex;
@@ -49,9 +47,7 @@ type Props = {
export default observer((props: Props) => {
const history = useHistory();
const [consent, setConsent] = useState(
getState().sectionToggle["nsfw"] ?? false,
);
const layout = useApplicationState().layout;
const [ageGate, setAgeGate] = useState(false);
if (ageGate || !props.gated) {
@@ -81,26 +77,19 @@ export default observer((props: Props) => {
</span>
<Checkbox
checked={consent}
onChange={(v) => {
setConsent(v);
if (v) {
dispatch({
type: "SECTION_TOGGLE_SET",
id: "nsfw",
state: true,
});
} else {
dispatch({ type: "SECTION_TOGGLE_UNSET", id: "nsfw" });
}
}}>
<Text id="app.main.channel.nsfw.confirm" />
</Checkbox>
title={<Text id="app.main.channel.nsfw.confirm" />}
value={layout.getSectionState(SECTION_NSFW, false)}
onChange={() => layout.toggleSectionState(SECTION_NSFW, false)}
/>
<div className="actions">
<Button contrast onClick={() => history.goBack()}>
<Button palette="secondary" onClick={() => history.goBack()}>
<Text id="app.special.modals.actions.back" />
</Button>
<Button contrast onClick={() => consent && setAgeGate(true)}>
<Button
palette="secondary"
onClick={() =>
layout.getSectionState(SECTION_NSFW) && setAgeGate(true)
}>
<Text id={`app.main.channel.nsfw.${props.type}.confirm`} />
</Button>
</div>
+106 -42
View File
@@ -1,32 +1,35 @@
import { Channel } from "revolt.js/dist/maps/Channels";
import { User } from "revolt.js/dist/maps/Users";
import styled, { css } from "styled-components";
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { Link } from "react-router-dom";
import { Channel, User } from "revolt.js";
import { Emoji as CustomEmoji } from "revolt.js/esm/maps/Emojis";
import styled, { css } from "styled-components/macro";
import { StateUpdater, useState } from "preact/hooks";
import { useClient } from "../../context/revoltjs/RevoltClient";
import { emojiDictionary } from "../../assets/emojis";
import { useClient } from "../../controllers/client/ClientController";
import ChannelIcon from "./ChannelIcon";
import Emoji from "./Emoji";
import ServerIcon from "./ServerIcon";
import Tooltip from "./Tooltip";
import UserIcon from "./user/UserIcon";
export type AutoCompleteState =
| { type: "none" }
| ({ selected: number; within: boolean } & (
| {
type: "emoji";
matches: string[];
}
| {
type: "user";
matches: User[];
}
| {
type: "channel";
matches: Channel[];
}
));
| {
type: "emoji";
matches: (string | CustomEmoji)[];
}
| {
type: "user";
matches: User[];
}
| {
type: "channel";
matches: Channel[];
}
));
export type SearchClues = {
users?: { type: "channel"; id: string } | { type: "all" };
@@ -61,7 +64,7 @@ export function useAutoComplete(
const cursor = el.selectionStart;
const content = el.value.slice(0, cursor);
const valid = /\w/;
const valid = /[\w\-]/;
let j = content.length - 1;
if (content[j] === "@") {
@@ -79,17 +82,17 @@ export function useAutoComplete(
if (current === ":" || current === "@" || current === "#") {
const search = content.slice(j + 1, content.length);
const minLen = current === ":" ? 2 : 1
const minLen = current === ":" ? 2 : 1;
if (search.length >= minLen) {
return [
current === "#"
? "channel"
: current === ":"
? "emoji"
: "user",
? "emoji"
: "user",
search.toLowerCase(),
j + 1,
current === ":" ? j + 1 : j,
];
}
}
@@ -106,16 +109,23 @@ export function useAutoComplete(
if (type === "emoji") {
// ! TODO: we should convert it to a Binary Search Tree and use that
const matches = Object.keys(emojiDictionary)
.filter((emoji: string) => emoji.match(regex))
.splice(0, 5);
const matches = [
...Object.keys(emojiDictionary).filter((emoji: string) =>
emoji.match(regex),
),
...Array.from(client.emojis.values()).filter((emoji) =>
emoji.name.match(regex),
),
].splice(0, 5);
if (matches.length > 0) {
const currentPosition =
state.type !== "none" ? state.selected : 0;
setState({
// @ts-ignore-next-line are you high
type: "emoji",
// @ts-ignore-next-line
matches,
selected: Math.min(currentPosition, matches.length - 1),
within: false,
@@ -167,8 +177,8 @@ export function useAutoComplete(
const matches = (
search.length > 0
? users.filter((user) =>
user.username.toLowerCase().match(regex),
)
user.username.toLowerCase().match(regex),
)
: users
)
.splice(0, 5)
@@ -199,8 +209,8 @@ export function useAutoComplete(
const matches = (
search.length > 0
? channels.filter((channel) =>
channel.name!.toLowerCase().match(regex),
)
channel.name!.toLowerCase().match(regex),
)
: channels
)
.splice(0, 5)
@@ -235,15 +245,18 @@ export function useAutoComplete(
const content = el.value.split("");
if (state.type === "emoji") {
const selected = state.matches[state.selected];
content.splice(
index,
search.length,
state.matches[state.selected],
selected instanceof CustomEmoji
? selected._id
: selected,
": ",
);
} else if (state.type === "user") {
content.splice(
index - 1,
index,
search.length + 1,
"<@",
state.matches[state.selected]._id,
@@ -251,7 +264,7 @@ export function useAutoComplete(
);
} else {
content.splice(
index - 1,
index,
search.length + 1,
"<#",
state.matches[state.selected]._id,
@@ -390,12 +403,17 @@ export default function AutoComplete({
setState,
onClick,
}: Pick<AutoCompleteProps, "detached" | "state" | "setState" | "onClick">) {
const client = useClient();
return (
<Base detached={detached}>
<div>
{state.type === "emoji" &&
state.matches.map((match, i) => (
<button
style={{
display: "flex",
justifyContent: "space-between",
}}
key={match}
className={i === state.selected ? "active" : ""}
onMouseEnter={() =>
@@ -414,15 +432,61 @@ export default function AutoComplete({
})
}
onClick={onClick}>
<Emoji
emoji={
(emojiDictionary as Record<string, string>)[
match
]
}
size={20}
/>
:{match}:
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
}}>
{match instanceof CustomEmoji ? (
<img
loading="lazy"
src={match.imageURL}
style={{
width: `20px`,
height: `20px`,
}}
/>
) : (
<Emoji
emoji={
(
emojiDictionary as Record<
string,
string
>
)[match]
}
size={20}
/>
)}
<span style={{ paddingLeft: "4px" }}>{`:${
match instanceof CustomEmoji
? match.name
: match
}:`}</span>
</div>
{match instanceof CustomEmoji &&
match.parent.type == "Server" && (
<>
<Tooltip
content={
client.servers.get(
match.parent.id,
)?.name
}>
<Link
to={`/server/${match.parent.id}`}>
<ServerIcon
target={client.servers.get(
match.parent.id,
)}
size={20}
/>
</Link>
</Tooltip>
</>
)}
</button>
))}
{state.type === "user" &&
+6 -8
View File
@@ -1,14 +1,12 @@
import { Hash, VolumeFull } from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Channel } from "revolt.js";
import { useContext } from "preact/hooks";
import { AppContext } from "../../context/revoltjs/RevoltClient";
import { ImageIconBase, IconBaseProps } from "./IconBase";
import fallback from "./assets/group.png";
import { useClient } from "../../controllers/client/ClientController";
import { ImageIconBase, IconBaseProps } from "./IconBase";
interface Props extends IconBaseProps<Channel> {
isServerChannel?: boolean;
}
@@ -21,7 +19,7 @@ export default observer(
keyof Props | "children" | "as"
>,
) => {
const client = useContext(AppContext);
const client = useClient();
const {
size,
@@ -32,7 +30,7 @@ export default observer(
...imgProps
} = props;
const iconURL = client.generateFileURL(
target?.icon ?? attachment,
target?.icon ?? attachment ?? undefined,
{ max_side: 256 },
animate,
);
+8 -24
View File
@@ -1,11 +1,8 @@
import { ChevronDown } from "@styled-icons/boxicons-regular";
import { State, store } from "../../redux";
import { Action } from "../../redux/reducers";
import { Details } from "@revoltchat/ui";
import Details from "../ui/Details";
import { Children } from "../../types/Preact";
import { useApplicationState } from "../../mobx/State";
interface Props {
id: string;
@@ -25,30 +22,17 @@ export default function CollapsibleSection({
children,
...detailsProps
}: Props) {
const state: State = store.getState();
function setState(state: boolean) {
if (state === defaultValue) {
store.dispatch({
type: "SECTION_TOGGLE_UNSET",
id,
} as Action);
} else {
store.dispatch({
type: "SECTION_TOGGLE_SET",
id,
state,
} as Action);
}
}
const layout = useApplicationState().layout;
return (
<Details
open={state.sectionToggle[id] ?? defaultValue}
onToggle={(e) => setState(e.currentTarget.open)}
open={layout.getSectionState(id, defaultValue)}
onToggle={(e) =>
layout.setSectionState(id, e.currentTarget.open, defaultValue)
}
{...detailsProps}>
<summary>
<div class="padding">
<div className="padding">
<ChevronDown size={20} />
{summary}
</div>
+12 -4
View File
@@ -1,9 +1,11 @@
import { EmojiPacks } from "../../redux/reducers/settings";
import { emojiDictionary } from "../../assets/emojis";
let EMOJI_PACK = "mutant";
export type EmojiPack = "mutant" | "twemoji" | "noto" | "openmoji";
let EMOJI_PACK: EmojiPack = "mutant";
const REVISION = 3;
export function setEmojiPack(pack: EmojiPacks) {
export function setGlobalEmojiPack(pack: EmojiPack) {
EMOJI_PACK = pack;
}
@@ -40,7 +42,13 @@ function toCodePoint(rune: string) {
.join("-");
}
function parseEmoji(emoji: string) {
export function parseEmoji(emoji: string) {
if (emoji.startsWith("custom:")) {
return `https://dl.insrt.uk/projects/revolt/emotes/${emoji.substring(
7,
)}`;
}
const codepoint = toCodePoint(emoji);
return `https://static.revolt.chat/emoji/${EMOJI_PACK}/${codepoint}.svg?rev=${REVISION}`;
}
+9 -4
View File
@@ -1,14 +1,19 @@
import { Attachment } from "revolt-api/types/Autumn";
import styled, { css } from "styled-components";
import { API } from "revolt.js";
import { Nullable } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { Ref } from "preact";
export interface IconBaseProps<T> {
target?: T;
url?: string;
attachment?: Attachment;
attachment?: Nullable<API.File>;
size: number;
hover?: boolean;
animate?: boolean;
innerRef?: Ref<any>;
}
interface IconModifiers {
@@ -21,7 +26,7 @@ interface IconModifiers {
export default styled.svg<IconModifiers>`
flex-shrink: 0;
cursor: pointer;
img {
width: 100%;
height: 100%;
+11 -19
View File
@@ -1,23 +1,21 @@
import { dispatch } from "../../redux";
import { connectState } from "../../redux/connector";
import { ComboBox } from "@revoltchat/ui";
import { Language, Languages } from "../../context/Locale";
import { useApplicationState } from "../../mobx/State";
import ComboBox from "../ui/ComboBox";
import { Language, Languages } from "../../../external/lang/Languages";
type Props = {
locale: string;
};
/**
* Component providing a language selector combobox.
* Note: this is not an observer but this is fine as we are just using a combobox.
*/
export default function LocaleSelector() {
const locale = useApplicationState().locale;
export function LocaleSelector(props: Props) {
return (
<ComboBox
value={props.locale}
value={locale.getLanguage()}
onChange={(e) =>
dispatch({
type: "SET_LOCALE",
locale: e.currentTarget.value as Language,
})
locale.setLanguage(e.currentTarget.value as Language)
}>
{Object.keys(Languages).map((x) => {
const l = Languages[x as keyof typeof Languages];
@@ -30,9 +28,3 @@ export function LocaleSelector(props: Props) {
</ComboBox>
);
}
export default connectState(LocaleSelector, (state) => {
return {
locale: state.locale,
};
});
+116 -58
View File
@@ -2,86 +2,144 @@ import { Check } from "@styled-icons/boxicons-regular";
import { Cog } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { Link } from "react-router-dom";
import { ServerPermission } from "revolt.js/dist/api/permissions";
import { Server } from "revolt.js/dist/maps/Servers";
import styled from "styled-components";
import { Server } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { Text } from "preact-i18n";
import Header from "../ui/Header";
import IconButton from "../ui/IconButton";
import { IconButton } from "@revoltchat/ui";
import { modalController } from "../../controllers/modals/ModalController";
import Tooltip from "./Tooltip";
interface Props {
server: Server;
background?: boolean;
}
const ServerName = styled.div`
flex-grow: 1;
const ServerBanner = styled.div<Omit<Props, "server">>`
flex-shrink: 0;
display: flex;
flex-direction: column;
justify-content: flex-end;
background-size: cover;
background-repeat: norepeat;
background-position: center center;
${(props) =>
props.background
? css`
height: 120px;
.container {
background: linear-gradient(
0deg,
var(--secondary-background),
transparent
);
}
`
: css`
background-color: var(--secondary-header);
`}
.container {
height: var(--header-height);
display: flex;
align-items: center;
padding: 0 14px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
gap: 8px;
.title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-grow: 1;
cursor: pointer;
color: var(--foreground);
}
}
`;
export default observer(({ server }: Props) => {
const bannerURL = server.generateBannerURL({ width: 480 });
return (
<Header
borders
placement="secondary"
<ServerBanner
background={typeof bannerURL !== "undefined"}
style={{
background: bannerURL ? `url('${bannerURL}')` : undefined,
backgroundImage: bannerURL ? `url('${bannerURL}')` : undefined,
}}>
{server.flags && server.flags & 1 ? (
<Tooltip
content={<Text id="app.special.server-badges.official" />}
placement={"bottom-start"}>
<svg width="20" height="20">
<image
xlinkHref="/assets/badges/verified.svg"
height="20"
width="20"
/>
<image
xlinkHref="/assets/badges/revolt_r.svg"
height="15"
width="15"
x="2"
y="3"
style={
"justify-content: center; align-items: center; filter: brightness(0);"
}
/>
</svg>
</Tooltip>
) : undefined}
{server.flags && server.flags & 2 ? (
<Tooltip
content={<Text id="app.special.server-badges.verified" />}
placement={"bottom-start"}>
<svg width="20" height="20">
<image
xlinkHref="/assets/badges/verified.svg"
height="20"
width="20"
/>
<foreignObject x="2" y="2" width="15" height="15">
<Check size={15} color="black" strokeWidth={8} />
</foreignObject>
</svg>
</Tooltip>
) : undefined}
<ServerName>{server.name}</ServerName>
{(server.permission & ServerPermission.ManageServer) > 0 && (
<div className="actions">
<div className="container">
{server.flags && server.flags & 1 ? (
<Tooltip
content={
<Text id="app.special.server-badges.official" />
}
placement={"bottom-start"}>
<svg width="20" height="20">
<image
xlinkHref="/assets/badges/verified.svg"
height="20"
width="20"
/>
<image
xlinkHref="/assets/badges/revolt_r.svg"
height="15"
width="15"
x="2"
y="3"
style={
"justify-content: center; align-items: center; filter: brightness(0);"
}
/>
</svg>
</Tooltip>
) : undefined}
{server.flags && server.flags & 2 ? (
<Tooltip
content={
<Text id="app.special.server-badges.verified" />
}
placement={"bottom-start"}>
<svg width="20" height="20">
<image
xlinkHref="/assets/badges/verified.svg"
height="20"
width="20"
/>
<foreignObject x="2" y="2" width="15" height="15">
<Check
size={15}
color="black"
strokeWidth={8}
/>
</foreignObject>
</svg>
</Tooltip>
) : undefined}
<a
className="title"
onClick={() =>
modalController.push({ type: "server_info", server })
}>
{server.name}
</a>
{server.havePermission("ManageServer") && (
<Link to={`/server/${server._id}/settings`}>
<IconButton>
<Cog size={24} />
<Cog size={20} />
</IconButton>
</Link>
</div>
)}
</Header>
)}
</div>
</ServerBanner>
);
});
+13 -9
View File
@@ -1,11 +1,10 @@
import { observer } from "mobx-react-lite";
import { Server } from "revolt.js/dist/maps/Servers";
import styled from "styled-components";
import { Server } from "revolt.js";
import styled from "styled-components/macro";
import { useContext } from "preact/hooks";
import { AppContext } from "../../context/revoltjs/RevoltClient";
import { useClient } from "../../controllers/client/ClientController";
import { IconBaseProps, ImageIconBase } from "./IconBase";
interface Props extends IconBaseProps<Server> {
@@ -13,10 +12,13 @@ interface Props extends IconBaseProps<Server> {
}
const ServerText = styled.div`
display: grid;
display: flex;
align-items: center;
justify-content: center;
padding: 0.2em;
font-size: 0.75rem;
font-weight: 600;
overflow: hidden;
place-items: center;
color: var(--foreground);
background: var(--primary-background);
border-radius: var(--border-radius-half);
@@ -31,12 +33,12 @@ export default observer(
keyof Props | "children" | "as"
>,
) => {
const client = useContext(AppContext);
const client = useClient();
const { target, attachment, size, animate, server_name, ...imgProps } =
props;
const iconURL = client.generateFileURL(
target?.icon ?? attachment,
target?.icon ?? attachment ?? undefined,
{ max_side: 256 },
animate,
);
@@ -49,7 +51,9 @@ export default observer(
{name
.split(" ")
.map((x) => x[0])
.filter((x) => typeof x !== "undefined")}
.filter((x) => typeof x !== "undefined")
.join("")
.substring(0, 3)}
</ServerText>
);
}
+2 -4
View File
@@ -1,10 +1,8 @@
import Tippy, { TippyProps } from "@tippyjs/react";
import styled from "styled-components";
import styled from "styled-components/macro";
import { Text } from "preact-i18n";
import { Children } from "../../types/Preact";
type Props = Omit<TippyProps, "children"> & {
children: Children;
content: Children;
@@ -14,7 +12,7 @@ export default function Tooltip(props: Props) {
const { children, content, ...tippyProps } = props;
return (
<Tippy content={content} {...tippyProps}>
<Tippy content={content} animation="shift-away" {...tippyProps}>
{/*
// @ts-expect-error Type mis-match. */}
<div style={`display: flex;`}>{children}</div>
+13 -10
View File
@@ -1,15 +1,15 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { Download, CloudDownload } from "@styled-icons/boxicons-regular";
import { useContext, useEffect, useState } from "preact/hooks";
import { useEffect, useState } from "preact/hooks";
import { IconButton } from "@revoltchat/ui";
import { internalSubscribe } from "../../lib/eventEmitter";
import { ThemeContext } from "../../context/Theme";
import { useApplicationState } from "../../mobx/State";
import IconButton from "../ui/IconButton";
import { updateSW } from "../../main";
import { updateSW } from "../../updateWorker";
import Tooltip from "./Tooltip";
let pendingUpdate = false;
@@ -27,27 +27,30 @@ export default function UpdateIndicator({ style }: Props) {
});
if (!pending) return null;
const theme = useContext(ThemeContext);
const theme = useApplicationState().settings.theme;
if (style === "titlebar") {
return (
<div class="actions">
<div className="actions">
<Tooltip
content="A new update is available!"
placement="bottom">
<div onClick={() => updateSW(true)}>
<CloudDownload size={22} color={theme.success} />
<CloudDownload
size={22}
color={theme.getVariable("success")}
/>
</div>
</Tooltip>
</div>
);
}
if (window.isNative) return null;
if (window.isNative && window.native.getConfig().frame) return null;
return (
<IconButton onClick={() => updateSW(true)}>
<Download size={22} color={theme.success} />
<Download size={22} color={theme.getVariable("success")} />
</IconButton>
);
}
+59 -36
View File
@@ -1,20 +1,20 @@
import { observer } from "mobx-react-lite";
import { Message as MessageObject } from "revolt.js/dist/maps/Messages";
import { Message as MessageObject } from "revolt.js";
import { attachContextMenu } from "preact-context-menu";
import { useTriggerEvents } from "preact-context-menu";
import { memo } from "preact/compat";
import { useState } from "preact/hooks";
import { useEffect, useState } from "preact/hooks";
import { Category, Button } from "@revoltchat/ui";
import { internalEmit } from "../../../lib/eventEmitter";
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
import { QueuedMessage } from "../../../redux/reducers/queue";
import { QueuedMessage } from "../../../mobx/stores/MessageQueue";
import { useIntermediate } from "../../../context/intermediate/Intermediate";
import { useClient } from "../../../context/revoltjs/RevoltClient";
import { I18nError } from "../../../context/Locale";
import Overline from "../../ui/Overline";
import { Children } from "../../../types/Preact";
import { modalController } from "../../../controllers/modals/ModalController";
import Markdown from "../../markdown/Markdown";
import UserIcon from "../user/UserIcon";
import { Username } from "../user/UserShort";
@@ -25,6 +25,8 @@ import MessageBase, {
} from "./MessageBase";
import Attachment from "./attachments/Attachment";
import { MessageReply } from "./attachments/MessageReply";
import { Reactions } from "./attachments/Reactions";
import { MessageOverlayBar } from "./bars/MessageOverlayBar";
import Embed from "./embed/Embed";
import InviteList from "./embed/EmbedInvite";
@@ -50,27 +52,26 @@ const Message = observer(
queued,
hideReply,
}: Props) => {
const client = useClient();
const client = message.client;
const user = message.author;
const { openScreen } = useIntermediate();
const content = message.content as string;
const content = message.content;
const head =
preferHead || (message.reply_ids && message.reply_ids.length > 0);
// ! TODO: tell fatal to make this type generic
// bree: Fatal please...
const userContext = attachContext
? (attachContextMenu("Menu", {
? useTriggerEvents("Menu", {
user: message.author_id,
contextualChannel: message.channel_id,
// eslint-disable-next-line
}) as any)
})
: undefined;
const openProfile = () =>
openScreen({ id: "profile", user_id: message.author_id });
modalController.push({
type: "user_profile",
user_id: message.author_id,
});
const handleUserClick = (e: MouseEvent) => {
if (e.shiftKey && user?._id) {
@@ -86,7 +87,9 @@ const Message = observer(
};
// ! FIXME(?): animate on hover
const [animate, setAnimate] = useState(false);
const [mouseHovering, setAnimate] = useState(false);
const [reactionsOpen, setReactionsOpen] = useState(false);
useEffect(() => setAnimate(false), [replacement]);
return (
<div id={message._id}>
@@ -96,7 +99,7 @@ const Message = observer(
key={message_id}
index={index}
id={message_id}
channel={message.channel!}
channel={message.channel}
parent_mentions={message.mention_ids ?? []}
/>
))}
@@ -114,28 +117,31 @@ const Message = observer(
}
contrast={contrast}
sending={typeof queued !== "undefined"}
mention={message.mention_ids?.includes(client.user!._id)}
failed={typeof queued?.error !== "undefined"}
onContextMenu={
attachContext
? attachContextMenu("Menu", {
message,
contextualChannel: message.channel_id,
queued,
})
mention={
message.mention_ids && client.user
? message.mention_ids.includes(client.user._id)
: undefined
}
failed={typeof queued?.error !== "undefined"}
{...(attachContext
? useTriggerEvents("Menu", {
message,
contextualChannel: message.channel_id,
queued,
})
: undefined)}
onMouseEnter={() => setAnimate(true)}
onMouseLeave={() => setAnimate(false)}>
<MessageInfo>
<MessageInfo click={typeof head !== "undefined"}>
{head ? (
<UserIcon
className="avatar"
url={message.generateMasqAvatarURL()}
target={user}
size={36}
onContextMenu={userContext}
onClick={handleUserClick}
animate={animate}
animate={mouseHovering}
{...(userContext as any)}
showServerIdentity
/>
) : (
@@ -150,8 +156,8 @@ const Message = observer(
className="author"
showServerIdentity
onClick={handleUserClick}
onContextMenu={userContext}
masquerade={message.masquerade!}
{...userContext}
/>
<MessageDetail
message={message}
@@ -159,21 +165,38 @@ const Message = observer(
/>
</span>
)}
{replacement ?? <Markdown content={content} />}
{replacement ??
(content && <Markdown content={content} />)}
{!queued && <InviteList message={message} />}
{queued?.error && (
<Overline type="error" error={queued.error} />
<Category>
<I18nError error={queued.error} />
</Category>
)}
{message.attachments?.map((attachment, index) => (
<Attachment
key={index}
attachment={attachment}
hasContent={index > 0 || content.length > 0}
hasContent={
index > 0 ||
(content ? content.length > 0 : false)
}
/>
))}
{message.embeds?.map((embed, index) => (
<Embed key={index} embed={embed} />
))}
<Reactions message={message} />
{(mouseHovering || reactionsOpen) &&
!replacement &&
!isTouchscreenDevice && (
<MessageOverlayBar
reactionsOpen={reactionsOpen}
setReactionsOpen={setReactionsOpen}
message={message}
queued={queued}
/>
)}
</MessageContent>
</MessageBase>
</div>
+21 -12
View File
@@ -1,6 +1,6 @@
import { observer } from "mobx-react-lite";
import { Message } from "revolt.js/dist/maps/Messages";
import styled, { css, keyframes } from "styled-components";
import { Message } from "revolt.js";
import styled, { css, keyframes } from "styled-components/macro";
import { decodeTime } from "ulid";
import { Text } from "preact-i18n";
@@ -134,7 +134,7 @@ export default styled.div<BaseMessageProps>`
}
`;
export const MessageInfo = styled.div`
export const MessageInfo = styled.div<{ click: boolean }>`
width: 62px;
display: flex;
flex-shrink: 0;
@@ -142,6 +142,14 @@ export const MessageInfo = styled.div`
flex-direction: row;
justify-content: center;
.avatar {
user-select: none;
cursor: pointer;
&:active {
transform: translateY(1px);
}
}
.copyBracket {
opacity: 0;
position: absolute;
@@ -152,15 +160,6 @@ export const MessageInfo = styled.div`
position: absolute;
}
svg {
user-select: none;
cursor: pointer;
&:active {
transform: translateY(1px);
}
}
time {
opacity: 0;
}
@@ -192,9 +191,19 @@ export const MessageInfo = styled.div`
margin-right: 0.5em;
color: var(--tertiary-foreground);
}
/*${(props) =>
props.click &&
css`
cursor: pointer;
`}*/
`;
export const MessageContent = styled.div`
// Position relatively so we can put
// the overlay in the right place.
position: relative;
min-width: 0;
flex-grow: 1;
display: flex;
+254 -76
View File
@@ -1,13 +1,15 @@
import { Send, ShieldX } from "@styled-icons/boxicons-solid";
import { HappyBeaming, Send, ShieldX } from "@styled-icons/boxicons-solid";
import Axios, { CancelTokenSource } from "axios";
import { observer } from "mobx-react-lite";
import { ChannelPermission } from "revolt.js/dist/api/permissions";
import { Channel } from "revolt.js/dist/maps/Channels";
import styled, { css } from "styled-components";
import { Channel } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { ulid } from "ulid";
import { Text } from "preact-i18n";
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
import { memo } from "preact/compat";
import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
import { IconButton, Picker } from "@revoltchat/ui";
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
import { debounce } from "../../../lib/debounce";
@@ -20,21 +22,25 @@ import {
SMOOTH_SCROLL_ON_RECEIVE,
} from "../../../lib/renderer/Singleton";
import { dispatch, getState } from "../../../redux";
import { Reply } from "../../../redux/reducers/queue";
import { state, useApplicationState } from "../../../mobx/State";
import { DraftObject } from "../../../mobx/stores/Draft";
import { Reply } from "../../../mobx/stores/MessageQueue";
import { SoundContext } from "../../../context/Settings";
import { useIntermediate } from "../../../context/intermediate/Intermediate";
import { dayjs } from "../../../context/Locale";
import { emojiDictionary } from "../../../assets/emojis";
import {
clientController,
useClient,
} from "../../../controllers/client/ClientController";
import { takeError } from "../../../controllers/client/jsx/error";
import {
FileUploader,
grabFiles,
uploadFile,
} from "../../../context/revoltjs/FileUploads";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import { takeError } from "../../../context/revoltjs/util";
import IconButton from "../../ui/IconButton";
} from "../../../controllers/client/jsx/legacy/FileUploads";
import { modalController } from "../../../controllers/modals/ModalController";
import { RenderEmoji } from "../../markdown/plugins/emoji";
import AutoComplete, { useAutoComplete } from "../AutoComplete";
import { PermissionTooltip } from "../Tooltip";
import FilePreview from "./bars/FilePreview";
@@ -57,6 +63,7 @@ export type UploadState =
| { type: "failed"; files: File[]; error: string };
const Base = styled.div`
z-index: 1;
display: flex;
align-items: flex-start;
background: var(--message-box);
@@ -79,9 +86,15 @@ const Blocked = styled.div`
user-select: none;
font-size: var(--text-size);
color: var(--tertiary-foreground);
flex-grow: 1;
cursor: not-allowed;
.text {
padding: 14px 14px 14px 0;
padding: var(--message-box-padding);
}
> div > div {
cursor: default;
}
svg {
@@ -90,13 +103,17 @@ const Blocked = styled.div`
`;
const Action = styled.div`
display: flex;
place-items: center;
> div {
> a {
height: 48px;
width: 48px;
padding: 12px;
display: flex;
align-items: center;
justify-content: center;
/*padding: 14px 0 14px 14px;*/
}
.mobile {
width: 62px;
}
${() =>
@@ -108,28 +125,139 @@ const Action = styled.div`
`}
`;
const FileAction = styled.div`
> a {
height: 48px;
width: 62px;
display: flex;
align-items: center;
justify-content: center;
}
`;
const FloatingLayer = styled.div`
position: relative;
`;
const ThisCodeWillBeReplacedAnywaysSoIMightAsWellJustDoItThisWay__Padding = styled.div`
width: 16px;
`;
// For sed replacement
const RE_SED = new RegExp("^s/([^])*/([^])*$");
// Tests for code block delimiters (``` at start of line)
const RE_CODE_DELIMITER = new RegExp("^```", "gm");
export const HackAlertThisFileWillBeReplaced = observer(
({
onSelect,
onClose,
}: {
onSelect: (emoji: string) => void;
onClose: () => void;
}) => {
const renderEmoji = useMemo(
() =>
memo(({ emoji }: { emoji: string }) => (
<RenderEmoji match={emoji} {...({} as any)} />
)),
[],
);
const emojis: Record<string, any> = {
default: Object.keys(emojiDictionary).map((id) => ({ id })),
};
// ! FIXME: also expose typing from component
const categories: any[] = [];
for (const server of state.ordering.orderedServers) {
// ! FIXME: add a separate map on each server for emoji
const list = [...clientController.getReadyClient()!.emojis.values()]
.filter(
(emoji) =>
emoji.parent.type !== "Detached" &&
emoji.parent.id === server._id,
)
.map(({ _id, name }) => ({ id: _id, name }));
if (list.length > 0) {
emojis[server._id] = list;
categories.push({
id: server._id,
name: server.name,
iconURL: server.generateIconURL({ max_side: 256 }),
});
}
}
categories.push({
id: "default",
name: "Default",
emoji: "smiley",
});
return (
<Picker
emojis={emojis}
categories={categories}
renderEmoji={renderEmoji}
onSelect={onSelect}
onClose={onClose}
/>
);
},
);
// ! FIXME: add to app config and load from app config
export const CAN_UPLOAD_AT_ONCE = 4;
export const CAN_UPLOAD_AT_ONCE = 5;
export default observer(({ channel }: Props) => {
const [draft, setDraft] = useState(getState().drafts[channel._id] ?? "");
const state = useApplicationState();
const [uploadState, setUploadState] = useState<UploadState>({
type: "none",
});
const [typing, setTyping] = useState<boolean | number>(false);
const [replies, setReplies] = useState<Reply[]>([]);
const playSound = useContext(SoundContext);
const { openScreen } = useIntermediate();
const client = useContext(AppContext);
const [picker, setPicker] = useState(false);
const client = useClient();
const translate = useTranslation();
const closePicker = useCallback(() => setPicker(false), []);
const renderer = getRenderer(channel);
if (!(channel.permission & ChannelPermission.SendMessage)) {
if (channel.server?.member?.timeout) {
return (
<Base>
<Blocked>
<Action>
<PermissionTooltip
permission="SendMessages"
placement="top">
<ShieldX size={22} />
</PermissionTooltip>
</Action>
<div className="text">
<Text
id="app.main.channel.misc.timed_out"
fields={{
// TODO: make this reactive
time: dayjs().to(
channel.server.member.timeout,
true,
),
}}
/>
</div>
</Blocked>
</Base>
);
}
if (!channel.havePermission("SendMessage")) {
return (
<Base>
<Blocked>
@@ -148,27 +276,23 @@ export default observer(({ channel }: Props) => {
);
}
// Push message content to draft.
const setMessage = useCallback(
(content?: string) => {
setDraft(content ?? "");
if (content) {
dispatch({
type: "SET_DRAFT",
channel: channel._id,
content,
});
} else {
dispatch({
type: "CLEAR_DRAFT",
channel: channel._id,
});
}
const dobj: DraftObject = {
content,
};
state.draft.set(channel._id, dobj);
},
[channel._id],
[state.draft, channel._id],
);
useEffect(() => {
/**
*
* @param content
* @param action
*/
function append(content: string, action: "quote" | "mention") {
const text =
action === "quote"
@@ -178,10 +302,10 @@ export default observer(({ channel }: Props) => {
.join("\n")}\n\n`
: `${content} `;
if (!draft || draft.length === 0) {
if (!state.draft.has(channel._id)) {
setMessage(text);
} else {
setMessage(`${draft}\n${text}`);
setMessage(`${state.draft.get(channel._id)}\n${text}`);
}
}
@@ -190,16 +314,20 @@ export default observer(({ channel }: Props) => {
"append",
append as (...args: unknown[]) => void,
);
}, [draft, setMessage]);
}, [state.draft, channel._id, setMessage]);
/**
* Trigger send message.
*/
async function send() {
if (uploadState.type === "uploading" || uploadState.type === "sending")
return;
const content = draft?.trim() ?? "";
const content = state.draft.get(channel._id)?.content?.trim() ?? "";
if (uploadState.type === "attached") return sendFile(content);
if (content.length === 0) return;
internalEmit("NewMessages", "hide");
stopTyping();
setMessage();
setReplies([]);
@@ -215,7 +343,7 @@ export default observer(({ channel }: Props) => {
);
renderer.messages.reverse();
if (msg) {
if (msg?.content) {
// eslint-disable-next-line prefer-const
let [_, toReplace, newText, flags] = content.split(/\//);
@@ -247,20 +375,15 @@ export default observer(({ channel }: Props) => {
}
}
} else {
playSound("outbound");
state.settings.sounds.playSound("outbound");
dispatch({
type: "QUEUE_ADD",
nonce,
state.queue.add(nonce, channel._id, {
_id: nonce,
channel: channel._id,
message: {
_id: nonce,
channel: channel._id,
author: client.user!._id,
author: client.user!._id,
content,
replies,
},
content,
replies,
});
defer(() => renderer.jumpToBottom(SMOOTH_SCROLL_ON_RECEIVE));
@@ -272,18 +395,20 @@ export default observer(({ channel }: Props) => {
replies,
});
} catch (error) {
dispatch({
type: "QUEUE_FAIL",
error: takeError(error),
nonce,
});
state.queue.fail(nonce, takeError(error));
}
}
}
/**
*
* @param content
* @returns
*/
async function sendFile(content: string) {
if (uploadState.type !== "attached") return;
const attachments: string[] = [];
setMessage;
const cancel = Axios.CancelToken.source();
const files = uploadState.files;
@@ -360,7 +485,7 @@ export default observer(({ channel }: Props) => {
setMessage();
setReplies([]);
playSound("outbound");
state.settings.sounds.playSound("outbound");
if (files.length > CAN_UPLOAD_AT_ONCE) {
setUploadState({
@@ -372,6 +497,10 @@ export default observer(({ channel }: Props) => {
}
}
/**
*
* @returns
*/
function startTyping() {
if (typeof typing === "number" && +new Date() < typing) return;
@@ -385,6 +514,10 @@ export default observer(({ channel }: Props) => {
}
}
/**
*
* @param force
*/
function stopTyping(force?: boolean) {
if (force || typing) {
const ws = client.websocket;
@@ -398,6 +531,21 @@ export default observer(({ channel }: Props) => {
}
}
function isInCodeBlock(cursor: number): boolean {
const content = state.draft.get(channel._id)?.content || "";
const contentBeforeCursor = content.substring(0, cursor);
let delimiterCount = 0;
for (const delimiter of contentBeforeCursor.matchAll(
RE_CODE_DELIMITER,
)) {
delimiterCount++;
}
// Odd number of ``` delimiters before cursor => we are in code block
return delimiterCount % 2 === 1;
}
// TODO: change to useDebounceCallback
// eslint-disable-next-line
const debouncedStopTyping = useCallback(
@@ -434,7 +582,10 @@ export default observer(({ channel }: Props) => {
files: [...uploadState.files, ...files],
}),
() =>
openScreen({ id: "error", error: "FileTooLarge" }),
modalController.push({
type: "error",
error: "FileTooLarge",
}),
true,
)
}
@@ -457,9 +608,25 @@ export default observer(({ channel }: Props) => {
replies={replies}
setReplies={setReplies}
/>
<FloatingLayer>
{picker && (
<HackAlertThisFileWillBeReplaced
onSelect={(emoji) => {
const v = state.draft.get(channel._id);
const cnt: DraftObject = {
content:
(v?.content ? `${v.content} ` : "") +
`:${emoji}:`,
};
state.draft.set(channel._id, cnt);
}}
onClose={closePicker}
/>
)}
</FloatingLayer>
<Base>
{channel.permission & ChannelPermission.UploadFiles ? (
<Action>
{channel.havePermission("UploadFiles") ? (
<FileAction>
<FileUploader
size={24}
behaviour="multi"
@@ -494,8 +661,10 @@ export default observer(({ channel }: Props) => {
}
}}
/>
</Action>
) : undefined}
</FileAction>
) : (
<ThisCodeWillBeReplacedAnywaysSoIMightAsWellJustDoItThisWay__Padding />
)}
<TextAreaAutoSize
autoFocus
hideBorder
@@ -503,7 +672,7 @@ export default observer(({ channel }: Props) => {
id="message"
maxLength={2000}
onKeyUp={onKeyUp}
value={draft ?? ""}
value={state.draft.get(channel._id)?.content ?? ""}
padding="var(--message-box-padding)"
onKeyDown={(e) => {
if (e.ctrlKey && e.key === "Enter") {
@@ -515,7 +684,7 @@ export default observer(({ channel }: Props) => {
if (
e.key === "ArrowUp" &&
(!draft || draft.length === 0)
!state.draft.has(channel._id)
) {
e.preventDefault();
internalEmit("MessageRenderer", "edit_last");
@@ -526,7 +695,8 @@ export default observer(({ channel }: Props) => {
!e.shiftKey &&
!e.isComposing &&
e.key === "Enter" &&
!isTouchscreenDevice
!isTouchscreenDevice &&
!isInCodeBlock(e.currentTarget.selectionStart)
) {
e.preventDefault();
return send();
@@ -574,12 +744,20 @@ export default observer(({ channel }: Props) => {
onFocus={onFocus}
onBlur={onBlur}
/>
{state.experiments.isEnabled("picker") && (
<Action>
<IconButton onClick={() => setPicker(!picker)}>
<HappyBeaming size={24} />
</IconButton>
</Action>
)}
<Action>
{/*<IconButton onClick={emojiPicker}>
<HappyAlt size={20} />
</IconButton>*/}
<IconButton
className="mobile"
className={
state.settings.get("appearance:show_send_button")
? ""
: "mobile"
}
onClick={send}
onMouseDown={(e) => e.preventDefault()}>
<Send size={20} />
@@ -9,16 +9,26 @@ import {
EditAlt,
Edit,
MessageSquareEdit,
Key,
} from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { SystemMessage as SystemMessageI } from "revolt-api/types/Channels";
import { Message } from "revolt.js/dist/maps/Messages";
import styled from "styled-components";
import { Message, API } from "revolt.js";
import styled from "styled-components/macro";
import { decodeTime } from "ulid";
import { attachContextMenu } from "preact-context-menu";
import { useTriggerEvents } from "preact-context-menu";
import { Text } from "preact-i18n";
import { Row } from "@revoltchat/ui";
import { TextReact } from "../../../lib/i18n";
import { useApplicationState } from "../../../mobx/State";
import { dayjs } from "../../../context/Locale";
import Markdown from "../../markdown/Markdown";
import Tooltip from "../Tooltip";
import UserShort from "../user/UserShort";
import MessageBase, { MessageDetail, MessageInfo } from "./MessageBase";
@@ -29,6 +39,26 @@ const SystemContent = styled.div`
flex-wrap: wrap;
align-items: center;
flex-direction: row;
font-size: 14px;
color: var(--secondary-foreground);
span {
font-weight: 600;
color: var(--foreground);
}
svg {
margin-inline-end: 4px;
}
svg,
span {
cursor: pointer;
}
span:hover {
text-decoration: underline;
}
`;
interface Props {
@@ -48,20 +78,23 @@ const iconDictionary = {
channel_renamed: EditAlt,
channel_description_changed: Edit,
channel_icon_changed: MessageSquareEdit,
channel_ownership_changed: Key,
text: InfoCircle,
};
export const SystemMessage = observer(
({ attachContext, message, highlight, hideInfo }: Props) => {
const data = message.asSystemMessage;
const SystemMessageIcon =
iconDictionary[data.type as SystemMessageI["type"]] ?? InfoCircle;
if (!data) return null;
let children;
const settings = useApplicationState().settings;
const SystemMessageIcon =
iconDictionary[data.type as API.SystemMessage["type"]] ??
InfoCircle;
let children = null;
switch (data.type) {
case "text":
children = <span>{data.content}</span>;
break;
case "user_added":
case "user_remove":
children = (
@@ -81,16 +114,39 @@ export const SystemMessage = observer(
case "user_joined":
case "user_left":
case "user_kicked":
case "user_banned":
case "user_banned": {
const createdAt = data.user ? decodeTime(data.user._id) : null;
children = (
<TextReact
id={`app.main.channel.system.${data.type}`}
fields={{
user: <UserShort user={data.user} />,
}}
/>
<Row centred>
<TextReact
id={`app.main.channel.system.${data.type}`}
fields={{
user: <UserShort user={data.user} />,
}}
/>
{data.type == "user_joined" &&
createdAt &&
(settings.get("appearance:show_account_age") ||
Date.now() - createdAt <
1000 * 60 * 60 * 24 * 7) && (
<Tooltip
content={
<Text
id="app.main.channel.system.registered_at"
fields={{
time: dayjs(
createdAt,
).fromNow(),
}}
/>
}>
<InfoCircle size={16} />
</Tooltip>
)}
</Row>
);
break;
}
case "channel_renamed":
children = (
<TextReact
@@ -113,21 +169,35 @@ export const SystemMessage = observer(
/>
);
break;
case "channel_ownership_changed":
children = (
<TextReact
id={`app.main.channel.system.channel_ownership_changed`}
fields={{
from: <UserShort user={data.from} />,
to: <UserShort user={data.to} />,
}}
/>
);
break;
case "text":
if (message.system?.type === "text") {
children = <Markdown content={message.system?.content} />;
}
break;
}
return (
<MessageBase
highlight={highlight}
onContextMenu={
attachContext
? attachContextMenu("Menu", {
message,
contextualChannel: message.channel,
})
: undefined
}>
{...(attachContext
? useTriggerEvents("Menu", {
message,
contextualChannel: message.channel,
})
: undefined)}>
{!hideInfo && (
<MessageInfo>
<MessageInfo click={false}>
<MessageDetail message={message} position="left" />
<SystemMessageIcon className="systemIcon" />
</MessageInfo>
@@ -1,13 +1,11 @@
import { Attachment as AttachmentI } from "revolt-api/types/Autumn";
import { API } from "revolt.js";
import styles from "./Attachment.module.scss";
import classNames from "classnames";
import { attachContextMenu } from "preact-context-menu";
import { useContext, useState } from "preact/hooks";
import { useIntermediate } from "../../../../context/intermediate/Intermediate";
import { AppContext } from "../../../../context/revoltjs/RevoltClient";
import { useTriggerEvents } from "preact-context-menu";
import { useState } from "preact/hooks";
import { useClient } from "../../../../controllers/client/ClientController";
import AttachmentActions from "./AttachmentActions";
import { SizedGrid } from "./Grid";
import ImageFile from "./ImageFile";
@@ -15,14 +13,14 @@ import Spoiler from "./Spoiler";
import TextFile from "./TextFile";
interface Props {
attachment: AttachmentI;
hasContent: boolean;
attachment: API.File;
hasContent?: boolean;
}
const MAX_ATTACHMENT_WIDTH = 480;
export default function Attachment({ attachment, hasContent }: Props) {
const client = useContext(AppContext);
const client = useClient();
const { filename, metadata } = attachment;
const [spoiler, setSpoiler] = useState(filename.startsWith("SPOILER_"));
@@ -38,8 +36,8 @@ export default function Attachment({ attachment, hasContent }: Props) {
<SizedGrid
width={metadata.width}
height={metadata.height}
onContextMenu={attachContextMenu("Menu", {
attachment: attachment,
{...useTriggerEvents("Menu", {
attachment,
})}
className={classNames({
[styles.margin]: hasContent,
@@ -1,33 +1,33 @@
import {
Download,
LinkExternal,
File,
Headphone,
Video,
Download,
} from "@styled-icons/boxicons-regular";
import { Attachment } from "revolt-api/types/Autumn";
import { File, Video } from "@styled-icons/boxicons-solid";
import { isFirefox } from "react-device-detect";
import { API } from "revolt.js";
import styles from "./AttachmentActions.module.scss";
import classNames from "classnames";
import { useContext } from "preact/hooks";
import { IconButton } from "@revoltchat/ui";
import { determineFileSize } from "../../../../lib/fileSize";
import { AppContext } from "../../../../context/revoltjs/RevoltClient";
import IconButton from "../../../ui/IconButton";
import { useClient } from "../../../../controllers/client/ClientController";
interface Props {
attachment: Attachment;
attachment: API.File;
}
export default function AttachmentActions({ attachment }: Props) {
const client = useContext(AppContext);
const client = useClient();
const { filename, metadata, size } = attachment;
const url = client.generateFileURL(attachment)!;
const url = client.generateFileURL(attachment);
const open_url = `${url}/${filename}`;
const download_url = url.replace("attachments", "attachments/download");
const download_url = url?.replace("attachments", "attachments/download");
const filesize = determineFileSize(size);
@@ -52,7 +52,7 @@ export default function AttachmentActions({ attachment }: Props) {
href={download_url}
className={styles.downloadIcon}
download
target="_blank"
target={isFirefox || window.native ? "_blank" : "_self"}
rel="noreferrer">
<IconButton>
<Download size={24} />
@@ -70,7 +70,7 @@ export default function AttachmentActions({ attachment }: Props) {
href={download_url}
className={styles.downloadIcon}
download
target="_blank"
target={isFirefox || window.native ? "_blank" : "_self"}
rel="noreferrer">
<IconButton>
<Download size={24} />
@@ -90,7 +90,7 @@ export default function AttachmentActions({ attachment }: Props) {
href={download_url}
className={styles.downloadIcon}
download
target="_blank"
target={isFirefox || window.native ? "_blank" : "_self"}
rel="noreferrer">
<IconButton>
<Download size={24} />
@@ -119,7 +119,7 @@ export default function AttachmentActions({ attachment }: Props) {
href={download_url}
className={styles.downloadIcon}
download
target="_blank"
target={isFirefox || window.native ? "_blank" : "_self"}
rel="noreferrer">
<IconButton>
<Download size={24} />
@@ -1,12 +1,13 @@
import styled from "styled-components";
import styled from "styled-components/macro";
import { Children } from "../../../../types/Preact";
import { Ref } from "preact";
const Grid = styled.div<{ width: number; height: number }>`
--width: ${props => props.width}px;
--height: ${props => props.height}px;
--width: ${(props) => props.width}px;
--height: ${(props) => props.height}px;
display: grid;
overflow: hidden;
aspect-ratio: ${(props) => props.width} / ${(props) => props.height};
max-width: min(var(--width), var(--attachment-max-width));
@@ -42,7 +43,7 @@ const Grid = styled.div<{ width: number; height: number }>`
overflow: hidden;
object-fit: contain;
// It's something
object-position: left;
}
@@ -71,13 +72,14 @@ type Props = Omit<
children?: Children;
width: number;
height: number;
innerRef?: Ref<any>;
};
export function SizedGrid(props: Props) {
const { width, height, children, ...divProps } = props;
const { width, height, children, innerRef, ...divProps } = props;
return (
<Grid {...divProps} width={width} height={height}>
<Grid {...divProps} width={width} height={height} ref={innerRef}>
{children}
</Grid>
);
@@ -1,47 +1,42 @@
import { Attachment } from "revolt-api/types/Autumn";
import { API } from "revolt.js";
import styles from "./Attachment.module.scss";
import classNames from "classnames";
import { useContext, useState } from "preact/hooks";
import { useState } from "preact/hooks";
import { useIntermediate } from "../../../../context/intermediate/Intermediate";
import { AppContext } from "../../../../context/revoltjs/RevoltClient";
import { useClient } from "../../../../controllers/client/ClientController";
import { modalController } from "../../../../controllers/modals/ModalController";
enum ImageLoadingState {
Loading,
Loaded,
Error
Error,
}
type Props = JSX.HTMLAttributes<HTMLImageElement> & {
attachment: Attachment;
}
attachment: API.File;
};
export default function ImageFile({ attachment, ...props }: Props) {
const [loading, setLoading] = useState(ImageLoadingState.Loading);
const client = useContext(AppContext);
const { openScreen } = useIntermediate();
const client = useClient();
const url = client.generateFileURL(attachment)!;
return <img
{...props}
src={url}
alt={attachment.filename}
loading="lazy"
className={classNames(styles.image, {
[styles.loading]: loading !== ImageLoadingState.Loaded
})}
onClick={() =>
openScreen({ id: "image_viewer", attachment })
}
onMouseDown={(ev) =>
ev.button === 1 && window.open(url, "_blank")
}
onLoad={() =>
setLoading(ImageLoadingState.Loaded)
}
onError={() =>
setLoading(ImageLoadingState.Error)
}
/>
return (
<img
{...props}
src={url}
alt={attachment.filename}
loading="lazy"
className={classNames(styles.image, {
[styles.loading]: loading !== ImageLoadingState.Loaded,
})}
onClick={() =>
modalController.push({ type: "image_viewer", attachment })
}
onMouseDown={(ev) => ev.button === 1 && window.open(url, "_blank")}
onLoad={() => setLoading(ImageLoadingState.Loaded)}
onError={() => setLoading(ImageLoadingState.Error)}
/>
);
}
@@ -2,10 +2,8 @@ import { Reply } from "@styled-icons/boxicons-regular";
import { File } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { useHistory } from "react-router-dom";
import { RelationshipStatus } from "revolt-api/types/Users";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Message } from "revolt.js/dist/maps/Messages";
import styled, { css } from "styled-components";
import { Channel, Message, API } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { Text } from "preact-i18n";
import { useLayoutEffect, useState } from "preact/hooks";
@@ -18,7 +16,7 @@ import { SystemMessage } from "../SystemMessage";
interface Props {
parent_mentions: string[];
channel: Channel;
channel?: Channel;
index: number;
id: string;
}
@@ -28,30 +26,25 @@ export const ReplyBase = styled.div<{
fail?: boolean;
preview?: boolean;
}>`
gap: 4px;
gap: 8px;
min-width: 0;
display: flex;
margin-inline-start: 30px;
margin-inline-end: 12px;
font-size: 0.8em;
user-select: none;
align-items: center;
align-items: end;
color: var(--secondary-foreground);
/* nizune's Discord replies,
does not scale properly with messages,
reverted temporarily
&::before {
content: "";
flex-shrink: 0;
width: 22px;
height: 10px;
width: 28px;
margin-inline-end: 2px;
border-inline-start: 2px solid var(--message-box);
border-top: 2px solid var(--message-box);
align-self: flex-end;
display: flex;
border-top: 2.2px solid var(--tertiary-foreground);
border-inline-start: 2.2px solid var(--tertiary-foreground);
border-start-start-radius: 6px;
}*/
}
* {
overflow: hidden;
@@ -60,6 +53,7 @@ export const ReplyBase = styled.div<{
}
.user {
//margin-inline-start: 12px;
gap: 6px;
display: flex;
flex-shrink: 0;
@@ -74,13 +68,6 @@ export const ReplyBase = styled.div<{
text-decoration: underline;
}
}
/*&::before {
position:relative;
width: 50px;
height: 2px;
background: red;
}*/
}
.content {
@@ -98,6 +85,16 @@ export const ReplyBase = styled.div<{
transition: transform ease-in-out 0.1s;
filter: brightness(1);
> svg {
flex-shrink: 0;
}
> span > p {
display: flex;
align-items: center;
gap: 4px;
}
&:hover {
filter: brightness(2);
}
@@ -109,10 +106,6 @@ export const ReplyBase = styled.div<{
> * {
pointer-events: none;
}
/*> span > p {
display: flex;
}*/
}
> svg:first-child {
@@ -131,6 +124,10 @@ export const ReplyBase = styled.div<{
props.head &&
css`
margin-top: 12px;
&::before {
border-start-start-radius: 4px;
}
`}
${(props) =>
@@ -142,6 +139,8 @@ export const ReplyBase = styled.div<{
export const MessageReply = observer(
({ index, channel, id, parent_mentions }: Props) => {
if (!channel) return null;
const view = getRenderer(channel);
if (view.state !== "RENDER") return null;
@@ -171,8 +170,9 @@ export const MessageReply = observer(
return (
<ReplyBase head={index === 0}>
<Reply size={16} />
{message.author?.relationship === RelationshipStatus.Blocked ? (
{/*<Reply size={16} />*/}
{message.author?.relationship === "Blocked" ? (
<Text id="app.main.channel.misc.blocked_user" />
) : (
<>
@@ -182,7 +182,7 @@ export const MessageReply = observer(
<>
<div className="user">
<UserShort
size={16}
size={14}
showServerIdentity
user={message.author}
masquerade={message.masquerade!}
@@ -221,12 +221,15 @@ export const MessageReply = observer(
</em>
</>
)}
<Markdown
disallowBigEmoji
content={(
message.content as string
).replace(/\n/g, " ")}
/>
{message.content && (
<Markdown
disallowBigEmoji
content={message.content.replace(
/\n/g,
" ",
)}
/>
)}
</div>
</>
)}
@@ -0,0 +1,239 @@
import {
autoPlacement,
offset,
shift,
useFloating,
} from "@floating-ui/react-dom-interactions";
import { Plus } from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite";
import { Message } from "revolt.js";
import styled, { css } from "styled-components";
import { createPortal } from "preact/compat";
import { useCallback, useRef, useState } from "preact/hooks";
import { IconButton } from "@revoltchat/ui";
import { emojiDictionary } from "../../../../assets/emojis";
import { useClient } from "../../../../controllers/client/ClientController";
import { RenderEmoji } from "../../../markdown/plugins/emoji";
import { HackAlertThisFileWillBeReplaced } from "../MessageBox";
interface Props {
message: Message;
}
/**
* Reaction list element
*/
const List = styled.div`
gap: 0.4em;
display: flex;
flex-wrap: wrap;
margin-top: 0.2em;
align-items: center;
.add {
display: none;
}
&:hover .add {
display: grid;
}
`;
/**
* List divider
*/
const Divider = styled.div`
width: 1px;
height: 14px;
background: var(--tertiary-foreground);
`;
/**
* Reaction styling
*/
const Reaction = styled.div<{ active: boolean }>`
padding: 0.4em;
cursor: pointer;
user-select: none;
vertical-align: middle;
border: 1px solid transparent;
color: var(--secondary-foreground);
border-radius: var(--border-radius);
background: var(--secondary-background);
img {
width: 1.2em;
height: 1.2em;
object-fit: contain;
}
&:hover {
filter: brightness(0.9);
}
&:active {
filter: brightness(0.75);
}
${(props) =>
props.active &&
css`
border-color: var(--accent);
`}
`;
/**
* Render reactions on a message
*/
export const Reactions = observer(({ message }: Props) => {
const client = useClient();
const [showPicker, setPicker] = useState(false);
/**
* Render individual reaction entries
*/
const Entry = useCallback(
observer(({ id, user_ids }: { id: string; user_ids?: Set<string> }) => {
const active = user_ids?.has(client.user!._id) || false;
return (
<Reaction
active={active}
onClick={() =>
active ? message.unreact(id) : message.react(id)
}>
<RenderEmoji match={id} /> {user_ids?.size || 0}
</Reaction>
);
}),
[],
);
/**
* Determine two lists of 'required' and 'optional' reactions
*/
const { required, optional } = (() => {
const required = new Set<string>();
const optional = new Set<string>();
if (message.interactions?.reactions) {
for (const reaction of message.interactions.reactions) {
required.add(reaction);
}
}
for (const key of message.reactions.keys()) {
if (!required.has(key)) {
optional.add(key);
}
}
return {
required,
optional,
};
})();
// Don't render list if nothing is going to show anyways
if (required.size === 0 && optional.size === 0) return null;
return (
<List>
{Array.from(required, (id) => (
<Entry key={id} id={id} user_ids={message.reactions.get(id)} />
))}
{required.size !== 0 && optional.size !== 0 && <Divider />}
{Array.from(optional, (id) => (
<Entry key={id} id={id} user_ids={message.reactions.get(id)} />
))}
{message.channel?.havePermission("React") && (
<ReactionWrapper
message={message}
open={showPicker}
setOpen={setPicker}>
<IconButton className={showPicker ? "" : "add"}>
<Plus size={20} />
</IconButton>
</ReactionWrapper>
)}
</List>
);
});
const Base = styled.div`
> div {
position: unset;
}
`;
/**
* ! FIXME: rewrite
*/
export const ReactionWrapper: React.FC<{
message: Message;
open: boolean;
setOpen: (v: boolean) => void;
}> = ({ open, setOpen, message, children }) => {
const { x, y, reference, floating, strategy } = useFloating({
open,
middleware: [
offset(4),
shift({ mainAxis: true, crossAxis: true, padding: 4 }),
autoPlacement(),
],
});
const skip = useRef();
const toggle = () => {
if (skip.current) {
skip.current = null;
return;
}
setOpen(!open);
if (!open) {
skip.current = true;
}
};
return (
<>
<div
ref={reference}
onClick={toggle}
style={{ width: "fit-content" }}>
{children}
</div>
{createPortal(
<div id="reaction">
{open && (
<Base
ref={floating}
style={{
position: strategy,
top: y ?? 0,
left: x ?? 0,
}}>
<HackAlertThisFileWillBeReplaced
onSelect={(emoji) =>
message.react(
emojiDictionary[
emoji as keyof typeof emojiDictionary
] ?? emoji,
)
}
onClose={toggle}
/>
</Base>
)}
</div>,
document.body,
)}
</>
);
};
@@ -1,4 +1,4 @@
import styled from "styled-components";
import styled from "styled-components/macro";
import { Text } from "preact-i18n";
@@ -1,41 +1,33 @@
import axios from "axios";
import { Attachment } from "revolt-api/types/Autumn";
import { API } from "revolt.js";
import styles from "./Attachment.module.scss";
import { useContext, useEffect, useState } from "preact/hooks";
import { Text } from "preact-i18n";
import { useEffect, useState } from "preact/hooks";
import RequiresOnline from "../../../../context/revoltjs/RequiresOnline";
import {
AppContext,
StatusContext,
} from "../../../../context/revoltjs/RevoltClient";
import { Button, Preloader } from "@revoltchat/ui";
import Preloader from "../../../ui/Preloader";
import { useClient } from "../../../../controllers/client/ClientController";
import RequiresOnline from "../../../../controllers/client/jsx/RequiresOnline";
interface Props {
attachment: Attachment;
attachment: API.File;
}
const fileCache: { [key: string]: string } = {};
export default function TextFile({ attachment }: Props) {
const [gated, setGated] = useState(attachment.size > 100_000);
const [content, setContent] = useState<undefined | string>(undefined);
const [loading, setLoading] = useState(false);
const status = useContext(StatusContext);
const client = useContext(AppContext);
const client = useClient();
const url = client.generateFileURL(attachment)!;
useEffect(() => {
if (typeof content !== "undefined") return;
if (loading) return;
if (attachment.size > 100_000) {
setContent(
"This file is > 100 KB, for your sake I did not load it.\nSee tracking issue here for previews: https://github.com/revoltchat/revite/issues/35",
);
return;
}
if (gated) return;
setLoading(true);
@@ -60,13 +52,17 @@ export default function TextFile({ attachment }: Props) {
setLoading(false);
});
}
}, [content, loading, status, attachment._id, attachment.size, url]);
}, [content, loading, gated, attachment._id, attachment.size, url]);
return (
<div
className={styles.textContent}
data-loading={typeof content === "undefined"}>
{content ? (
{gated ? (
<Button palette="accent" onClick={() => setGated(false)}>
<Text id="app.main.channel.misc.load_file" />
</Button>
) : content ? (
<pre>
<code>{content}</code>
</pre>
@@ -1,6 +1,6 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { XCircle, Plus, Share, X, File } from "@styled-icons/boxicons-regular";
import styled from "styled-components";
import styled from "styled-components/macro";
import { Text } from "preact-i18n";
import { useEffect, useState } from "preact/hooks";
@@ -149,12 +149,12 @@ function FileEntry({
<EmptyEntry className="icon">
<File size={36} />
</EmptyEntry>
<div class="overlay">
<div className="overlay">
<XCircle size={36} />
</div>
</PreviewBox>
<span class="fn">{file.name}</span>
<span class="size">{determineFileSize(file.size)}</span>
<span className="fn">{file.name}</span>
<span className="size">{determineFileSize(file.size)}</span>
</Entry>
);
@@ -169,13 +169,18 @@ function FileEntry({
return (
<Entry className={index >= CAN_UPLOAD_AT_ONCE ? "fade" : ""}>
<PreviewBox onClick={remove}>
<img class="icon" src={url} alt={file.name} loading="eager" />
<div class="overlay">
<img
className="icon"
src={url}
alt={file.name}
loading="eager"
/>
<div className="overlay">
<XCircle size={36} />
</div>
</PreviewBox>
<span class="fn">{file.name}</span>
<span class="size">{determineFileSize(file.size)}</span>
<span className="fn">{file.name}</span>
<span className="size">{determineFileSize(file.size)}</span>
</Entry>
);
}
@@ -1,38 +1,121 @@
import { DownArrowAlt } from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite";
import { Channel } from "revolt.js/dist/maps/Channels";
import styled, { css } from "styled-components";
import { Channel } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { Text } from "preact-i18n";
import { internalEmit } from "../../../../lib/eventEmitter";
import { isTouchscreenDevice } from "../../../../lib/isTouchscreenDevice";
import { getRenderer } from "../../../../lib/renderer/Singleton";
const Bar = styled.div`
z-index: 10;
export const Bar = styled.div<{ position: "top" | "bottom"; accent?: boolean }>`
z-index: 1;
position: relative;
@keyframes bottomBounce {
0% {
transform: translateY(33px);
}
100% {
transform: translateY(0px);
}
}
@keyframes topBounce {
0% {
transform: translateY(-33px);
}
100% {
transform: translateY(0px);
}
}
${(props) =>
props.position === "top" &&
css`
top: 0;
animation: topBounce 340ms cubic-bezier(0.2, 0.9, 0.5, 1.16)
forwards;
`}
${(props) =>
props.position === "bottom" &&
css`
top: -28px;
animation: bottomBounce 340ms cubic-bezier(0.2, 0.9, 0.5, 1.16)
forwards;
${() =>
isTouchscreenDevice &&
css`
top: -90px;
`}
`}
> div {
top: -26px;
height: 28px;
width: 100%;
position: absolute;
display: flex;
align-items: center;
cursor: pointer;
font-size: 13px;
font-size: 12px;
font-weight: 600;
padding: 0 8px;
user-select: none;
justify-content: space-between;
color: var(--secondary-foreground);
transition: color ease-in-out 0.08s;
background: var(--secondary-background);
border-radius: var(--border-radius) var(--border-radius) 0 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
${(props) =>
props.accent
? css`
color: var(--accent-contrast);
background-color: rgba(
var(--accent-rgb),
max(var(--min-opacity), 0.9)
);
backdrop-filter: blur(20px);
`
: css`
color: var(--secondary-foreground);
background-color: rgba(
var(--secondary-background-rgb),
max(var(--min-opacity), 0.9)
);
backdrop-filter: blur(20px);
`}
${(props) =>
props.position === "top"
? css`
top: 48px;
border-radius: 0 0 var(--border-radius)
var(--border-radius);
`
: css`
border-radius: var(--border-radius) var(--border-radius) 0
0;
`}
${() =>
isTouchscreenDevice &&
css`
top: 56px;
`}
> div {
display: flex;
align-items: center;
gap: 6px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&:hover {
@@ -47,10 +130,15 @@ const Bar = styled.div`
isTouchscreenDevice &&
css`
height: 34px;
top: -32px;
padding: 0 12px;
`}
}
@media only screen and (max-width: 800px) {
.right > span {
display: none;
}
}
`;
export default observer(({ channel }: { channel: Channel }) => {
@@ -58,14 +146,20 @@ export default observer(({ channel }: { channel: Channel }) => {
if (renderer.state !== "RENDER" || renderer.atBottom) return null;
return (
<Bar>
<div onClick={() => renderer.jumpToBottom(true)}>
<Bar position="bottom">
<div
onClick={() => {
renderer.jumpToBottom(true);
internalEmit("NewMessages", "hide");
}}>
<div>
<Text id="app.main.channel.misc.viewing_old" />
</div>
<div>
<Text id="app.main.channel.misc.jump_present" />{" "}
<DownArrowAlt size={20} />
<div className="right">
<span>
<Text id="app.main.channel.misc.jump_present" />
</span>
<DownArrowAlt size={18} />
</div>
</div>
</Bar>
@@ -0,0 +1,237 @@
import { DotsVerticalRounded, LinkAlt } from "@styled-icons/boxicons-regular";
import {
Pencil,
Trash,
Share,
InfoSquare,
Notification,
HappyBeaming,
} from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { Message as MessageObject } from "revolt.js";
import styled from "styled-components";
import { openContextMenu } from "preact-context-menu";
import { useEffect, useState } from "preact/hooks";
import { internalEmit } from "../../../../lib/eventEmitter";
import { shiftKeyPressed } from "../../../../lib/modifiers";
import { getRenderer } from "../../../../lib/renderer/Singleton";
import { state } from "../../../../mobx/State";
import { QueuedMessage } from "../../../../mobx/stores/MessageQueue";
import { modalController } from "../../../../controllers/modals/ModalController";
import Tooltip from "../../../common/Tooltip";
import { ReactionWrapper } from "../attachments/Reactions";
interface Props {
reactionsOpen: boolean;
setReactionsOpen: (v: boolean) => void;
message: MessageObject;
queued?: QueuedMessage;
}
const OverlayBar = styled.div`
display: flex;
position: absolute;
justify-self: end;
align-self: end;
align-content: center;
justify-content: center;
right: 0;
top: -18px;
z-index: 0;
transition: box-shadow 0.1s ease-out;
border-radius: 5px;
background: var(--primary-header);
border: 1px solid var(--background);
&:hover {
box-shadow: rgb(0 0 0 / 20%) 0px 2px 10px;
}
`;
const Entry = styled.div`
height: 32px;
width: 32px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
cursor: pointer;
transition: 0.2s ease background-color;
color: var(--secondary-foreground);
&:hover {
background-color: var(--secondary-header);
color: var(--foreground);
}
&:focus {
border-radius: var(--border-radius);
box-shadow: 0 0 0 2.5pt var(--accent);
}
&:active {
svg {
transform: translateY(1px);
}
}
`;
const Divider = styled.div`
margin: 6px 4px;
width: 0.5px;
background: var(--tertiary-background);
`;
export const MessageOverlayBar = observer(
({ reactionsOpen, setReactionsOpen, message, queued }: Props) => {
const client = message.client;
const isAuthor = message.author_id === client.user!._id;
const [copied, setCopied] = useState<"link" | "id">(null!);
const [extraActions, setExtra] = useState(shiftKeyPressed);
useEffect(() => {
const handler = (ev: KeyboardEvent) => setExtra(ev.shiftKey);
document.addEventListener("keyup", handler);
document.addEventListener("keydown", handler);
return () => {
document.removeEventListener("keyup", handler);
document.removeEventListener("keydown", handler);
};
});
return (
<OverlayBar>
{message.channel?.havePermission("SendMessage") && (
<Tooltip content="Reply">
<Entry
onClick={() =>
internalEmit("ReplyBar", "add", message)
}>
<Share size={18} />
</Entry>
</Tooltip>
)}
{message.channel?.havePermission("React") &&
state.experiments.isEnabled("picker") && (
<ReactionWrapper
open={reactionsOpen}
setOpen={setReactionsOpen}
message={message}>
<Tooltip content="React">
<Entry>
<HappyBeaming size={18} />
</Entry>
</Tooltip>
</ReactionWrapper>
)}
{isAuthor && (
<Tooltip content="Edit">
<Entry
onClick={() =>
internalEmit(
"MessageRenderer",
"edit_message",
message._id,
)
}>
<Pencil size={18} />
</Entry>
</Tooltip>
)}
{isAuthor ||
(message.channel &&
message.channel.havePermission("ManageMessages")) ? (
<Tooltip content="Delete">
<Entry
onClick={(e) =>
e.shiftKey
? message.delete()
: modalController.push({
type: "delete_message",
target: message,
})
}>
<Trash size={18} color={"var(--error)"} />
</Entry>
</Tooltip>
) : undefined}
<Tooltip content="More">
<Entry
onClick={() =>
openContextMenu("Menu", {
message,
contextualChannel: message.channel_id,
queued,
})
}>
<DotsVerticalRounded size={18} />
</Entry>
</Tooltip>
{extraActions && (
<>
<Divider />
<Tooltip content="Mark as Unread">
<Entry
onClick={() => {
// ! FIXME: deduplicate this code with ctx menu
const messages = getRenderer(
message.channel!,
).messages;
const index = messages.findIndex(
(x) => x._id === message._id,
);
let unread_id = message._id;
if (index > 0) {
unread_id = messages[index - 1]._id;
}
internalEmit(
"NewMessages",
"mark",
unread_id,
);
message.channel?.ack(unread_id, true);
}}>
<Notification size={18} />
</Entry>
</Tooltip>
<Tooltip
content={
copied === "link" ? "Copied!" : "Copy Link"
}
hideOnClick={false}>
<Entry
onClick={() => {
setCopied("link");
modalController.writeText(message.url);
}}>
<LinkAlt size={18} />
</Entry>
</Tooltip>
<Tooltip
content={copied === "id" ? "Copied!" : "Copy ID"}
hideOnClick={false}>
<Entry
onClick={() => {
setCopied("id");
modalController.writeText(message._id);
}}>
<InfoSquare size={18} />
</Entry>
</Tooltip>
</>
)}
</OverlayBar>
);
},
);
@@ -0,0 +1,69 @@
import { UpArrowAlt } from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite";
import { useHistory } from "react-router-dom";
import { Channel } from "revolt.js";
import { decodeTime } from "ulid";
import { Text } from "preact-i18n";
import { useEffect, useState } from "preact/hooks";
import { internalSubscribe } from "../../../../lib/eventEmitter";
import { getRenderer } from "../../../../lib/renderer/Singleton";
import { dayjs } from "../../../../context/Locale";
import { Bar } from "./JumpToBottom";
export default observer(
({ channel, last_id }: { channel: Channel; last_id?: string }) => {
const [hidden, setHidden] = useState(false);
const hide = () => setHidden(true);
useEffect(() => setHidden(false), [last_id]);
useEffect(() => internalSubscribe("NewMessages", "hide", hide), []);
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) =>
e.key === "Escape" && hide();
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, []);
const renderer = getRenderer(channel);
const history = useHistory();
if (renderer.state !== "RENDER") return null;
if (!last_id) return null;
if (hidden) return null;
return (
<Bar position="top" accent>
<div
onClick={() => {
setHidden(true);
if (channel.channel_type === "TextChannel") {
history.push(
`/server/${channel.server_id}/channel/${channel._id}/${last_id}`,
);
} else {
history.push(`/channel/${channel._id}/${last_id}`);
}
}}>
<div>
<Text
id="app.main.channel.misc.new_messages"
fields={{
time_ago: dayjs(decodeTime(last_id)).fromNow(),
}}
/>
</div>
<div className="right">
<span>
<Text id="app.main.channel.misc.jump_beginning" />
</span>
<UpArrowAlt size={20} />
</div>
</div>
</Bar>
);
},
);
+106 -65
View File
@@ -1,20 +1,21 @@
import { At, Reply as ReplyIcon } from "@styled-icons/boxicons-regular";
import { At } from "@styled-icons/boxicons-regular";
import { File, XCircle } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Message } from "revolt.js/dist/maps/Messages";
import styled from "styled-components";
import { Channel, Message } from "revolt.js";
import styled from "styled-components/macro";
import { Text } from "preact-i18n";
import { StateUpdater, useEffect } from "preact/hooks";
import { IconButton } from "@revoltchat/ui";
import { internalSubscribe } from "../../../../lib/eventEmitter";
import { dispatch, getState } from "../../../../redux";
import { Reply } from "../../../../redux/reducers/queue";
import IconButton from "../../../ui/IconButton";
import { useApplicationState } from "../../../../mobx/State";
import { SECTION_MENTION } from "../../../../mobx/stores/Layout";
import { Reply } from "../../../../mobx/stores/MessageQueue";
import Tooltip from "../../../common/Tooltip";
import Markdown from "../../../markdown/Markdown";
import UserShort from "../../user/UserShort";
import { SystemMessage } from "../SystemMessage";
@@ -27,12 +28,22 @@ interface Props {
}
const Base = styled.div`
@keyframes bottomBounce {
0% {
transform: translateY(33px);
}
100% {
transform: translateY(0px);
}
}
display: flex;
height: 30px;
padding: 0 12px;
padding: 0 20px;
user-select: none;
align-items: center;
background: var(--message-box);
background: var(--secondary-background);
animation: bottomBounce 340ms cubic-bezier(0.2, 0.9, 0.5, 1.16) forwards;
> div {
flex-grow: 1;
@@ -48,21 +59,34 @@ const Base = styled.div`
display: flex;
font-size: 12px;
align-items: center;
font-weight: 600;
font-weight: 800;
text-transform: uppercase;
min-width: 6ch;
}
.username {
display: flex;
align-items: center;
gap: 6px;
font-weight: 600;
.replyto {
align-self: center;
font-weight: 500;
flex-shrink: 0;
}
.message {
.content {
display: flex;
max-height: 26px;
pointer-events: none;
.username {
display: flex;
align-items: center;
gap: 6px;
font-weight: 600;
flex-shrink: 0;
}
.message {
display: flex;
max-height: 26px;
gap: 4px;
}
}
.actions {
@@ -81,6 +105,7 @@ const Base = styled.div`
const MAX_REPLIES = 5;
export default observer(({ channel, replies, setReplies }: Props) => {
const client = channel.client;
const layout = useApplicationState().layout;
// Event listener for adding new messages to reply bar.
useEffect(() => {
@@ -99,7 +124,7 @@ export default observer(({ channel, replies, setReplies }: Props) => {
mention:
message.author_id === client.user!._id
? false
: getState().sectionToggle.mention ?? false,
: layout.getSectionState(SECTION_MENTION, false),
},
]);
});
@@ -127,42 +152,53 @@ export default observer(({ channel, replies, setReplies }: Props) => {
return (
<Base key={reply.id}>
<ReplyBase preview>
<ReplyIcon size={22} />
<div class="username">
<UserShort
size={16}
showServerIdentity
user={message.author}
masquerade={message.masquerade!}
/>
<div className="replyto">
<Text id="app.main.channel.reply.replying" />
</div>
<div class="message">
{message.attachments && (
<>
<File size={16} />
<em>
{message.attachments.length > 1 ? (
<Text id="app.main.channel.misc.sent_multiple_files" />
) : (
<Text id="app.main.channel.misc.sent_file" />
)}
</em>
</>
)}
{message.author_id ===
"00000000000000000000000000" ? (
<SystemMessage message={message} hideInfo />
) : (
<Markdown
disallowBigEmoji
content={(
message.content as string
).replace(/\n/g, " ")}
<div className="content">
<div className="username">
<UserShort
size={16}
showServerIdentity
user={message.author}
masquerade={message.masquerade!}
/>
)}
</div>
<div className="message">
{message.attachments && (
<>
<File size={16} />
<em>
{message.attachments.length >
1 ? (
<Text id="app.main.channel.misc.sent_multiple_files" />
) : (
<Text id="app.main.channel.misc.sent_file" />
)}
</em>
</>
)}
{message.author_id ===
"00000000000000000000000000" ? (
<SystemMessage
message={message}
hideInfo
/>
) : (
message.content && (
<Markdown
disallowBigEmoji
content={message.content.replace(
/\n/g,
" ",
)}
/>
)
)}
</div>
</div>
</ReplyBase>
<span class="actions">
<span className="actions">
{message.author_id !== client.user!._id && (
<IconButton
onClick={() => {
@@ -181,22 +217,27 @@ export default observer(({ channel, replies, setReplies }: Props) => {
}),
);
dispatch({
type: "SECTION_TOGGLE_SET",
id: "mention",
layout.setSectionState(
SECTION_MENTION,
state,
});
false,
);
}}>
<span class="toggle">
<At size={15} />
<Text
id={
reply.mention
? "general.on"
: "general.off"
}
/>
</span>
<Tooltip
content={
<Text id="app.main.channel.reply.toggle" />
}>
<span className="toggle">
<At size={15} />
<Text
id={
reply.mention
? "general.on"
: "general.off"
}
/>
</span>
</Tooltip>
</IconButton>
)}
<IconButton
@@ -1,7 +1,6 @@
import { observer } from "mobx-react-lite";
import { RelationshipStatus } from "revolt-api/types/Users";
import { Channel } from "revolt.js/dist/maps/Channels";
import styled from "styled-components";
import { Channel } from "revolt.js";
import styled from "styled-components/macro";
import { Text } from "preact-i18n";
@@ -13,10 +12,10 @@ const Base = styled.div`
position: relative;
> div {
height: 24px;
margin-top: -24px;
height: 26px;
top: -26px;
position: absolute;
font-size: 13px;
gap: 8px;
display: flex;
padding: 0 10px;
@@ -25,7 +24,11 @@ const Base = styled.div`
flex-direction: row;
width: calc(100% - var(--scrollbar-thickness));
color: var(--secondary-foreground);
background: var(--secondary-background);
background-color: rgba(
var(--secondary-background-rgb),
max(var(--min-opacity), 0.75)
);
backdrop-filter: blur(10px);
}
.avatars {
@@ -36,9 +39,12 @@ const Base = styled.div`
height: 16px;
object-fit: cover;
border-radius: var(--border-radius-half);
background: var(--secondary-background);
//background-clip: border-box;
border: 2px solid var(--secondary-background);
&:not(:first-child) {
margin-left: -4px;
margin-left: -6px;
}
}
}
@@ -49,6 +55,7 @@ const Base = styled.div`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
//font-weight: 600;
}
`;
@@ -57,7 +64,7 @@ export default observer(({ channel }: Props) => {
(x) =>
typeof x !== "undefined" &&
x._id !== x.client.user!._id &&
x.relationship !== RelationshipStatus.Blocked,
x.relationship !== "Blocked",
);
if (users.length > 0) {
@@ -4,6 +4,7 @@
iframe {
border: none;
border-radius: var(--border-radius);
width: 100%;
}
&.image {
@@ -12,7 +13,6 @@
&.website {
gap: 6px;
display: flex;
flex-direction: row;
> div:nth-child(1) {
+96 -45
View File
@@ -1,17 +1,18 @@
import { Embed as EmbedI } from "revolt-api/types/January";
import { API } from "revolt.js";
import styles from "./Embed.module.scss";
import classNames from "classnames";
import { useContext } from "preact/hooks";
import { useIntermediate } from "../../../../context/intermediate/Intermediate";
import { useClient } from "../../../../context/revoltjs/RevoltClient";
import { useClient } from "../../../../controllers/client/ClientController";
import { modalController } from "../../../../controllers/modals/ModalController";
import { MessageAreaWidthContext } from "../../../../pages/channels/messaging/MessageArea";
import Markdown from "../../../markdown/Markdown";
import Attachment from "../attachments/Attachment";
import EmbedMedia from "./EmbedMedia";
interface Props {
embed: EmbedI;
embed: API.Embed;
}
const MAX_EMBED_WIDTH = 480;
@@ -22,7 +23,6 @@ const MAX_PREVIEW_SIZE = 150;
export default function Embed({ embed }: Props) {
const client = useClient();
const { openScreen, openLink } = useIntermediate();
const maxWidth = Math.min(
useContext(MessageAreaWidthContext) - CONTAINER_PADDING,
MAX_EMBED_WIDTH,
@@ -45,39 +45,64 @@ export default function Embed({ embed }: Props) {
}
switch (embed.type) {
case "Text":
case "Website": {
// Determine special embed size.
let mw, mh;
const largeMedia =
(embed.special && embed.special.type !== "None") ||
embed.image?.size === "Large";
switch (embed.special?.type) {
case "YouTube":
case "Bandcamp": {
mw = embed.video?.width ?? 1280;
mh = embed.video?.height ?? 720;
break;
}
case "Twitch": {
mw = 1280;
mh = 720;
break;
}
default: {
if (embed.image?.size === "Preview") {
mw = MAX_EMBED_WIDTH;
mh = Math.min(
embed.image.height ?? 0,
MAX_PREVIEW_SIZE,
);
} else {
mw = embed.image?.width ?? MAX_EMBED_WIDTH;
mh = embed.image?.height ?? 0;
embed.type === "Text"
? typeof embed.media !== "undefined"
: (embed.special && embed.special.type !== "None") ||
embed.image?.size === "Large";
if (embed.type === "Text") {
mw = MAX_EMBED_WIDTH;
mh = 1;
} else {
switch (embed.special?.type) {
case "YouTube":
case "Bandcamp": {
mw = embed.video?.width ?? 1280;
mh = embed.video?.height ?? 720;
break;
}
case "Twitch":
case "Lightspeed":
case "Streamable": {
mw = 1280;
mh = 720;
break;
}
default: {
if (embed.image?.size === "Preview") {
mw = MAX_EMBED_WIDTH;
mh = Math.min(
embed.image.height ?? 0,
MAX_PREVIEW_SIZE,
);
} else {
mw = embed.image?.width ?? MAX_EMBED_WIDTH;
mh = embed.image?.height ?? 0;
}
}
}
}
const { width, height } = calculateSize(mw, mh);
if (embed.type === "Website" && embed.special?.type === "GIF") {
return (
<EmbedMedia
embed={embed}
width={
height *
((embed.image?.width ?? 0) /
(embed.image?.height ?? 0))
}
height={height}
/>
);
}
return (
<div
className={classNames(styles.embed, styles.website)}
@@ -87,7 +112,9 @@ export default function Embed({ embed }: Props) {
width: width + CONTAINER_PADDING,
}}>
<div>
{embed.site_name && (
{(embed.type === "Text"
? embed.title
: embed.site_name) && (
<div className={styles.siteinfo}>
{embed.icon_url && (
<img
@@ -102,35 +129,43 @@ export default function Embed({ embed }: Props) {
/>
)}
<div className={styles.site}>
{embed.site_name}{" "}
{embed.type === "Text"
? embed.title
: embed.site_name}{" "}
</div>
</div>
)}
{/*<span><a href={embed.url} target={"_blank"} className={styles.author}>Author</a></span>*/}
{embed.title && (
{embed.type === "Website" && embed.title && (
<span>
<a
onMouseDown={(ev) =>
(ev.button === 0 || ev.button === 1) &&
openLink(embed.url)
modalController.openLink(embed.url!)
}
className={styles.title}>
{embed.title}
</a>
</span>
)}
{embed.description && (
<div className={styles.description}>
{embed.description}
</div>
)}
{embed.description &&
(embed.type === "Text" ? (
<Markdown content={embed.description} />
) : (
<div className={styles.description}>
{embed.description}
</div>
))}
{largeMedia && (
<EmbedMedia embed={embed} height={height} />
)}
{largeMedia &&
(embed.type === "Text" ? (
<Attachment attachment={embed.media!} />
) : (
<EmbedMedia embed={embed} height={height} />
))}
</div>
{!largeMedia && (
{!largeMedia && embed.type === "Website" && (
<div>
<EmbedMedia
embed={embed}
@@ -155,8 +190,24 @@ export default function Embed({ embed }: Props) {
type="text/html"
frameBorder="0"
loading="lazy"
onClick={() => openScreen({ id: "image_viewer", embed })}
onMouseDown={(ev) => ev.button === 1 && openLink(embed.url)}
onClick={() =>
modalController.push({ type: "image_viewer", embed })
}
onMouseDown={(ev) =>
ev.button === 1 && modalController.openLink(embed.url)
}
/>
);
}
case "Video": {
return (
<video
className={classNames(styles.embed, styles.image)}
style={calculateSize(embed.width, embed.height)}
src={client.proxyFile(embed.url)}
frameBorder="0"
loading="lazy"
controls
/>
);
}
@@ -1,28 +1,23 @@
import { autorun } from "mobx";
import { Group } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { useHistory } from "react-router-dom";
import { RetrievedInvite } from "revolt-api/types/Invites";
import { Message } from "revolt.js/dist/maps/Messages";
import styled, { css } from "styled-components";
import { Message, API } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { useContext, useEffect, useState } from "preact/hooks";
import { defer } from "../../../../lib/defer";
import { Button, Category, Preloader } from "@revoltchat/ui";
import { isTouchscreenDevice } from "../../../../lib/isTouchscreenDevice";
import { dispatch } from "../../../../redux";
import {
AppContext,
ClientStatus,
StatusContext,
} from "../../../../context/revoltjs/RevoltClient";
import { takeError } from "../../../../context/revoltjs/util";
import { I18nError } from "../../../../context/Locale";
import ServerIcon from "../../../../components/common/ServerIcon";
import Button from "../../../../components/ui/Button";
import Overline from "../../../ui/Overline";
import Preloader from "../../../ui/Preloader";
import {
useClient,
useSession,
} from "../../../../controllers/client/ClientController";
import { takeError } from "../../../../controllers/client/jsx/error";
const EmbedInviteBase = styled.div`
width: 400px;
@@ -33,7 +28,7 @@ const EmbedInviteBase = styled.div`
align-items: center;
padding: 0 12px;
margin-top: 2px;
${() =>
${() =>
isTouchscreenDevice &&
css`
flex-wrap: wrap;
@@ -44,19 +39,17 @@ const EmbedInviteBase = styled.div`
> button {
width: 100%;
}
`
}
`}
`;
const EmbedInviteDetails = styled.div`
flex-grow: 1;
padding-left: 12px;
${() =>
padding-inline-start: 12px;
${() =>
isTouchscreenDevice &&
css`
width: calc(100% - 55px);
`
}
`}
`;
const EmbedInviteName = styled.div`
@@ -67,36 +60,44 @@ const EmbedInviteName = styled.div`
`;
const EmbedInviteMemberCount = styled.div`
display: flex;
align-items: center;
gap: 2px;
font-size: 0.8em;
> svg {
color: var(--secondary-foreground);
}
`;
type Props = {
code: string;
};
export function EmbedInvite(props: Props) {
export function EmbedInvite({ code }: Props) {
const history = useHistory();
const client = useContext(AppContext);
const status = useContext(StatusContext);
const code = props.code;
const session = useSession()!;
const client = session.client!;
const [processing, setProcessing] = useState(false);
const [error, setError] = useState<string | undefined>(undefined);
const [joinError, setJoinError] = useState<string | undefined>(undefined);
const [invite, setInvite] = useState<RetrievedInvite | undefined>(
undefined,
);
const [invite, setInvite] = useState<
(API.InviteResponse & { type: "Server" }) | undefined
>(undefined);
useEffect(() => {
if (
typeof invite === "undefined" &&
(status === ClientStatus.ONLINE || status === ClientStatus.READY)
(session.state === "Online" || session.state === "Ready")
) {
client
.fetchInvite(code)
.then((data) => setInvite(data))
.then((data) =>
setInvite(data as API.InviteResponse & { type: "Server" }),
)
.catch((err) => setError(takeError(err)));
}
}, [client, code, invite, status]);
}, [client, code, invite, session.state]);
if (typeof invite === "undefined") {
return error ? (
@@ -124,7 +125,9 @@ export function EmbedInvite(props: Props) {
<EmbedInviteDetails>
<EmbedInviteName>{invite.server_name}</EmbedInviteName>
<EmbedInviteMemberCount>
{invite.member_count.toLocaleString()} {invite.member_count === 1 ? "member" : "members"}
<Group size={12} />
{invite.member_count.toLocaleString()}{" "}
{invite.member_count === 1 ? "member" : "members"}
</EmbedInviteMemberCount>
</EmbedInviteDetails>
{processing ? (
@@ -134,49 +137,31 @@ export function EmbedInvite(props: Props) {
) : (
<Button
onClick={async () => {
setProcessing(true);
try {
setProcessing(true);
await client.joinInvite(invite);
if (invite.type === "Server") {
if (client.servers.get(invite.server_id)) {
history.push(
`/server/${invite.server_id}/channel/${invite.channel_id}`,
);
}
const dispose = autorun(() => {
const server = client.servers.get(
invite.server_id,
);
defer(() => {
if (server) {
dispatch({
type: "UNREADS_MARK_MULTIPLE_READ",
channels: server.channel_ids,
});
history.push(
`/server/${server._id}/channel/${invite.channel_id}`,
);
}
});
dispose();
});
}
await client.joinInvite(code);
history.push(
`/server/${invite.server_id}/channel/${invite.channel_id}`,
);
} catch (err) {
setJoinError(takeError(err));
} finally {
setProcessing(false);
}
}}>
{client.servers.get(invite.server_id) ? "Joined" : "Join"}
{client.servers.get(invite.server_id)
? "Joined"
: "Join"}
</Button>
)}
</EmbedInviteBase>
{joinError && <Overline type="error" error={joinError} />}
{joinError && (
<Category>
<I18nError error={joinError} />
</Category>
)}
</>
);
}
@@ -207,9 +192,12 @@ export default observer(({ message }: { message: Message }) => {
return (
<>
{entries.map((entry) => (
<EmbedInvite key={entry} code={entry} />
))}
{entries.map(
(entry) =>
entry !== "discover" && (
<EmbedInvite key={entry} code={entry} />
),
)}
</>
);
}
@@ -1,20 +1,19 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { Embed } from "revolt-api/types/January";
import { API } from "revolt.js";
import styles from "./Embed.module.scss";
import { useIntermediate } from "../../../../context/intermediate/Intermediate";
import { useClient } from "../../../../context/revoltjs/RevoltClient";
import { useClient } from "../../../../controllers/client/ClientController";
import { modalController } from "../../../../controllers/modals/ModalController";
interface Props {
embed: Embed;
embed: API.Embed;
width?: number;
height: number;
}
export default function EmbedMedia({ embed, width, height }: Props) {
if (embed.type !== "Website") return null;
const { openScreen } = useIntermediate();
const client = useClient();
switch (embed.special?.type) {
@@ -47,6 +46,17 @@ export default function EmbedMedia({ embed, width, height }: Props) {
style={{ height }}
/>
);
case "Lightspeed":
return (
<iframe
src={`https://new.lightspeed.tv/embed/${embed.special.id}/stream`}
frameBorder="0"
allowFullScreen
scrolling="no"
loading="lazy"
style={{ height }}
/>
);
case "Spotify":
return (
<iframe
@@ -82,19 +92,43 @@ export default function EmbedMedia({ embed, width, height }: Props) {
/>
);
}
case "Streamable": {
return (
<iframe
src={`https://streamable.com/e/${embed.special.id}?loop=0`}
seamless
loading="lazy"
style={{ height }}
/>
);
}
default: {
if (embed.image) {
if (embed.video) {
const url = embed.video.url;
return (
<video
loading="lazy"
className={styles.image}
style={{ width, height }}
src={client.proxyFile(url)}
loop={embed.special?.type === "GIF"}
controls={embed.special?.type !== "GIF"}
autoPlay={embed.special?.type === "GIF"}
muted={embed.special?.type === "GIF" ? true : undefined}
/>
);
} else if (embed.image) {
const url = embed.image.url;
return (
<img
className={styles.image}
src={client.proxyFile(url)}
loading="lazy"
style={{ width, height }}
style={{ width: "100%", height: "100%" }}
onClick={() =>
openScreen({
id: "image_viewer",
embed: embed.image,
modalController.push({
type: "image_viewer",
embed: embed.image!,
})
}
onMouseDown={(ev) =>
@@ -1,12 +1,12 @@
import { LinkExternal } from "@styled-icons/boxicons-regular";
import { EmbedImage } from "revolt-api/types/January";
import { API } from "revolt.js";
import styles from "./Embed.module.scss";
import IconButton from "../../../ui/IconButton";
import { IconButton } from "@revoltchat/ui";
interface Props {
embed: EmbedImage;
embed: API.Image;
}
export default function EmbedMediaActions({ embed }: Props) {
@@ -20,7 +20,7 @@ export default function EmbedMediaActions({ embed }: Props) {
</span>
<a
href={embed.url}
class={styles.openIcon}
className={styles.openIcon}
target="_blank"
rel="noreferrer">
<IconButton>
+22 -2
View File
@@ -1,11 +1,24 @@
import { Shield } from "@styled-icons/boxicons-regular";
import { Badges } from "revolt-api/types/Users";
import styled from "styled-components";
import styled from "styled-components/macro";
import { Localizer, Text } from "preact-i18n";
import Tooltip from "../Tooltip";
enum Badges {
Developer = 1,
Translator = 2,
Supporter = 4,
ResponsibleDisclosure = 8,
Founder = 16,
PlatformModeration = 32,
ActiveSupporter = 64,
Paw = 128,
EarlyAdopter = 256,
ReservedRelevantJokeBadge1 = 512,
ReservedRelevantJokeBadge2 = 1024,
}
const BadgesBase = styled.div`
gap: 8px;
display: flex;
@@ -123,6 +136,13 @@ export default function UserBadges({ badges, uid }: Props) {
) : (
<></>
)}
{badges & Badges.ReservedRelevantJokeBadge2 ? (
<Tooltip content="It's Morbin Time">
<img src="/assets/badges/amorbus.svg" />
</Tooltip>
) : (
<></>
)}
{badges & Badges.Paw ? (
<Tooltip content="🦊">
<img src="/assets/badges/paw.svg" />
+14 -7
View File
@@ -1,17 +1,24 @@
import { User } from "revolt.js/dist/maps/Users";
import { User } from "revolt.js";
import Checkbox, { CheckboxProps } from "../../ui/Checkbox";
import { Checkbox, Row, Column } from "@revoltchat/ui";
import UserIcon from "./UserIcon";
import { Username } from "./UserShort";
type UserProps = Omit<CheckboxProps, "children"> & { user: User };
type UserProps = { value: boolean; onChange: (v: boolean) => void; user: User };
export default function UserCheckbox({ user, ...props }: UserProps) {
return (
<Checkbox {...props}>
<UserIcon target={user} size={32} />
<Username user={user} />
</Checkbox>
<Checkbox
{...props}
title={
<Row centred>
<UserIcon target={user} size={32} />
<Column centred>
<Username user={user} />
</Column>
</Row>
}
/>
);
}
+9 -11
View File
@@ -1,19 +1,17 @@
import { Cog } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { Link } from "react-router-dom";
import { User } from "revolt.js/dist/maps/Users";
import styled from "styled-components";
import { User } from "revolt.js";
import styled from "styled-components/macro";
import { openContextMenu } from "preact-context-menu";
import { Text, Localizer } from "preact-i18n";
import { Header, IconButton } from "@revoltchat/ui";
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
import { useIntermediate } from "../../../context/intermediate/Intermediate";
import Header from "../../ui/Header";
import IconButton from "../../ui/IconButton";
import { modalController } from "../../../controllers/modals/ModalController";
import Tooltip from "../Tooltip";
import UserStatus from "./UserStatus";
@@ -49,16 +47,16 @@ interface Props {
}
export default observer(({ user }: Props) => {
const { writeClipboard } = useIntermediate();
return (
<Header borders placement="secondary">
<Header topBorder palette="secondary">
<HeaderBase>
<Localizer>
<Tooltip content={<Text id="app.special.copy_username" />}>
<span
className="username"
onClick={() => writeClipboard(user.username)}>
onClick={() =>
modalController.writeText(user.username)
}>
@{user.username}
</span>
</Tooltip>
+3 -7
View File
@@ -1,7 +1,6 @@
import { User } from "revolt.js/dist/maps/Users";
import styled from "styled-components";
import { User } from "revolt.js";
import styled from "styled-components/macro";
import { Children } from "../../../types/Preact";
import Tooltip from "../Tooltip";
import { Username } from "./UserShort";
import UserStatus from "./UserStatus";
@@ -42,10 +41,7 @@ export default function UserHover({ user, children }: Props) {
placement="right-end"
content={
<Base>
<Username
className="username"
user={user}
/>
<Username className="username" user={user} />
<span className="status">
<UserStatus user={user} />
</span>
+21 -27
View File
@@ -1,40 +1,36 @@
import { MicrophoneOff } from "@styled-icons/boxicons-regular";
import { VolumeMute } from "@styled-icons/boxicons-solid";
import { VolumeMute, MicrophoneOff } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { useParams } from "react-router-dom";
import { Masquerade } from "revolt-api/types/Channels";
import { Presence } from "revolt-api/types/Users";
import { User } from "revolt.js/dist/maps/Users";
import { Nullable } from "revolt.js/dist/util/null";
import styled, { css } from "styled-components";
import { User, API } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { useContext } from "preact/hooks";
import { ThemeContext } from "../../../context/Theme";
import { useClient } from "../../../context/revoltjs/RevoltClient";
import { useApplicationState } from "../../../mobx/State";
import fallback from "../assets/user.png";
import { useClient } from "../../../controllers/client/ClientController";
import IconBase, { IconBaseProps } from "../IconBase";
type VoiceStatus = "muted" | "deaf";
interface Props extends IconBaseProps<User> {
status?: boolean;
voice?: VoiceStatus;
masquerade?: Masquerade;
masquerade?: API.Masquerade;
showServerIdentity?: boolean;
}
export function useStatusColour(user?: User) {
const theme = useContext(ThemeContext);
const theme = useApplicationState().settings.theme;
return user?.online && user?.status?.presence !== Presence.Invisible
? user?.status?.presence === Presence.Idle
? theme["status-away"]
: user?.status?.presence === Presence.Busy
? theme["status-busy"]
: theme["status-online"]
: theme["status-invisible"];
return user?.online && user?.status?.presence !== "Invisible"
? user?.status?.presence === "Idle"
? theme.getVariable("status-away")
: user?.status?.presence === "Focus"
? theme.getVariable("status-focus")
: user?.status?.presence === "Busy"
? theme.getVariable("status-busy")
: theme.getVariable("status-online")
: theme.getVariable("status-invisible");
}
const VoiceIndicator = styled.div<{ status: VoiceStatus }>`
@@ -46,10 +42,6 @@ const VoiceIndicator = styled.div<{ status: VoiceStatus }>`
align-items: center;
justify-content: center;
svg {
stroke: white;
}
${(props) =>
(props.status === "muted" || props.status === "deaf") &&
css`
@@ -77,12 +69,13 @@ export default observer(
hover,
showServerIdentity,
masquerade,
innerRef,
...svgProps
} = props;
let { url } = props;
if (masquerade?.avatar) {
url = masquerade.avatar;
url = client.proxyFile(masquerade.avatar);
} else if (!url) {
let override;
if (target && showServerIdentity) {
@@ -101,7 +94,7 @@ export default observer(
url =
client.generateFileURL(
override ?? target?.avatar ?? attachment,
override ?? target?.avatar ?? attachment ?? undefined,
{ max_side: 256 },
animate,
) ?? (target ? target.defaultAvatarURL : fallback);
@@ -110,6 +103,7 @@ export default observer(
return (
<IconBase
{...svgProps}
ref={innerRef}
width={size}
height={size}
hover={hover}
@@ -121,7 +115,7 @@ export default observer(
y="0"
width="32"
height="32"
class="icon"
className="icon"
mask={mask ?? (status ? "url(#user)" : undefined)}>
{<img src={url} draggable={false} loading="lazy" />}
</foreignObject>
+81 -38
View File
@@ -1,17 +1,19 @@
import { TimeFive } from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite";
import { useParams } from "react-router-dom";
import { Masquerade } from "revolt-api/types/Channels";
import { User } from "revolt.js/dist/maps/Users";
import { Nullable } from "revolt.js/dist/util/null";
import styled from "styled-components";
import { User, API } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { Ref } from "preact";
import { Text } from "preact-i18n";
import { internalEmit } from "../../../lib/eventEmitter";
import { useIntermediate } from "../../../context/intermediate/Intermediate";
import { useClient } from "../../../context/revoltjs/RevoltClient";
import { dayjs } from "../../../context/Locale";
import { useClient } from "../../../controllers/client/ClientController";
import { modalController } from "../../../controllers/modals/ModalController";
import Tooltip from "../Tooltip";
import UserIcon from "./UserIcon";
const BotBadge = styled.div`
@@ -21,31 +23,52 @@ const BotBadge = styled.div`
padding: 0 4px;
font-size: 0.6em;
user-select: none;
margin-inline-start: 2px;
margin-inline-start: 4px;
text-transform: uppercase;
color: var(--foreground);
color: var(--accent-contrast);
background: var(--accent);
border-radius: calc(var(--border-radius) / 2);
`;
type UsernameProps = JSX.HTMLAttributes<HTMLElement> & {
type UsernameProps = Omit<
JSX.HTMLAttributes<HTMLElement>,
"children" | "as"
> & {
user?: User;
prefixAt?: boolean;
masquerade?: Masquerade;
masquerade?: API.Masquerade;
showServerIdentity?: boolean | "both";
innerRef?: Ref<any>;
};
const Name = styled.span<{ colour?: string | null }>`
${(props) =>
props.colour &&
(props.colour.includes("gradient")
? css`
background: ${props.colour};
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
`
: css`
color: ${props.colour};
`)}
`;
export const Username = observer(
({
user,
prefixAt,
masquerade,
showServerIdentity,
innerRef,
...otherProps
}: UsernameProps) => {
let username = user?.username;
let color;
let color = masquerade?.colour;
let timed_out: Date | undefined;
if (user && showServerIdentity) {
const { server } = useParams<{ server?: string }>();
@@ -65,15 +88,14 @@ export const Username = observer(
}
}
if (member.roles && member.roles.length > 0) {
const srv = client.servers.get(member._id.server);
if (srv?.roles) {
for (const role of member.roles) {
const c = srv.roles[role].colour;
if (c) {
color = c;
continue;
}
if (member.timeout) {
timed_out = member.timeout;
}
if (!color) {
for (const [_, { colour }] of member.orderedRoles) {
if (colour) {
color = colour;
}
}
}
@@ -81,29 +103,50 @@ export const Username = observer(
}
}
const el = (
<>
<Name {...otherProps} ref={innerRef} colour={color}>
{prefixAt ? "@" : undefined}
{masquerade?.name ?? username ?? (
<Text id="app.main.channel.unknown_user" />
)}
</Name>
{timed_out && (
<Tooltip
content={
<Text
id="app.main.channel.user_timed_out"
fields={{
time: dayjs(timed_out).fromNow(true),
}}
/>
}>
<TimeFive
size={16}
color="var(--secondary-foreground)"
/>
</Tooltip>
)}
</>
);
if (user?.bot) {
return (
<>
<span {...otherProps} style={{ color }}>
{masquerade?.name ?? username ?? (
<Text id="app.main.channel.unknown_user" />
)}
</span>
{el}
<BotBadge>
<Text id="app.main.channel.bot" />
{masquerade ? (
<Text id="app.main.channel.bridge" />
) : (
<Text id="app.main.channel.bot" />
)}
</BotBadge>
</>
);
}
return (
<span {...otherProps} style={{ color }}>
{prefixAt ? "@" : undefined}
{masquerade?.name ?? username ?? (
<Text id="app.main.channel.unknown_user" />
)}
</span>
);
return el;
},
);
@@ -117,12 +160,12 @@ export default function UserShort({
user?: User;
size?: number;
prefixAt?: boolean;
masquerade?: Masquerade;
masquerade?: API.Masquerade;
showServerIdentity?: boolean;
}) {
const { openScreen } = useIntermediate();
const openProfile = () =>
user && openScreen({ id: "profile", user_id: user._id });
user &&
modalController.push({ type: "user_profile", user_id: user._id });
const handleUserClick = (e: MouseEvent) => {
if (e.shiftKey && user?._id) {
+8 -5
View File
@@ -1,6 +1,5 @@
import { observer } from "mobx-react-lite";
import { Presence } from "revolt-api/types/Users";
import { User } from "revolt.js/dist/maps/Users";
import { User, API } from "revolt.js";
import { Text } from "preact-i18n";
@@ -25,15 +24,19 @@ export default observer(({ user, tooltip }: Props) => {
return <>{user.status.text}</>;
}
if (user.status?.presence === Presence.Busy) {
if (user.status?.presence === "Busy") {
return <Text id="app.status.busy" />;
}
if (user.status?.presence === Presence.Idle) {
if (user.status?.presence === "Idle") {
return <Text id="app.status.idle" />;
}
if (user.status?.presence === Presence.Invisible) {
if (user.status?.presence === "Focus") {
return <Text id="app.status.focus" />;
}
if (user.status?.presence === "Invisible") {
return <Text id="app.status.offline" />;
}
@@ -1,214 +0,0 @@
.markdown {
:global(.emoji) {
height: 1.25em;
width: 1.25em;
margin: 0 0.05em 0 0.1em;
vertical-align: -0.2em;
}
&[data-large-emojis="true"] :global(.emoji) {
width: 3rem;
height: 3rem;
margin-bottom: 0;
margin-top: 1px;
margin-right: 2px;
vertical-align: -0.3em;
}
p,
pre {
margin: 0;
}
a {
text-decoration: none;
&[data-type="mention"] {
padding: 0 6px;
font-weight: 600;
display: inline-block;
background: var(--secondary-background);
border-radius: calc(var(--border-radius) * 2);
&:hover {
text-decoration: none;
}
}
&:hover {
text-decoration: underline;
}
}
h1,
h2,
h3,
h4,
h5,
h6,
ul,
ol,
blockquote {
margin: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
&:not(:first-child) {
margin-top: 12px;
}
}
ul,
ol {
list-style-position: inside;
padding-left: 10px;
}
blockquote {
margin: 2px 0;
padding: 2px 0;
background: var(--hover);
border-radius: var(--border-radius);
border-inline-start: 4px solid var(--tertiary-background);
> * {
margin: 0 8px;
}
}
pre {
padding: 1em;
overflow-x: scroll;
border-radius: var(--border-radius);
background: var(--block) !important;
}
p > code {
padding: 1px 4px;
}
code {
color: white;
font-size: 90%;
background: var(--block);
border-radius: var(--border-radius);
font-family: var(--monospace-font), monospace;
border-radius: 3px;
-webkit-box-decoration-break: clone;
}
input[type="checkbox"] {
margin-right: 4px;
pointer-events: none;
}
table {
border-collapse: collapse;
th,
td {
padding: 6px;
border: 1px solid var(--tertiary-foreground);
}
}
:global(.katex-block) {
overflow-x: auto;
}
:global(.spoiler) {
padding: 0 2px;
cursor: pointer;
user-select: none;
color: transparent;
background: #151515;
border-radius: var(--border-radius);
> * {
opacity: 0;
pointer-events: none;
}
&:global(.shown) {
cursor: auto;
user-select: all;
color: var(--foreground);
background: var(--secondary-background);
> * {
opacity: 1;
pointer-events: unset;
}
}
}
:global(.code) {
font-family: var(--monospace-font), monospace;
:global(.lang) {
width: fit-content;
padding-bottom: 8px;
div {
color: #111;
cursor: pointer;
padding: 2px 6px;
font-weight: 600;
user-select: none;
display: inline-block;
background: var(--accent);
font-size: 10px;
text-transform: uppercase;
box-shadow: 0 2px #787676;
border-radius: calc(var(--border-radius) / 3);
&:active {
transform: translateY(1px);
box-shadow: 0 1px #787676;
}
}
}
}
input[type="checkbox"] {
width: 0;
opacity: 0;
pointer-events: none;
}
label {
pointer-events: none;
}
input[type="checkbox"] + label:before {
width: 12px;
height: 12px;
content: "a";
font-size: 10px;
margin-right: 6px;
line-height: 12px;
background: white;
position: relative;
display: inline-block;
border-radius: var(--border-radius);
}
input[type="checkbox"][checked="true"] + label:before {
content: "";
align-items: center;
display: inline-flex;
justify-content: center;
background: var(--accent);
}
input[type="checkbox"] + label {
line-height: 12px;
position: relative;
}
}
+4 -2
View File
@@ -1,13 +1,15 @@
import { Suspense, lazy } from "preact/compat";
const Renderer = lazy(() => import("./Renderer"));
const Renderer = lazy(() => import("./RemarkRenderer"));
export interface MarkdownProps {
content?: string;
content: string;
disallowBigEmoji?: boolean;
}
export default function Markdown(props: MarkdownProps) {
if (!props.content) return null;
return (
// @ts-expect-error Typings mis-match.
<Suspense fallback={props.content}>
+258
View File
@@ -0,0 +1,258 @@
import "katex/dist/katex.min.css";
import rehypeKatex from "rehype-katex";
import rehypePrism from "rehype-prism";
import rehypeReact from "rehype-react";
import remarkBreaks from "remark-breaks";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import styled, { css } from "styled-components";
import { unified } from "unified";
import { createElement } from "preact";
import { memo } from "preact/compat";
import { useLayoutEffect, useMemo, useState } from "preact/hooks";
import { MarkdownProps } from "./Markdown";
import { handlers } from "./hast";
import { RenderCodeblock } from "./plugins/Codeblock";
import { RenderAnchor } from "./plugins/anchors";
import { remarkChannels, RenderChannel } from "./plugins/channels";
import { isOnlyEmoji, remarkEmoji, RenderEmoji } from "./plugins/emoji";
import { remarkHtmlToText } from "./plugins/htmlToText";
import { remarkMention, RenderMention } from "./plugins/mentions";
import { remarkSpoiler, RenderSpoiler } from "./plugins/spoiler";
import { remarkTimestamps } from "./plugins/timestamps";
import "./prism";
/**
* Null element
*/
const Null: React.FC = () => null;
/**
* Custom Markdown components
*/
const components = {
emoji: RenderEmoji,
mention: RenderMention,
spoiler: RenderSpoiler,
channel: RenderChannel,
a: RenderAnchor,
p: styled.p`
margin: 0;
> code {
padding: 1px 4px;
flex-shrink: 0;
}
`,
h1: styled.h1`
margin: 0.2em 0;
`,
h2: styled.h2`
margin: 0.2em 0;
`,
h3: styled.h3`
margin: 0.2em 0;
`,
h4: styled.h4`
margin: 0.2em 0;
`,
h5: styled.h5`
margin: 0.2em 0;
`,
h6: styled.h6`
margin: 0.2em 0;
`,
pre: RenderCodeblock,
code: styled.code`
color: white;
background: var(--block);
font-size: 90%;
font-family: var(--monospace-font), monospace;
border-radius: 3px;
box-decoration-break: clone;
`,
table: styled.table`
border-collapse: collapse;
th,
td {
padding: 6px;
border: 1px solid var(--tertiary-foreground);
}
`,
ul: styled.ul`
list-style-position: inside;
padding-left: 10px;
margin: 0.2em 0;
`,
ol: styled.ol`
list-style-position: inside;
padding-left: 10px;
margin: 0.2em 0;
`,
li: styled.li`
${(props) =>
props.class === "task-list-item" &&
css`
list-style-type: none;
`}
`,
blockquote: styled.blockquote`
margin: 2px 0;
padding: 2px 0;
background: var(--hover);
border-radius: var(--border-radius);
border-inline-start: 4px solid var(--tertiary-background);
> * {
margin: 0 8px;
}
`,
// Block image elements
img: Null,
// Catch literally everything else just in case
video: Null,
figure: Null,
picture: Null,
source: Null,
audio: Null,
script: Null,
style: Null,
};
/**
* Unified Markdown renderer
*/
const render = unified()
.use(remarkParse)
.use(remarkBreaks)
.use(remarkGfm)
.use(remarkMath)
.use(remarkSpoiler)
.use(remarkChannels)
.use(remarkTimestamps)
.use(remarkEmoji)
.use(remarkMention)
.use(remarkHtmlToText)
.use(remarkRehype, {
handlers,
})
.use(rehypeKatex, {
maxSize: 10,
maxExpand: 0,
trust: false,
strict: false,
output: "html",
throwOnError: false,
errorColor: "var(--error)",
})
.use(rehypePrism)
// @ts-expect-error typings do not
// match between Preact and React
.use(rehypeReact, {
createElement,
Fragment,
components,
});
/**
* Markdown parent container
*/
const Container = styled.div<{ largeEmoji: boolean }>`
// Allow scrolling block math
.math-display {
overflow-x: auto;
}
// Set emoji size
--emoji-size: ${(props) => (props.largeEmoji ? "3em" : "1.25em")};
// Underline link hover
a:hover {
text-decoration: underline;
}
`;
/**
* Regex for matching execessive recursion of blockquotes and lists
*/
const RE_RECURSIVE = /(^(?:[>*+-][^\S\r\n]*){5})(?:[>*+-][^\S\r\n]*)+(.*$)/gm;
/**
* Regex for matching multi-line blockquotes
*/
const RE_BLOCKQUOTE = /^([^\S\r\n]*>[^\n]+\n?)+/gm;
/**
* Regex for matching HTML tags
*/
const RE_HTML_TAGS = /^(<\/?[a-zA-Z0-9]+>)(.*$)/gm;
/**
* Regex for matching empty lines
*/
const RE_EMPTY_LINE = /^\s*?$/gm;
/**
* Regex for matching line starting with plus
*/
const RE_PLUS = /^\s*\+(?:$|[^+])/gm;
/**
* Sanitise Markdown input before rendering
* @param content Input string
* @returns Sanitised string
*/
function sanitise(content: string) {
return (
content
// Strip excessive blockquote or list indentation
.replace(RE_RECURSIVE, (_, m0, m1) => m0 + m1)
// Append empty character if string starts with html tag
// This is to avoid inconsistencies in rendering Markdown inside/after HTML tags
// https://github.com/revoltchat/revite/issues/733
.replace(RE_HTML_TAGS, (match) => `\u200E${match}`)
// Append empty character if line starts with a plus
// which would usually open a new list but we want
// to avoid that behaviour in our case.
.replace(RE_PLUS, (match) => `\u200E${match}`)
// Replace empty lines with non-breaking space
// because remark renderer is collapsing empty
// or otherwise whitespace-only lines of text
.replace(RE_EMPTY_LINE, "")
// Ensure empty line after blockquotes for correct rendering
.replace(RE_BLOCKQUOTE, (match) => `${match}\n`)
);
}
/**
* Remark renderer component
*/
export default memo(({ content, disallowBigEmoji }: MarkdownProps) => {
const sanitisedContent = useMemo(() => sanitise(content), [content]);
const [Content, setContent] = useState<React.ReactElement>(null!);
useLayoutEffect(() => {
render
.process(sanitisedContent)
.then((file) => setContent(file.result));
}, [sanitisedContent]);
const largeEmoji = useMemo(
() => !disallowBigEmoji && isOnlyEmoji(content!),
[content, disallowBigEmoji],
);
return <Container largeEmoji={largeEmoji}>{Content}</Container>;
});
-269
View File
@@ -1,269 +0,0 @@
/* eslint-disable react-hooks/rules-of-hooks */
import MarkdownKatex from "@traptitech/markdown-it-katex";
import MarkdownSpoilers from "@traptitech/markdown-it-spoiler";
import "katex/dist/katex.min.css";
import MarkdownIt from "markdown-it";
// @ts-expect-error No typings.
import MarkdownEmoji from "markdown-it-emoji/dist/markdown-it-emoji-bare";
// @ts-expect-error No typings.
import MarkdownSub from "markdown-it-sub";
// @ts-expect-error No typings.
import MarkdownSup from "markdown-it-sup";
import Prism from "prismjs";
import "prismjs/themes/prism-tomorrow.css";
import { RE_MENTIONS } from "revolt.js";
import styles from "./Markdown.module.scss";
import { useCallback, useContext } from "preact/hooks";
import { internalEmit } from "../../lib/eventEmitter";
import { determineLink } from "../../lib/links";
import { useIntermediate } from "../../context/intermediate/Intermediate";
import { AppContext } from "../../context/revoltjs/RevoltClient";
import { generateEmoji } from "../common/Emoji";
import { emojiDictionary } from "../../assets/emojis";
import { MarkdownProps } from "./Markdown";
// TODO: global.d.ts file for defining globals
declare global {
interface Window {
copycode: (element: HTMLDivElement) => void;
}
}
// Handler for code block copy.
if (typeof window !== "undefined") {
window.copycode = function (element: HTMLDivElement) {
try {
const code = element.parentElement?.parentElement?.children[1];
if (code) {
navigator.clipboard.writeText(code.textContent?.trim() ?? "");
}
} catch (e) {}
};
}
export const md: MarkdownIt = MarkdownIt({
breaks: true,
linkify: true,
highlight: (str, lang) => {
const v = Prism.languages[lang];
if (v) {
const out = Prism.highlight(str, v, lang);
return `<pre class="code"><div class="lang"><div onclick="copycode(this)">${lang}</div></div><code class="language-${lang}">${out}</code></pre>`;
}
return `<pre class="code"><code>${md.utils.escapeHtml(
str,
)}</code></pre>`;
},
})
.disable("image")
.use(MarkdownEmoji, { defs: emojiDictionary })
.use(MarkdownSpoilers)
.use(MarkdownSup)
.use(MarkdownSub)
.use(MarkdownKatex, {
throwOnError: false,
maxExpand: 0,
maxSize: 10,
strict: false,
errorColor: "var(--error)",
});
md.linkify.set({ fuzzyLink: false });
// TODO: global.d.ts file for defining globals
declare global {
interface Window {
internalHandleURL: (element: HTMLAnchorElement) => void;
}
}
// Include emojis.
md.renderer.rules.emoji = function (token, idx) {
return generateEmoji(token[idx].content);
};
// Force line breaks.
// https://github.com/markdown-it/markdown-it/issues/211#issuecomment-508380611
const defaultParagraphRenderer =
md.renderer.rules.paragraph_open ||
((tokens, idx, options, env, self) =>
self.renderToken(tokens, idx, options));
md.renderer.rules.paragraph_open = function (tokens, idx, options, env, self) {
let result = "";
if (idx > 1) {
const inline = tokens[idx - 2];
const paragraph = tokens[idx];
if (
inline.type === "inline" &&
inline.map &&
inline.map[1] &&
paragraph.map &&
paragraph.map[0]
) {
const diff = paragraph.map[0] - inline.map[1];
if (diff > 0) {
result = "<br>".repeat(diff);
}
}
}
return result + defaultParagraphRenderer(tokens, idx, options, env, self);
};
const RE_TWEMOJI = /:(\w+):/g;
// ! FIXME: Move to library
const RE_CHANNELS = /<#([A-z0-9]{26})>/g;
export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
const client = useContext(AppContext);
const { openLink } = useIntermediate();
if (typeof content === "undefined") return null;
if (content.length === 0) return null;
// We replace the message with the mention at the time of render.
// We don't care if the mention changes.
const newContent = content
.replace(RE_MENTIONS, (sub: string, ...args: unknown[]) => {
const id = args[0] as string,
user = client.users.get(id);
if (user) {
return `[@${user.username}](/@${id})`;
}
return sub;
})
.replace(RE_CHANNELS, (sub: string, ...args: unknown[]) => {
const id = args[0] as string,
channel = client.channels.get(id);
if (channel?.channel_type === "TextChannel") {
return `[#${channel.name}](/server/${channel.server_id}/channel/${id})`;
}
return sub;
});
const useLargeEmojis = disallowBigEmoji
? false
: content.replace(RE_TWEMOJI, "").trim().length === 0;
const toggle = useCallback((ev: MouseEvent) => {
if (ev.currentTarget) {
const element = ev.currentTarget as HTMLDivElement;
if (element.classList.contains("spoiler")) {
element.classList.add("shown");
}
}
}, []);
const handleLink = useCallback(
(ev: MouseEvent) => {
if (ev.currentTarget) {
const element = ev.currentTarget as HTMLAnchorElement;
if (ev.shiftKey) {
switch (element.dataset.type) {
case "mention": {
internalEmit(
"MessageBox",
"append",
`<@${element.dataset.mentionId}>`,
"mention",
);
ev.preventDefault();
return;
}
case "channel_mention": {
internalEmit(
"MessageBox",
"append",
`<#${element.dataset.mentionId}>`,
"channel_mention",
);
ev.preventDefault();
return;
}
}
}
if (openLink(element.href)) {
ev.preventDefault();
}
}
},
[openLink],
);
return (
<span
ref={(el) => {
if (el) {
el.querySelectorAll<HTMLDivElement>(".spoiler").forEach(
(element) => {
element.removeEventListener("click", toggle);
element.addEventListener("click", toggle);
},
);
el.querySelectorAll<HTMLAnchorElement>("a").forEach(
(element) => {
element.removeEventListener("click", handleLink);
element.addEventListener("click", handleLink);
element.removeAttribute("data-type");
element.removeAttribute("data-mention-id");
element.removeAttribute("target");
const link = determineLink(element.href);
switch (link.type) {
case "profile": {
element.setAttribute(
"data-type",
"mention",
);
element.setAttribute(
"data-mention-id",
link.id,
);
break;
}
case "navigate": {
if (link.navigation_type === "channel") {
element.setAttribute(
"data-type",
"channel_mention",
);
element.setAttribute(
"data-mention-id",
link.channel_id,
);
}
break;
}
case "external": {
element.setAttribute("target", "_blank");
element.setAttribute("rel", "noreferrer");
break;
}
}
},
);
}
}}
className={styles.markdown}
dangerouslySetInnerHTML={{
__html: md.render(newContent),
}}
data-large-emojis={useLargeEmojis}
/>
);
}
+7
View File
@@ -0,0 +1,7 @@
import { passThroughComponents } from "./plugins/remarkRegexComponent";
import { timestampHandler } from "./plugins/timestamps";
export const handlers = {
...passThroughComponents("emoji", "spoiler", "mention", "channel"),
timestamp: timestampHandler,
};
@@ -0,0 +1,79 @@
import styled from "styled-components";
import { useCallback, useRef } from "preact/hooks";
import { Tooltip } from "@revoltchat/ui";
import { modalController } from "../../../controllers/modals/ModalController";
/**
* Base codeblock styles
*/
const Base = styled.pre`
padding: 1em;
overflow-x: scroll;
background: var(--block);
border-radius: var(--border-radius);
`;
/**
* Copy codeblock contents button styles
*/
const Lang = styled.div`
font-family: var(--monospace-font);
width: fit-content;
padding-bottom: 8px;
a {
color: #111;
cursor: pointer;
padding: 2px 6px;
font-weight: 600;
user-select: none;
display: inline-block;
background: var(--accent);
font-size: 10px;
text-transform: uppercase;
box-shadow: 0 2px #787676;
border-radius: calc(var(--border-radius) / 3);
&:active {
transform: translateY(1px);
box-shadow: 0 1px #787676;
}
}
`;
/**
* Render a codeblock with copy text button
*/
export const RenderCodeblock: React.FC<{ class: string }> = ({
children,
...props
}) => {
const ref = useRef<HTMLPreElement>(null);
let text = "text";
if (props.class) {
text = props.class.split("-")[1];
}
const onCopy = useCallback(() => {
const text = ref.current?.querySelector("code")?.innerText;
text && modalController.writeText(text);
}, [ref]);
return (
<Base ref={ref}>
<Lang>
<Tooltip content="Copy to Clipboard" placement="top">
{/**
// @ts-expect-error Preact-React */}
<a onClick={onCopy}>{text}</a>
</Tooltip>
</Lang>
{children}
</Base>
);
};
@@ -0,0 +1,34 @@
import { Link } from "react-router-dom";
import { determineLink } from "../../../lib/links";
import { modalController } from "../../../controllers/modals/ModalController";
export function RenderAnchor({
href,
...props
}: JSX.HTMLAttributes<HTMLAnchorElement>) {
// Pass-through no href or if anchor
if (!href || href.startsWith("#")) return <a href={href} {...props} />;
// Determine type of link
const link = determineLink(href);
if (link.type === "none") return <a {...props} />;
// Render direct link if internal
if (link.type === "navigate") {
return <Link to={link.path} children={props.children} />;
}
return (
<a
{...props}
href={href}
target="_blank"
rel="noreferrer"
onClick={(ev) =>
modalController.openLink(href) && ev.preventDefault()
}
/>
);
}
@@ -0,0 +1,21 @@
import { Link } from "react-router-dom";
import { clientController } from "../../../controllers/client/ClientController";
import { createComponent, CustomComponentProps } from "./remarkRegexComponent";
export function RenderChannel({ match }: CustomComponentProps) {
const channel = clientController.getAvailableClient().channels.get(match)!;
return (
<Link
to={`${
channel.server_id ? `/server/${channel.server_id}` : ""
}/channel/${match}`}>{`#${channel.name}`}</Link>
);
}
export const remarkChannels = createComponent(
"channel",
/<#([A-z0-9]{26})>/g,
(match) => clientController.getAvailableClient().channels.has(match),
);
+66
View File
@@ -0,0 +1,66 @@
import styled from "styled-components";
import { useState } from "preact/hooks";
import { emojiDictionary } from "../../../assets/emojis";
import { clientController } from "../../../controllers/client/ClientController";
import { parseEmoji } from "../../common/Emoji";
import { createComponent, CustomComponentProps } from "./remarkRegexComponent";
const Emoji = styled.img`
object-fit: contain;
height: var(--emoji-size);
width: var(--emoji-size);
margin: 0 0.05em 0 0.1em;
vertical-align: -0.2em;
img:before {
content: " ";
display: block;
position: absolute;
height: 50px;
width: 50px;
background-image: url(ishere.jpg);
}
`;
const RE_EMOJI = /:([a-zA-Z0-9\-_]+):/g;
const RE_ULID = /^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$/;
export function RenderEmoji({ match }: CustomComponentProps) {
const [fail, setFail] = useState(false);
const url = RE_ULID.test(match)
? `${
clientController.getAvailableClient().configuration?.features
.autumn.url
}/emojis/${match}`
: parseEmoji(
match in emojiDictionary
? emojiDictionary[match as keyof typeof emojiDictionary]
: match,
);
if (fail) return <span>{`:${match}:`}</span>;
return (
<Emoji
alt={`:${match}:`}
loading="lazy"
className="emoji"
draggable={false}
src={url}
onError={() => setFail(true)}
/>
);
}
export const remarkEmoji = createComponent(
"emoji",
RE_EMOJI,
(match) => match in emojiDictionary || RE_ULID.test(match),
);
export function isOnlyEmoji(text: string) {
return text.replaceAll(RE_EMOJI, "").trim().length === 0;
}

Some files were not shown because too many files have changed in this diff Show More