Compare commits

...

1681 Commits

Author SHA1 Message Date
Aravinth Manivannan f9aa7b52f8
feat: switch to matrix.test.mystiq.app
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-08-19 17:41:05 +05:30
Aravinth Manivannan 2e54866353
fix: submit path
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-08-18 17:51:23 +05:30
Aravinth Manivannan ce075eb32b
feat: set custom homeserver and bugreport endpoint
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-08-18 17:41:24 +05:30
Aravinth Manivannan 02a50a19cb
feat: add ci badge
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-08-16 17:25:37 +05:30
Aravinth Manivannan a33d9981bd
fix: secrets
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-08-16 17:05:59 +05:30
Aravinth Manivannan 8335a50308
feat: switch to python, debian doesn't have make installed by default
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-08-16 17:02:10 +05:30
Aravinth Manivannan ee9e73d8c7
fix: use debian latest img to get git with `git branch --show-current`
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-08-16 16:59:15 +05:30
Aravinth Manivannan 63f77feb7b
fix: set project root
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-08-16 16:55:52 +05:30
Aravinth Manivannan 04de39596f
feat: bump ci node to 16
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-08-16 16:52:22 +05:30
Aravinth Manivannan 25b634bb78
fix: use same tests as github actions
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-08-16 16:47:23 +05:30
Aravinth Manivannan 96c9ea8de7
fix: use node 14, same as github actions config
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-08-16 16:44:15 +05:30
Aravinth Manivannan d80e970117
feat: conditional deploy pipeline
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-08-16 16:38:53 +05:30
Aravinth Manivannan 6db5f34ac2
feat: multi-pipeline workflow 2022-08-16 16:36:05 +05:30
Aravinth Manivannan df0000783d
feat: deploy to librepages
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-08-16 16:14:27 +05:30
Bruno Windels c898bcb46a release v0.3.1 2022-08-02 12:16:55 +02:00
Bruno Windels 97391663d3 sdk version 0.1.0 2022-08-01 14:32:26 +02:00
R Midhun Suresh 7d3f22c106
Merge pull request #824 from vector-im/fix-dev-server-1
Fix develop server breaking due to import syntax
2022-08-01 17:29:52 +05:30
RMidhunSuresh 832597447a Add explaining doc 2022-08-01 17:01:36 +05:30
RMidhunSuresh 236a4ab49b Ignore error 2022-08-01 17:01:36 +05:30
RMidhunSuresh ba8cdea6b4 Use default import if other not found 2022-08-01 17:01:36 +05:30
RMidhunSuresh ef9f90bc36 Fix imports breaking on dev 2022-08-01 17:01:36 +05:30
R Midhun Suresh 67e94bd642
Merge pull request #825 from vector-im/fix-sdk-fail-1
Fix sdk build failing after derived theme implementation
2022-08-01 16:17:09 +05:30
R Midhun Suresh f7839135a4
Merge pull request #823 from vector-im/fix-tmp-dir
Fix .tmp being created in `/`
2022-08-01 16:16:35 +05:30
RMidhunSuresh 4571ecd851 Specify theme as array 2022-07-29 23:45:58 +05:30
RMidhunSuresh 5091090795 Produce .tmp directory within root 2022-07-29 23:11:17 +05:30
Bruno Windels db2b4e693c release v0.3.0 2022-07-29 17:10:24 +02:00
Bruno Windels eee8412621
Merge pull request #822 from vector-im/bwindels/move-runtime-theme-test-out-of-root
move semi-automatic test for runtime themes into dedicated directory
2022-07-29 15:00:34 +00:00
Bruno Windels 5e83eca3b9 move semi-automatic test for runtime themes into dedicated directory 2022-07-29 16:43:28 +02:00
Bruno Windels 041e628520
Merge pull request #769 from vector-im/implement-derived-theme
Support for derived themes
2022-07-29 14:25:05 +00:00
Bruno Windels 4838e19c92
Merge pull request #811 from vector-im/bwindels/sharekeyswithinvitees
Key sharing based on room history visibility
2022-07-29 14:23:26 +00:00
Bruno Windels cb0ac846c7 remove obsolete comment 2022-07-29 16:22:01 +02:00
Bruno Windels b40ce6137e
Merge pull request #676 from vector-im/ts-conversion-domain-navigation
Convert /domain/navigation to typescript
2022-07-29 14:21:17 +00:00
Bruno Windels fdefea5b88 Merge branch 'master' into ts-conversion-domain-navigation 2022-07-29 16:18:22 +02:00
RMidhunSuresh 39817dc36b Revert back option 2022-07-29 17:33:33 +05:30
RMidhunSuresh 708637e390 No need for this complex resolve 2022-07-29 16:45:25 +05:30
Bruno Windels b6f795505d fix lint 2022-07-29 12:21:16 +02:00
Bruno Windels 10522cacef
Merge pull request #813 from vector-im/doc-derived-theming
[Documentation] - Add information about derived themes to doc
2022-07-29 10:16:41 +00:00
Bruno Windels 02116103a1
Merge pull request #816 from Kaki-In/restore_last
Opening the last opened room at start
2022-07-29 10:16:23 +00:00
Bruno Windels 06da5a8ae4
clarification 2022-07-29 10:14:58 +00:00
Bruno Windels 02bc7d1d7e
fix typo 2022-07-29 10:14:41 +00:00
Kaki In 09bc77073b
Merge branch 'vector-im:master' into restore_last 2022-07-29 12:06:49 +02:00
Bruno Windels 4a2e14925a
Merge pull request #812 from vector-im/doc-config
[Documentation] - Add type for config options
2022-07-29 10:05:27 +00:00
Bruno Windels 224ab2672a
Merge pull request #809 from Kaki-In/implement-join
Implemented /join
2022-07-29 10:03:18 +00:00
Bruno Windels 170460f5a9 add link to sygnal webpush docs as well 2022-07-29 12:02:09 +02:00
Bruno Windels 2a5e0302dc
Merge pull request #785 from vector-im/hs/log-when-storage-access-fails
Log the error when we can't get storage access
2022-07-29 09:47:58 +00:00
Kaki In f512bfcfc1 Pretty syntaxed the RoomViewModel 2022-07-29 11:47:47 +02:00
Half-Shot 5b5c852401 Revert "use logging items"
This reverts commit d937b9b14b.
2022-07-29 10:44:37 +01:00
Kaki In 58a2d1f34c Restored the common.js indentation 2022-07-29 11:44:23 +02:00
Half-Shot d937b9b14b use logging items 2022-07-29 10:39:41 +01:00
Bruno Windels d3e93196e3
Merge pull request #777 from ibeckermayer/ibeckermayer/ts-conversion-loginviewmodel
TS conversion for `LoginViewModel`
2022-07-29 09:27:10 +00:00
Bruno Windels 62b3a67e33 write unit tests for correctly reading history visibility when needed 2022-07-28 17:09:41 +02:00
Bruno Windels 319ec37864 fix typos preventing to load the history visibility 2022-07-28 11:44:50 +02:00
Kaki In f5dacb4e42 Fixed last check 2022-07-28 10:26:59 +02:00
Kaki In 302131c447 Review last checks 2022-07-28 10:14:21 +02:00
Kaki In fb79326747 Forgot one change 2022-07-28 09:26:08 +02:00
Kaki In 3c64f7d49b Finals checks about https://github.com/vector-im/hydrogen-web/pull/809#pullrequestreview-1053501341
- joined the processJoinRoom and joinRoom methods;
 - fixed some precisions miss;
 - removed some useless code;
 - change the error message height from absolute (40px) to relative (auto)
2022-07-28 09:23:30 +02:00
Isaiah Becker-Mayer a82df95b82 marking private methods as such 2022-07-27 22:09:30 -07:00
Isaiah Becker-Mayer cadca70946 fixes linter errors and removes some unneeded async/await 2022-07-27 22:09:30 -07:00
Isaiah Becker-Mayer 8b91d8fac8 adds newline 2022-07-27 22:09:30 -07:00
Isaiah Becker-Mayer a5b9cb6b95 removes unnecessary awaits 2022-07-27 22:09:30 -07:00
Isaiah Becker-Mayer aeed978789 changes signature of emitChange to require changedProps 2022-07-27 22:09:30 -07:00
Isaiah Becker-Mayer 7b7b19476c updates some signatures to be more verbose, fixes wrong type for attemptLogin 2022-07-27 22:09:30 -07:00
Isaiah Becker-Mayer ad0bd82bda creating default exports 2022-07-27 22:09:30 -07:00
Isaiah Becker-Mayer d7657dcc4d first draft of fully typescriptified LoginViewModel.ts 2022-07-27 22:09:30 -07:00
Kaki In 176caf340f Placed the join command outside of the processCommand method 2022-07-27 16:42:44 +02:00
Kaki In a40bb59dc0 Some fixes :
- fixed a pretty syntax miss (a !== b);
 - fixed a type error : replaced "msgtype" by "type" when instantied the "messinfo" variable;
 - some indentation fixes
2022-07-27 16:36:58 +02:00
Kaki In ab64ce02b2 Separated the _processCommand and the joinRoom command
- renamed executeJoinCommand as joinRoom;
 - separated the joinRoom process and the parse and result process
2022-07-27 15:18:32 +02:00
Kaki In 2d3b6fe973 Canceled indentation modification. 2022-07-27 12:40:19 +02:00
Kaki In 550b9db4dc Separated the join instructions into a executeJoinCommand method 2022-07-27 12:21:00 +02:00
Bruno Windels 0df66b5aea track room before listing user ids when sharing key 2022-07-27 12:06:55 +02:00
Bruno Windels f18520a2fe let loadMembers use own txn in case members haven't been fetched yet
if they haven't, it will need a network request, meaning that the txn
will get closed, so we can't reuse it afterwards
2022-07-27 11:39:50 +02:00
Bruno Windels 50b6ee91d7 don't need history visibility here 2022-07-27 11:39:36 +02:00
Kaki In 9b0ab0c8f1 Used "null" instead of "undefined"
When creating the this._lastSessionHash attribute of History
2022-07-27 09:19:36 +02:00
Bruno Windels 402cf17d22 Merge branch 'master' into bwindels/sharekeyswithinvitees 2022-07-27 09:17:31 +02:00
Bruno Windels bfaba63f47 fix ts error 2022-07-26 17:55:21 +02:00
Bruno Windels 544afef902 test adding and removing when tracking multiple rooms 2022-07-26 17:41:26 +02:00
Bruno Windels dd878bb8d6 also take rejecting invites into account to remove user identity 2022-07-26 16:58:07 +02:00
Bruno Windels dea3852425 add some tests for sharing keys with invitees 2022-07-26 16:57:28 +02:00
Bruno Windels 4c17612b05 allow passing txn to loadMembers so we can do it as part of sync txn
to rewrite useridentities upon receiving new history visibility
2022-07-26 16:53:02 +02:00
Kaki In f9f49b7640 Fixed an error and improving css
If the /join command success, an error was thrown, because of a copy-pasted command not well integrated
The button of the error on "theme.css" contains now an unicode cross. The :after/:before cross was disformed when opening the room informations.
2022-07-26 14:48:03 +02:00
Kaki In 0718f1e77e Fixed the https://github.com/vector-im/hydrogen-web/pull/816#discussion_r929692693 comment
Added the _lastSessionHash attribute inside the History constructor
2022-07-26 11:11:16 +02:00
Kaki In 09fd1a5113 Use "args.join" instead of "message.substring"
into RoomViewModel._processCommands
2022-07-26 10:37:05 +02:00
Kaki In 832b840a15 Merge remote-tracking branch 'origin' into restore_last 2022-07-26 10:06:31 +02:00
Kaki In adfecf0778 Fix restoring the last url at start
The last session url is now remembered for being restored at the beginning of the session. Thanks for the help of @bwindels
2022-07-26 10:02:20 +02:00
Kaki In 5fa6793958
Merge branch 'vector-im:master' into implement-join 2022-07-25 16:30:50 +02:00
Kaki In 1e5179f835 - Application des différents commentaires du Pull Request (#809)
- Correction des erreurs d'indentations.
2022-07-25 15:22:06 +02:00
Bruno Windels bc385e2cdc
Merge pull request #778 from vector-im/bwindels/uidocs
more detailed docs for IView, TemplateView and ListView
2022-07-25 13:02:22 +00:00
Kaki In 0bf021ea87 The room is now joined after having actualised the rooms list, to avoid the synchronisations waits that can sometimes disable to enter the room (message "You're not into this room" or simply "You're not in this room yet. *Join the room*") 2022-07-25 13:37:03 +02:00
RMidhunSuresh fdd60a7516 Add documentation for derived themes 2022-07-25 11:38:50 +05:30
RMidhunSuresh 63bdbee39c Make optional fields optional 2022-07-25 11:33:22 +05:30
RMidhunSuresh 8a976861fb Add type 2022-07-25 11:31:14 +05:30
Bruno Windels a23df8a545 pass history visibility to device tracker
and delegate adding and removing members to share keys with to it
2022-07-22 17:49:59 +02:00
Bruno Windels 17f42f523a add write method for when history visibility changes
also returning added and removed user ids
2022-07-22 17:49:26 +02:00
Bruno Windels f6011f3f34 take history visibility into account in device tracker
and return added and removed userids to their userIdentity for the given
room, so room encryption can share and discard the keys for them
2022-07-22 17:48:26 +02:00
Bruno Windels 86c0e9e669 logic for whether a key should be shared by membership and h. visibility 2022-07-22 17:46:53 +02:00
Bruno Windels f337940202 this migration shouldn't be needed anymore
and undoes the export of addRoomToIdentity, which is somewhat internal
2022-07-22 17:46:29 +02:00
Kaki In b7fd22c7f9 SyntaxError fixed 2022-07-22 17:10:29 +02:00
Kaki In 66a59e6f4d Error of interpretation of the 403 status at the last update. Fixed 2022-07-22 17:09:43 +02:00
Kaki In e345d0b33e Added the 403 status when joining an unknown room 2022-07-22 17:06:09 +02:00
Kaki In be8962cec2 Fixed priority operations when checking request status 2022-07-22 16:59:48 +02:00
Kaki In 8b39346409 The error message can now be closed 2022-07-22 16:34:52 +02:00
Kaki In fb58d9c9ef Corrected some syntax dismiss 2022-07-22 16:08:53 +02:00
Bruno Windels 22831e710c support async callback in iterateResponseStateEvents 2022-07-22 14:15:26 +02:00
Kaki In faa8cae532 Added the possibility to join a room using /join (also added the global commands uses, and some others commands like /shrug .) 2022-07-21 13:55:23 +02:00
RMidhunSuresh 8d766ac504 Remove await within loop 2022-07-21 12:05:10 +05:30
Bruno Windels c8a8eb10b5 get user ids for sharing a new key when the message is sent
rather than when the key happens to get sent
2022-07-20 15:21:33 +02:00
Bruno Windels d79e5f7806 create key share operations for invitees when history visibility=invited 2022-07-20 15:20:23 +02:00
RMidhunSuresh 7feaa479c0 Typescript update to support .mjs files 2022-07-20 15:55:11 +05:30
RMidhunSuresh 1456e308a8 Add type and fix formatting 2022-07-20 15:36:02 +05:30
RMidhunSuresh 313e65e00c Write tests 2022-07-20 12:30:41 +05:30
RMidhunSuresh 612b878793 Update theme name 2022-07-19 21:21:35 +05:30
RMidhunSuresh 8aa96e8031 Update log label 2022-07-19 21:19:22 +05:30
RMidhunSuresh 7ac2c7c7fa Get tests to work 2022-07-19 21:06:55 +05:30
RMidhunSuresh de02456641 Add explaining comment 2022-07-19 19:46:36 +05:30
RMidhunSuresh 994667205f Remove change 2022-07-19 19:38:36 +05:30
RMidhunSuresh ecb3a66dfc WIP 2022-07-19 17:56:08 +05:30
RMidhunSuresh e1ee258630 Change path 2022-07-19 17:56:08 +05:30
RMidhunSuresh 83b5d3b68e Change directory name 2022-07-19 17:56:08 +05:30
RMidhunSuresh 7a1591e0ce Move code 2022-07-19 17:56:08 +05:30
RMidhunSuresh 07db5450b7 Aliases can also be derived 2022-07-19 17:56:08 +05:30
RMidhunSuresh 081de5afa8 .js --> .mjs 2022-07-19 17:56:08 +05:30
RMidhunSuresh dece42dce3 Do not store all the manifests in memory 2022-07-19 17:56:08 +05:30
RMidhunSuresh b29287c47e await in loop --> Promise.all() 2022-07-19 17:56:08 +05:30
RMidhunSuresh 9bdf9c500b Add return types 2022-07-19 17:56:08 +05:30
RMidhunSuresh 9e2d355573 Add logging 2022-07-19 17:56:08 +05:30
RMidhunSuresh ce5db47708 Support using derived theme as default theme 2022-07-19 17:56:08 +05:30
RMidhunSuresh da0a918c18 This code should only run once 2022-07-19 17:56:08 +05:30
RMidhunSuresh 043cc9f12c Use ThemeManifest type 2022-07-19 17:56:08 +05:30
RMidhunSuresh 80fb953688 Don't fail on erros; expect the code to throw! 2022-07-19 17:56:08 +05:30
RMidhunSuresh f15e23762a Add more missing keys to type 2022-07-19 17:56:08 +05:30
RMidhunSuresh f440457875 Use ThemeManifest type where possible 2022-07-19 17:56:08 +05:30
RMidhunSuresh a8cab98666 Add mroe missing types 2022-07-19 17:56:08 +05:30
RMidhunSuresh ac7be0c7a1 WIP 2022-07-19 17:56:08 +05:30
RMidhunSuresh d731eab51c Support fetching text 2022-07-19 17:56:08 +05:30
RMidhunSuresh f7b302d34f Don't optimzie colors 2022-07-19 17:56:08 +05:30
RMidhunSuresh 5ba74b1d75 Use script to copy over runtime theme after build 2022-07-19 17:56:08 +05:30
RMidhunSuresh c5f4a75d4b Split code so that it can be reused 2022-07-19 17:56:08 +05:30
RMidhunSuresh 2f3db89e0a Let ts know that we can use replaceAll() 2022-07-19 17:56:08 +05:30
RMidhunSuresh 1ef382f3a9 Add gruvbox color scheme 2022-07-19 17:56:08 +05:30
RMidhunSuresh 161e29b36e Use existing code 2022-07-19 17:56:08 +05:30
RMidhunSuresh 2947f9f6ff Remove console.log 2022-07-19 17:56:08 +05:30
RMidhunSuresh c873804543 produce asset hashed icons 2022-07-19 17:56:08 +05:30
RMidhunSuresh 43e8cc9e52 Add svgo for optimizing svgs as dev dependency 2022-07-19 17:56:08 +05:30
RMidhunSuresh bf87ed7eae Do not add variables to root for runtime theme 2022-07-19 17:56:08 +05:30
RMidhunSuresh 8c02541b69 WIP - 1 2022-07-19 17:56:08 +05:30
RMidhunSuresh 599e519f22 Convert color code to use es6 module 2022-07-19 17:56:08 +05:30
RMidhunSuresh d5e24bf6e8 Convert color.js to color.mjs 2022-07-19 17:56:08 +05:30
Bruno Windels bb5711db7e
Merge pull request #802 from vector-im/fix-dev-server
Fix bug that stops hydrogen from running in dev server
2022-07-19 10:22:36 +00:00
RMidhunSuresh 88808b0b06 Fix bug preventing yarn start 2022-07-19 15:50:01 +05:30
R Midhun Suresh c9bca52e82
Merge pull request #760 from vector-im/refactor-rollup-plugin
Refactor theme builder plugin
2022-07-11 16:54:18 +05:30
RMidhunSuresh 6718198d9c Continue with other items if this throws 2022-07-11 12:40:24 +05:30
Bruno Windels 7b9e681d55 sdk v0.0.15 2022-07-07 15:25:17 +02:00
R Midhun Suresh 8291aea2f7
Merge pull request #790 from vector-im/fix-hide-composer
Pass childOptions to LowPowerLevelViewModel
2022-07-07 18:19:08 +05:30
RMidhunSuresh f073f40e31 Fix error 2022-07-07 18:16:33 +05:30
R Midhun Suresh 963324c767
Merge pull request #789 from vector-im/support-pl-room-creation
Support power_level_content_override option on room creation
2022-07-07 17:42:19 +05:30
R Midhun Suresh eac75644e7
Merge pull request #788 from vector-im/pl-composer
Disable composer when user lacks powerlevel needed to send messages
2022-07-07 17:35:29 +05:30
RMidhunSuresh 0bdbb96036 Use same kind 2022-07-07 17:26:43 +05:30
RMidhunSuresh d292e1f5ad Extract into function 2022-07-07 17:23:23 +05:30
RMidhunSuresh cd9e00b847 Support power_level_content_override 2022-07-07 17:17:05 +05:30
RMidhunSuresh 3941b7e3f0 Rename method 2022-07-07 16:45:18 +05:30
RMidhunSuresh efd9f70e92 WIP 2022-07-07 16:39:45 +05:30
Isaiah Becker-Mayer 204948db64 changing filename to ts 2022-07-06 21:06:36 -04:00
Will Hunt a85d2c96d6
Log the error when we can't get storage access
This is quite useful when debugging why a session isn't working properly.
2022-07-06 10:06:00 +01:00
R Midhun Suresh 28b686dae7
Merge pull request #784 from vector-im/fix-build-race
Fix build error caused due to race in postcss plugin
2022-07-05 20:13:57 +05:30
RMidhunSuresh dd82469ab4 Don't assume object is available 2022-07-05 20:07:48 +05:30
Bruno Windels 3bf6a46a39 release sdk 0.0.14 2022-07-05 16:02:47 +02:00
Bruno Windels e42e76a21c
Merge pull request #782 from vector-im/image-view-fix
Do not render images as links if lightboxUrl is empty
2022-07-05 14:00:53 +00:00
RMidhunSuresh 8ec0bd7295 Check if lightbox url is available 2022-07-05 17:55:51 +05:30
Bruno Windels ff2129f36a
Merge pull request #773 from vector-im/madlittlemods/consistent-test-selector
Add a couple consistent selectors to reference in tests
2022-07-04 14:19:09 +00:00
Bruno Windels 1aa2ff5c10
Merge pull request #781 from vector-im/bwindels/fixlint-2022-7-4
fix lint
2022-07-04 14:18:05 +00:00
Bruno Windels 34ce8a8e3c fix lint 2022-07-04 16:15:59 +02:00
Bruno Windels 652e2c6d3b
Merge pull request #780 from vector-im/bwindels/update-olm-3.2.8
update olm to 3.2.8
2022-07-04 14:15:04 +00:00
Bruno Windels c0445f2182 update lock file 2022-07-04 15:40:17 +02:00
Bruno Windels b76fd1d792 update olm to 3.2.8 2022-07-04 15:39:11 +02:00
R Midhun Suresh 751dfa66a8
Merge pull request #758 from vector-im/document-theming
Document theming in Hydrogen
2022-07-04 17:20:53 +05:30
RMidhunSuresh a3c6d744f5 Add link to ts file 2022-07-04 17:18:50 +05:30
R Midhun Suresh b9f316e7c3 Better sentence structure
Co-authored-by: Bruno Windels <274386+bwindels@users.noreply.github.com>
2022-07-04 17:16:43 +05:30
R Midhun Suresh d448ee1722 Fix typo
Co-authored-by: Bruno Windels <274386+bwindels@users.noreply.github.com>
2022-07-04 17:16:43 +05:30
RMidhunSuresh da87470996 Store images in source tree 2022-07-04 17:16:43 +05:30
RMidhunSuresh b319c0acb0 Remvoe stray newlines 2022-07-04 17:16:43 +05:30
RMidhunSuresh e90e573bf9 Add doc 2022-07-04 17:16:43 +05:30
R Midhun Suresh a68f0bba39
Merge pull request #752 from vector-im/theme-document-manifest
Create a type for theme-manifest
2022-07-04 17:11:08 +05:30
Bruno Windels ca94c65dac clarify LazyListView constraints 2022-07-04 10:19:56 +02:00
Bruno Windels fba3275f5b
Merge pull request #746 from vector-im/madlittlemods/assets-path-for-assets
Import SDK assets from the `assets/` directory
2022-07-04 06:53:37 +00:00
Bruno Windels fc93acfd8d some rewording 2022-07-01 14:09:06 +02:00
Bruno Windels d398e490eb some rewording 2022-07-01 13:59:57 +02:00
Bruno Windels 0ab611b013 more detailed docs for IView, TemplateView and ListView 2022-07-01 13:08:50 +02:00
Bruno Windels bb923b8eb9 bump sdk version 2022-06-30 10:54:11 +02:00
Bruno Windels 73cd96fe3a abort release script on error 2022-06-30 10:54:00 +02:00
Bruno Windels 4929839fe9 release v0.2.33 2022-06-30 10:51:11 +02:00
Eric Eastwood c59f65e43b Add a couple consistent selectors to reference in tests
Using `data-testid` because it seems generic out of the list from:

 - https://docs.cypress.io/guides/core-concepts/cypress-app#Uniqueness
 - https://docs.cypress.io/guides/references/best-practices#How-It-Works
2022-06-29 12:56:20 +02:00
Eric Eastwood fd3a0f0126 Merge branch 'master' into madlittlemods/assets-path-for-assets 2022-06-28 16:35:54 +02:00
Eric Eastwood ccfd63dfeb Restore backwards compatible theme paths
See https://github.com/vector-im/hydrogen-web/pull/746#discussion_r901347536
2022-06-28 16:35:30 +02:00
Eric Eastwood 5b54280ac2
Ignore macOS metadata .DS_Store (#770) 2022-06-28 05:08:24 -05:00
Bruno Windels bd5bf7d456
Merge pull request #761 from vector-im/hs/node-15-replaceal
Require node 15+
2022-06-25 18:22:35 +00:00
Bruno Windels ad8ad22cc1
Merge pull request #767 from vector-im/bwindels/download-media
Menu option to download attached image or video of event
2022-06-25 18:21:17 +00:00
Bruno Windels 3369bda2f0 offer menu options to download media
also always show status (before sendStatus), not just when isPending
as we are recycling it to show download status as well
2022-06-25 20:15:33 +02:00
Bruno Windels 7430aa7aab allow download media in media view model 2022-06-25 20:14:32 +02:00
Bruno Windels 3bc453d5ca
Merge pull request #766 from vector-im/bwindels/fix-765
Also allow undefined, which means at the end of the paginated direction
2022-06-25 17:40:26 +00:00
Bruno Windels 84bac0afe9 Also allow undefined, which means at the end of the paginated direction
we already detect the end by chunk.length===0, so we just need to not throw
2022-06-25 19:37:36 +02:00
Will Hunt 9cb7d89097 Require node 15.
We use replaceAll in scripts/postcss/svg-colorizer.js which is a ES2021 feature. https://node.green/#ES2021-features--String-prototype-replaceAll
2022-06-24 13:27:09 +01:00
RMidhunSuresh d688fa4737 Get the theme-collection id from manifest 2022-06-23 15:06:22 +05:30
RMidhunSuresh 0dfd24af22 Update info on path
path is now relative to the manifest!
2022-06-21 12:52:10 +05:30
RMidhunSuresh 34eac94da3 Make everything optional
Now typescript will force us to validate everything.
2022-06-20 21:27:02 +05:30
RMidhunSuresh fbdd512e06 Split functions into smaller functions 2022-06-20 21:10:11 +05:30
RMidhunSuresh 5eec724712 Locations must be relative to manifest 2022-06-20 20:35:06 +05:30
RMidhunSuresh 93165cb947 runtime theme chunks should also be stored in map
There will be more than one runtime theme file when multiple theme
collections exist.
2022-06-20 13:46:14 +05:30
RMidhunSuresh e3372f0f2b Don't use theme-name in manifest file names 2022-06-20 12:54:18 +05:30
R Midhun Suresh 5a3cf03f0b
Merge pull request #759 from vector-im/move-scope-down
Refactor out global variables in postcss plugins
2022-06-20 12:14:06 +05:30
R Midhun Suresh c050ade03c
Merge pull request #756 from vector-im/themeing-improvement-1
Improve code quality in css-url-variables plugin
2022-06-20 11:19:47 +05:30
RMidhunSuresh cc29dc045d Move scope down in css-url-processor 2022-06-17 16:38:13 +05:30
RMidhunSuresh 09b2437e72 Move scope of variables down in compile-variables 2022-06-17 16:35:18 +05:30
RMidhunSuresh cfd347335b Move scope of variables down
This was causing icons to be repeated in the css-file
2022-06-16 21:29:33 +05:30
RMidhunSuresh d322f380ad Fix typo here
This was causing the icons section to be omitted from the source section
of the manifest.
2022-06-16 21:26:16 +05:30
RMidhunSuresh f658dc2e4b Make comment clearer 2022-06-15 15:06:16 +05:30
RMidhunSuresh 7a3eabf39c Formatting fix 2022-06-15 15:04:33 +05:30
RMidhunSuresh 48da6c782c Remove base key 2022-06-15 15:04:12 +05:30
RMidhunSuresh b00bbc7daf Fix formatting 2022-06-15 15:03:41 +05:30
RMidhunSuresh 9fbe8a4e32 Change description of version key 2022-06-15 15:02:15 +05:30
Bruno Windels 623939c671 release v0.2.32 2022-06-15 11:29:29 +02:00
Bruno Windels fccc41f4b9
Merge pull request #753 from vector-im/bwindels/rageshake-submit
Allow sending logs to rageshake server
2022-06-15 11:28:54 +02:00
Bruno Windels 3b66ed8c17 fix type 2022-06-15 11:24:16 +02:00
Bruno Windels 8fe8981ffa add options to send logs to server in settings ui 2022-06-15 11:14:06 +02:00
Bruno Windels 375d8b066c complete settings view model for logs ui 2022-06-15 11:13:46 +02:00
Bruno Windels 69ada73dd4 cleanup rageshake code 2022-06-15 11:13:05 +02:00
Bruno Windels 2129a97588 remove unused param 2022-06-15 11:12:49 +02:00
Bruno Windels 4caabae895 extract map -> formdata conversion and also suppor this for xhr 2022-06-15 10:15:15 +02:00
RMidhunSuresh d0375141f8 WIP - write type for manifest 2022-06-15 12:11:15 +05:30
Bruno Windels a644621889 basic support for sending rageshake in view model 2022-06-14 18:46:02 +02:00
Bruno Windels 4ed7e01dfd release v0.2.31 2022-06-14 16:00:35 +02:00
Bruno Windels e643ffb334
Merge pull request #751 from vector-im/fix-theming-watch
Fix: don't crash on platforms that don't have a preferred color scheme
2022-06-14 16:00:13 +02:00
RMidhunSuresh d00ea39dc4 No need to throw here 2022-06-14 19:27:18 +05:30
RMidhunSuresh 69d8e6031e This isn't used anywhere 2022-06-14 19:26:59 +05:30
Bruno Windels abee9baf60 release v0.2.30 2022-06-14 10:15:00 +02:00
Bruno Windels d4aaa8117b
Merge pull request #742 from vector-im/theme-chooser-improvements
Theme chooser improvements
2022-06-14 10:14:29 +02:00
RMidhunSuresh be66969c9a Remove font section from manifest 2022-06-14 11:52:45 +05:30
R Midhun Suresh 7bce0d848f
Merge pull request #750 from vector-im/madlittlemods/fix-broken-hydrogen-dev
Fix Vite not being able to analyze dynamic CSS styles import in dev on Windows
2022-06-13 20:04:25 +05:30
RMidhunSuresh 53a8915ffc Parellelize code 2022-06-12 17:05:31 +05:30
RMidhunSuresh b5fd3656a7 Fix code breaking on dev server 2022-06-12 16:53:25 +05:30
R Midhun Suresh acffd15002
Add comment
Co-authored-by: Bruno Windels <274386+bwindels@users.noreply.github.com>
2022-06-12 16:52:21 +05:30
R Midhun Suresh 989ecd785a
Lowercase string
Co-authored-by: Bruno Windels <274386+bwindels@users.noreply.github.com>
2022-06-12 16:51:58 +05:30
RMidhunSuresh 9a5a002293 Remove test-variant 2022-06-08 13:35:58 +05:30
Eric Eastwood 2cfd08e500 Remove debug logging 2022-06-07 23:47:38 -05:00
Eric Eastwood 2b4a7f05a6 Fix Vite not being able analyze dynamic CSS styles import in dev
Fix:
```
$ yarn start
[vite] warning:
@theme/default
1  |  import "C:\Users\MLM\Documents\GitHub\element\hydrogen-web\src\platform\web\ui\css\themes\element\theme.css";import "@theme/element/light/variables.css"
   |          ^
The above dynamic import cannot be analyzed by vite.
See https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations for supported dynamic import formats. If this is intended to be left as-is, you can use the /* @vite-ignore */ comment inside the import() call to suppress this warning.

  Plugin: vite:import-analysis
  File: @theme/default
```

And in the browser, it results in none of the styles loading because of the following error:
```
Uncaught SyntaxError: Invalid Unicode escape sequence (at default:formatted:1:163)
```

---

Before:
```
import { injectQuery as __vite__injectQuery } from "/@vite/client";import "__vite__injectQuery(C:\Users\MLM\Documents\GitHub\element\hydrogen-web\src\platform\web\ui\css\themes\element\theme.css, 'import')";import "/@id/__x00__@theme/element/light/variables.css"
```

After:
```
import "/ui/css/themes/element/theme.css";import "/@id/__x00__@theme/element/light/variables.css"
```
2022-06-07 23:41:45 -05:00
RMidhunSuresh d31f127982 Add explaining comment 2022-06-07 13:28:56 +05:30
RMidhunSuresh d08cfe3a29 Add more logging 2022-06-07 11:57:57 +05:30
RMidhunSuresh 51a837d459 Remove unuseed import 2022-06-06 17:26:39 +05:30
RMidhunSuresh 2f0f7143b5 Simplify code 2022-06-06 17:20:36 +05:30
RMidhunSuresh 0dac00f327 themeVariant is optional 2022-06-06 17:20:16 +05:30
RMidhunSuresh a639fc5467 Rever to sensisble defaults 2022-06-06 12:20:06 +05:30
RMidhunSuresh 258a604cc6 Don't make defaultTheme compulsory 2022-06-06 12:19:48 +05:30
RMidhunSuresh a2cbac9e0c Move code into method 2022-06-06 11:53:13 +05:30
RMidhunSuresh 71c3fb39a2 store theme-name and variant in settings 2022-06-05 20:52:47 +05:30
RMidhunSuresh 43244fa026 Add explaining comment 2022-06-05 20:52:47 +05:30
RMidhunSuresh 9e88bc3098 Fix bugs 2022-06-05 20:52:47 +05:30
RMidhunSuresh b74f4b612b Change UI 2022-06-05 20:52:47 +05:30
RMidhunSuresh 8de91291dd Add more methods to ThemeLoader 2022-06-05 20:52:47 +05:30
RMidhunSuresh dc2d1ce700 Remove id 2022-06-05 20:52:47 +05:30
RMidhunSuresh 12a8e94243 Move code into ThemeLoader 2022-06-05 20:52:47 +05:30
RMidhunSuresh 9e79b632a8 Extract variable 2022-06-05 20:52:47 +05:30
RMidhunSuresh efb1a67470 Make method name a verb 2022-06-05 20:52:47 +05:30
RMidhunSuresh e3235ea3eb Rename themeName --> themeId 2022-06-05 20:52:47 +05:30
RMidhunSuresh 46d2792dac Modify comment 2022-06-05 20:52:47 +05:30
RMidhunSuresh 8ad0b8a726 rename themeName --> variantName 2022-06-05 20:52:47 +05:30
RMidhunSuresh e8e4c33bae Rephrase comment 2022-06-05 20:52:47 +05:30
RMidhunSuresh cb03e97e78 Use default theme intially 2022-06-05 20:52:47 +05:30
RMidhunSuresh f6cec938a7 Add default theme to mapping 2022-06-05 20:52:47 +05:30
RMidhunSuresh bbec2effe5 Add typing 2022-06-05 20:52:47 +05:30
RMidhunSuresh d4084da299 Extract code into function 2022-06-05 20:52:47 +05:30
RMidhunSuresh 1f00c8f635 Add a temporary theme to test this PR 2022-06-05 20:52:47 +05:30
RMidhunSuresh 0b98473e85 Render a radio button for default variants 2022-06-05 20:52:47 +05:30
RMidhunSuresh 3afbe1148e Use the new built-asset format in ThemeLoader 2022-06-05 20:52:47 +05:30
RMidhunSuresh 809c522571 Change the format of built-asset 2022-06-05 20:52:47 +05:30
RMidhunSuresh 4474458f4b getActiveTheme should never return undefined
Instead it should throw an error.

This is useful for when we do setTheme(await getActiveTheme()) because
setTheme expects a string.
2022-06-05 20:52:47 +05:30
Eric Eastwood 9d8a578dce Better comment 2022-05-31 15:35:48 -05:00
Eric Eastwood 38c3774869 Import assets from the assets/ directory
> Will be easier towards the future when adding more assets. Probably best to keep style.css for now for backwards compat though.
>
> *-- https://github.com/vector-im/hydrogen-web/pull/693#discussion_r853844282*
2022-05-31 15:30:56 -05:00
Bruno Windels 8b2299852e
Merge pull request #744 from vector-im/bwindels/fix-tracker-changed-key-check
Fix: device with changed key not being properly ignored
2022-05-31 13:51:17 +02:00
Bruno Windels c62c8da10b fix changed key not being ignored 2022-05-31 13:39:35 +02:00
Bruno Windels bc51644868 reassignment is not used later on, remove 2022-05-31 13:39:23 +02:00
Bruno Windels 3d3d590334 add failing test for device with changed key being returned 2022-05-31 13:39:05 +02:00
Bruno Windels 11d7535c23 add some basic tests (with mock utils) for DeviceTracker 2022-05-31 13:38:34 +02:00
Bruno Windels a49d7eae5d
Merge pull request #693 from vector-im/madlittlemods/686-682-local-friendly-development-and-commonjs
Make the SDK friendly to locally link and develop on
2022-05-30 14:45:16 +02:00
Bruno Windels 1b2a6b5d0e
Merge branch 'master' into madlittlemods/686-682-local-friendly-development-and-commonjs 2022-05-30 14:15:19 +02:00
RMidhunSuresh ba647d012d Fix type in observeNavigation 2022-05-29 20:38:14 +05:30
RMidhunSuresh fc873757d8 WIP 2022-05-27 22:42:21 +05:30
RMidhunSuresh ec1cc89cf9 Make URLRouter in options conditional on generic
URLRouter can be passed in option to vm only if the SegmentType used
contains session.
ViewModel.urlCreator returns undefined when used with a SegmentType that
lacks session.
2022-05-27 22:42:21 +05:30
RMidhunSuresh a336623f3a Generic parameter should extend object 2022-05-27 22:42:21 +05:30
RMidhunSuresh 9300347e9b Give defaultt type 2022-05-27 22:42:21 +05:30
RMidhunSuresh f49d580d49 WIP 2022-05-27 22:42:21 +05:30
RMidhunSuresh 263948faa3 Remove unwanted export 2022-05-27 22:42:21 +05:30
RMidhunSuresh 52f0690c70 Add return type 2022-05-27 22:42:21 +05:30
RMidhunSuresh 7a24059337 Remove empty line 2022-05-27 22:42:21 +05:30
RMidhunSuresh 4fd1918202 Remove comment 2022-05-27 22:42:21 +05:30
RMidhunSuresh 4ae3a5bf7a Use undefined instead of null 2022-05-27 22:42:21 +05:30
RMidhunSuresh 5be00f051f Use subtype instead of whole SegmentType 2022-05-27 22:42:21 +05:30
RMidhunSuresh e7f4ce6175 Mark methods as private 2022-05-27 22:42:21 +05:30
RMidhunSuresh 09bc0f1b60 Extract complex type as type alias 2022-05-27 22:42:21 +05:30
RMidhunSuresh 76d04ee277 Make defaultSessionId optional 2022-05-27 22:42:21 +05:30
RMidhunSuresh f28dfc6964 Type createRouter function 2022-05-27 22:42:21 +05:30
RMidhunSuresh c14e4f3eed Use segment type 2022-05-27 22:42:21 +05:30
RMidhunSuresh 5d42f372f6 Pass as separate arguments to constructor 2022-05-27 22:42:21 +05:30
RMidhunSuresh 4c3e0a6ff0 Convert URLRouter.js to typescript 2022-05-27 22:42:21 +05:30
RMidhunSuresh d9bfca10e1 Type function 2022-05-27 22:42:21 +05:30
RMidhunSuresh bf2fb52691 Fix formatting 2022-05-27 22:42:21 +05:30
RMidhunSuresh 646cbe0fff Make all keys string 2022-05-27 22:42:21 +05:30
RMidhunSuresh 92e8fc8ad3 Remove deprecated method 2022-05-27 22:42:21 +05:30
RMidhunSuresh 92c79c853d Convert index.js to typescript 2022-05-27 22:42:21 +05:30
RMidhunSuresh 55229252d7 Type allowsChild 2022-05-27 22:42:21 +05:30
RMidhunSuresh 3efc426fed Complete converting Navigation.js to ts 2022-05-27 22:42:21 +05:30
RMidhunSuresh 04d5b9bfda WIP - 2 2022-05-27 22:42:21 +05:30
RMidhunSuresh 66f6c4aba1 WIP 2022-05-27 22:42:18 +05:30
Bruno Windels ed8c98558d release v0.2.29 2022-05-18 21:45:45 +02:00
Bruno Windels 514d5c0a50
add notes about client side caching 2022-05-18 19:44:39 +00:00
Bruno Windels 13428bd03c allow updating cache of unhashed assets (like config) in service worker 2022-05-18 21:41:47 +02:00
Bruno Windels 1555b0f4bc put a message in container node when config file is not found 2022-05-18 21:41:31 +02:00
Bruno Windels 0e46aed0df rename config file to config.sample.json when packaging 2022-05-18 20:52:18 +02:00
Bruno Windels 7b0591be46 explain that push section of config usually doesn't need to be touched 2022-05-18 20:51:50 +02:00
Bruno Windels f21e103270 add newlines to config file when rewriting with theme stuff 2022-05-18 20:46:38 +02:00
Bruno Windels 7a197c0a1a add deployment instruction now that we have a config file 2022-05-18 20:44:04 +02:00
Bruno Windels 8a5f1ed9cd Merge remote-tracking branch 'origin/move-config-root' 2022-05-18 20:40:12 +02:00
Bruno Windels 36ddd61318
Merge pull request #724 from vector-im/theme-chooser
Implement theme chooser in settings
2022-05-18 20:22:38 +02:00
Bruno Windels 03ab1ee2c7 log theme being loaded 2022-05-18 17:48:03 +02:00
RMidhunSuresh a550788788 Remove some logging + use wrapOrRun 2022-05-18 18:56:28 +05:30
RMidhunSuresh 683ffa9ed3 injectServiceWorker plugin should accept callback 2022-05-18 17:31:17 +05:30
RMidhunSuresh 7952a34d64 Add logging 2022-05-18 16:09:09 +05:30
RMidhunSuresh 7426d17e33 Precache config and theme manifest 2022-05-18 16:07:26 +05:30
RMidhunSuresh 660a08db3e Give a better name 2022-05-18 14:41:52 +05:30
RMidhunSuresh 1b22a48b54 Treat theme-manifests the same way as config 2022-05-18 14:23:41 +05:30
Eric Eastwood b725269c7a Clean up index.html in the right spot 2022-05-18 00:21:56 -05:00
Eric Eastwood 639358b146 Merge branch 'master' into madlittlemods/686-682-local-friendly-development-and-commonjs
Conflicts:
	scripts/sdk/base-manifest.json
2022-05-12 12:05:45 -05:00
RMidhunSuresh 34e8b60917 Create config.json in root 2022-05-12 16:05:33 +05:30
RMidhunSuresh 9ba1534390 Remove unused import 2022-05-12 16:03:06 +05:30
RMidhunSuresh 4ddfd3b508 built-asset --> built-assets 2022-05-12 14:56:58 +05:30
RMidhunSuresh e63440527a Move condition to binding 2022-05-12 13:43:19 +05:30
RMidhunSuresh 0984aeb570 Move code to ThemeLoader 2022-05-12 13:39:57 +05:30
RMidhunSuresh 654e83a5f9 Remove method 2022-05-12 13:28:11 +05:30
RMidhunSuresh b306344739 Add explaining comment 2022-05-12 12:55:08 +05:30
R Midhun Suresh 4231037345
Update src/platform/web/Platform.js
Co-authored-by: Bruno Windels <274386+bwindels@users.noreply.github.com>
2022-05-12 12:48:41 +05:30
R Midhun Suresh d5bc9f5d7d
Update src/platform/web/Platform.js
Co-authored-by: Bruno Windels <274386+bwindels@users.noreply.github.com>
2022-05-12 12:48:34 +05:30
Bruno Windels 6fde6bbf6b bump sdk version 2022-05-11 14:58:57 +02:00
RMidhunSuresh cc88245933 Create themeLoader only if not dev 2022-05-11 15:46:12 +05:30
RMidhunSuresh 174adc0755 Move platform dependent code to Platform 2022-05-11 15:38:37 +05:30
RMidhunSuresh c26dc04b52 Fix type 2022-05-11 15:03:32 +05:30
RMidhunSuresh 2761789f45 Move theme code to separate file 2022-05-11 14:58:14 +05:30
RMidhunSuresh 213f87378b Use t.if instead of t.map 2022-05-11 12:46:12 +05:30
RMidhunSuresh 855298bdaf Read from manifest 2022-05-11 12:40:32 +05:30
RMidhunSuresh e8a4ab5ecc built-asset must be a mapping
A mapping from theme-name to location of css file
2022-05-10 16:58:06 +05:30
RMidhunSuresh 5204fe5c99 This emitFile is no longer needed 2022-05-10 14:22:37 +05:30
RMidhunSuresh c39f0d2efb Don't show theme chooser on dev 2022-05-10 14:12:36 +05:30
RMidhunSuresh bb3368959f Use sh instead of bash 2022-05-10 14:12:36 +05:30
RMidhunSuresh af9cbd727f Remove existing stylesheets when changing themes 2022-05-10 14:12:36 +05:30
RMidhunSuresh 12a70469eb Fix formatting 2022-05-10 14:12:36 +05:30
RMidhunSuresh c611d3f85c Select current theme in dropdown 2022-05-10 14:12:36 +05:30
RMidhunSuresh ecb83bb277 Store and load theme from setting 2022-05-10 14:12:36 +05:30
RMidhunSuresh daae7442bb Create theme chooser 2022-05-10 14:12:36 +05:30
RMidhunSuresh cc2c74fdff Generate theme summary on build 2022-05-10 14:12:36 +05:30
RMidhunSuresh 541cd96eeb Add script to cleanup after build 2022-05-10 14:12:36 +05:30
RMidhunSuresh f16a2e5d22 Don't add asset hash to manifest json on build 2022-05-10 14:12:36 +05:30
Bruno Windels b7675f46c4 bump sdk version 2022-05-10 09:59:38 +02:00
R Midhun Suresh a06474d7ac
Merge pull request #731 from vector-im/fix-tilescollection
Newly created tiles must be given a copy of tilesOptions
2022-05-10 12:33:46 +05:30
Bruno Windels e903d3a6a4 mark options as readonly 2022-05-09 14:12:31 +02:00
Bruno Windels 3888291758 updateOptions is unused,not the best idea since options is/can be shared 2022-05-09 14:10:50 +02:00
Bruno Windels 6beff7e552 override emitChange so no need to clone option object for all tiles
instead, we don't store the emitChange in the options but rather on
the tile itself.
2022-05-09 14:09:45 +02:00
RMidhunSuresh 139a87de99 Pass a copy of the options to the tiles 2022-05-08 19:14:51 +05:30
Eric Eastwood e54482e4c0 Add some comments 2022-05-05 17:57:25 -05:00
Eric Eastwood 75098b4712 Merge branch 'master' into madlittlemods/686-682-local-friendly-development-and-commonjs 2022-05-05 17:50:33 -05:00
Eric Eastwood d053d4388f Update Vite to avoid flakey errors in our PostCSS plugins
Fix https://github.com/vector-im/hydrogen-web/issues/722

Updating Vite to includes fixes from
https://github.com/vitejs/vite/issues/7822 -> https://github.com/vitejs/vite/pull/7827
2022-05-05 14:58:43 -05:00
Bruno Windels 23b621492f
Merge pull request #726 from vector-im/flow-registration
Allow passing in flowSelector from startRegistration method
2022-04-27 11:18:53 +02:00
RMidhunSuresh 83664a1b13 viewClassForTile is needed for TimelineView 2022-04-27 12:38:12 +05:30
RMidhunSuresh c07a42292c Include Platform change in sdk docs 2022-04-27 12:28:48 +05:30
RMidhunSuresh 049a477008 Pass flowSelector from Client.startRegistration 2022-04-27 12:27:19 +05:30
Bruno Windels fa34315210 undo refactoring typo from #723 2022-04-25 16:44:31 +02:00
Bruno Windels bec8cea583 fix for breaking in #725 2022-04-25 14:17:07 +02:00
Bruno Windels 3536d12680
Merge pull request #725 from vector-im/bwindels/templateview-ts
add typing for text bindings in template view
2022-04-25 12:42:37 +02:00
Bruno Windels ab893f63b5 remove unneeded assignment 2022-04-25 12:40:25 +02:00
Bruno Windels 6c57c96cb9 add typing for text bindings in template view 2022-04-25 12:07:28 +02:00
R Midhun Suresh 6ba5fbeebb
Merge pull request #723 from vector-im/implement-609
Read config.json on app start
2022-04-22 15:00:02 +05:30
RMidhunSuresh d8da128780 remove await 2022-04-22 14:34:16 +05:30
RMidhunSuresh 7a33c2e00d await 2022-04-22 12:26:29 +05:30
RMidhunSuresh 5a94a2feba Move handleConfigRequest inside handleRequest 2022-04-22 12:22:30 +05:30
RMidhunSuresh c6691cf1cb Simplify code 2022-04-22 12:10:25 +05:30
RMidhunSuresh 826835e518 No need to rewrite to index.html 2022-04-22 12:07:53 +05:30
RMidhunSuresh b6e55ef59c Remove comment 2022-04-21 14:46:55 +05:30
RMidhunSuresh 4f23944581 Use named param in Legacy Platform 2022-04-21 14:17:47 +05:30
RMidhunSuresh 1cdc76f5a4 Use undefine instead of null 2022-04-21 14:14:38 +05:30
RMidhunSuresh 468b7e1595 Cache config.json 2022-04-21 12:52:42 +05:30
Eric Eastwood ce289baba6 Remove extra space 2022-04-20 17:32:12 -05:00
Eric Eastwood f1e07b6842 Explain what is being deleted by the strange syntax
See https://github.com/vector-im/hydrogen-web/pull/693#discussion_r815284713
2022-04-20 11:59:49 -05:00
Eric Eastwood e9cee2e6a4 Merge branch 'master' into madlittlemods/686-682-local-friendly-development-and-commonjs
Conflicts:
	scripts/sdk/build.sh
2022-04-20 11:58:39 -05:00
Eric Eastwood 5f8a171c2c
Fix asset build throwing and swallowing errors (#721)
- Fix `svg-colorizer` throwing errors with Windows file paths
 - Fix `css-url-parser` swallowing errors because it was `async`
 - Fail SDK build script (`yarn build:sdk`, `build.sh`) overall when some commands are failing
2022-04-20 11:55:48 -05:00
RMidhunSuresh 6cd3c8ee2b Read config from URL 2022-04-20 12:42:07 +05:30
RMidhunSuresh 2cfcd4653f Use named params 2022-04-20 12:00:33 +05:30
Eric Eastwood f56dc582a5 Fix tests after theme updates 2022-04-20 00:39:32 -05:00
Eric Eastwood f61bf6090e Enable extended globs for removing all but some filename !(filename)
See https://github.com/vector-im/hydrogen-web/pull/693#discussion_r853534719
2022-04-19 17:28:09 -05:00
Eric Eastwood 12d6447b06 Merge branch 'master' into madlittlemods/686-682-local-friendly-development-and-commonjs
Conflicts:
	package.json
	scripts/sdk/base-manifest.json
	scripts/sdk/build.sh
2022-04-19 17:19:13 -05:00
Bruno Windels 480c5c1584 update SDK docs with new style location 2022-04-14 13:49:54 +02:00
Bruno Windels 2d6cbcfce0 release v0.2.28 2022-04-14 10:38:17 +02:00
Bruno Windels 78f352b839 avoid white ring around avatars in dark theme 2022-04-14 10:38:08 +02:00
Bruno Windels cbdd7548da release v0.2.27 2022-04-14 09:53:21 +02:00
Bruno Windels 3b74e2ea7e
Merge pull request #712 from vector-im/theme-convert
Theming - Convert existing theme to use new theming architecture
2022-04-14 09:52:38 +02:00
RMidhunSuresh 3f4dddc004 Add backwards compatibility 2022-04-13 17:49:38 +05:30
RMidhunSuresh 5170329c79 Remove unsued imports 2022-04-13 17:44:07 +05:30
RMidhunSuresh 2d8a3d9f9b Fix SDK build 2022-04-13 17:12:38 +05:30
RMidhunSuresh 83dffef47d Use new theme config 2022-04-13 14:26:40 +05:30
RMidhunSuresh 23aac5cb45 Make theme-name lowercase in manifest 2022-04-13 14:26:40 +05:30
RMidhunSuresh f7bfab6e08 Add develop only script tag to index.html 2022-04-13 14:26:40 +05:30
RMidhunSuresh 5e7432b5de Make badge font color always white 2022-04-13 14:26:40 +05:30
RMidhunSuresh 2de0450e97 Make colors better looking for dark variant 2022-04-13 14:26:40 +05:30
RMidhunSuresh f26b51e5da Change colors in more css files 2022-04-13 14:26:40 +05:30
RMidhunSuresh bf74c3c67b Add more colors to manifest 2022-04-13 14:26:40 +05:30
RMidhunSuresh 3d304be211 Convert theme.css
- Use color variables
- Use colorized icons
2022-04-13 14:26:40 +05:30
RMidhunSuresh 698d47e221 Enable plugins in config 2022-04-13 14:26:40 +05:30
RMidhunSuresh 3e2a2b7942 Add theme manifest 2022-04-13 14:26:40 +05:30
RMidhunSuresh 061dc5f824 Replace icon colors with predefined color 2022-04-13 14:26:40 +05:30
R Midhun Suresh 366e75b242
Merge pull request #716 from vector-im/vite-plugin-dev
Theming - Support theming in dev server
2022-04-13 14:20:04 +05:30
R Midhun Suresh b76fb70579
Merge pull request #717 from vector-im/fix-css-url-processor
Theming - Fix css-url-processor
2022-04-13 14:19:36 +05:30
R Midhun Suresh aacd0e6dfb
Merge pull request #718 from vector-im/fix-css-compile-variables
Theming - Some more changes for compile-variables plugin
2022-04-13 14:19:21 +05:30
RMidhunSuresh bf0cdcd3f1 Add explaining comment 2022-04-13 13:39:20 +05:30
RMidhunSuresh 825c9847fe Don't hardcode theme/variant names 2022-04-13 12:56:14 +05:30
RMidhunSuresh 14523ecc5d Use new theme option in vite-config 2022-04-13 12:40:49 +05:30
RMidhunSuresh efef7147af Modify jsdoc comment 2022-04-12 21:02:30 +05:30
RMidhunSuresh 39bc827aaf Invert operation for dark theme 2022-04-12 20:58:14 +05:30
RMidhunSuresh bb9954a36c Let derive function know if theme is dark 2022-04-12 20:57:43 +05:30
RMidhunSuresh 0b241db058 Produce a mapping of aliases to resolved colors 2022-04-12 20:57:03 +05:30
RMidhunSuresh 743bd0db1c Support dark mode and remove dev script tag 2022-04-12 20:39:04 +05:30
RMidhunSuresh 25a8521efc Use hash instead of UUID 2022-04-12 20:15:14 +05:30
RMidhunSuresh 36782fb4fe Use unique filenames
Otherwise newly produced svgs will replace other svgs produced earlier
in the build.
2022-04-12 19:44:29 +05:30
RMidhunSuresh 6456d4ef76 Cache cssPath 2022-04-10 14:59:42 +05:30
RMidhunSuresh 49535807bf Do not run plugin on runtime theme 2022-04-10 14:59:08 +05:30
RMidhunSuresh 0a95eb0940 Fix formatting 2022-04-10 14:52:26 +05:30
RMidhunSuresh ff98ef4465 Support theming in dev server 2022-04-10 14:49:19 +05:30
Bruno Windels a6b6fef6d2 sdk release 0.0.10 2022-04-08 17:48:20 +02:00
Bruno Windels c9bc080aef
Merge pull request #713 from vector-im/bwindels/fix-request-responsecode-error
fix error thrown during request when response code is not used
2022-04-08 15:26:12 +02:00
Bruno Windels 4cbd149c25
Merge pull request #715 from vector-im/bwindels/rename-viewclassfortile
Some timeline refactoring and also make reply tiles of correct custom view class
2022-04-08 15:19:39 +02:00
Bruno Windels cf780ce259 also apply custom tiles in reply preview in composer 2022-04-08 15:16:22 +02:00
Bruno Windels d21d10e4f2 pass in viewClassForTile from SessionView
so you can also use custom tiles when using the grid view
2022-04-08 15:15:21 +02:00
Bruno Windels 1fea14dd10 ensure other parameters don't get passed to TemplateView parent ctors 2022-04-08 15:04:38 +02:00
Bruno Windels 1f0cb542c8 pass viewClassForTile to tile views, so they can create reply view with correct subtile 2022-04-08 15:02:07 +02:00
Bruno Windels 57f50cc416 fix lint warnings 2022-04-08 15:01:27 +02:00
Bruno Windels cda96a35ee rename viewClassForEntry to viewClassForTile 2022-04-08 15:01:06 +02:00
Bruno Windels e977a6829b
Merge pull request #714 from vector-im/bwindels/custom-tiles
Allow custom timeline tiles for SDK usage
2022-04-08 14:29:54 +02:00
Bruno Windels ac4bb8ca15 export tile view & view models from SDK 2022-04-08 14:27:08 +02:00
Bruno Windels a913671f0c make tileClassForEntry optional, as otherwise it is a breaking change 2022-04-08 14:19:34 +02:00
Bruno Windels 5445db2a42 allow injecting the tilesCreator from the Root/Session/RoomViewModel
this changes the API slightly to be more future-proof,
as we'll expose it in the SDK now.

The function now returns a SimpleTile constructor, rather than an
instance. This allows us to test if an entry would render in the
timeline without creating a tile, which is something we might want in
the matrix layer later on.

The function is now called tileClassForEntry, analogue to what we
do in TimelineView.
2022-04-08 12:52:30 +02:00
Bruno Windels 220f35ae03 fix typescript error 2022-04-08 11:52:21 +02:00
Bruno Windels 6aa79cf6e2 allow to inject custom tile view creator fn into timeline view 2022-04-07 17:25:20 +02:00
Bruno Windels 88482292e1
Merge pull request #700 from vector-im/ajbura-patch-2
Add observeNavigation in ViewModel
2022-04-07 14:08:40 +02:00
Bruno Windels 9755062563 fix error thrown during request when response code is not used 2022-04-07 10:35:00 +02:00
R Midhun Suresh 0a225292f0
Merge pull request #704 from vector-im/vite-plugin
Theming - Rollup plugin to enumerate and compile themes (and their variants)
2022-04-07 11:57:03 +05:30
R Midhun Suresh 1b18b1f815
Merge pull request #707 from vector-im/css-url-processor
Theming - Postcss plugin to process URLs
2022-04-07 11:54:53 +05:30
RMidhunSuresh c0fb8a2c77 Throw error if no replacements were made 2022-04-07 11:53:11 +05:30
RMidhunSuresh f2b4f2e069 Remove console.log 2022-04-07 11:53:11 +05:30
RMidhunSuresh 7046fcc7c7 Find list of resolved colors from result
and also throw only if secondary color was provided
2022-04-07 11:53:11 +05:30
RMidhunSuresh 8c6400ab2c utf-8 --> utf8 2022-04-07 11:53:11 +05:30
RMidhunSuresh 5d5eb93baa Implement plugin 2022-04-07 11:53:11 +05:30
R Midhun Suresh 4ded893880
Merge pull request #703 from vector-im/css-url-variables-plugin
Theming - Postcss plugin to replace URL values with css variable
2022-04-07 11:44:46 +05:30
RMidhunSuresh bfd73ae52a Pass derive function as argument 2022-04-07 11:37:20 +05:30
RMidhunSuresh 6d724e27e7 No need to check if icons are already written 2022-04-07 11:35:24 +05:30
RMidhunSuresh 2dd655cd9a Check if icon is in shared var 2022-04-07 11:35:24 +05:30
RMidhunSuresh 9a96112146 Rename function name 2022-04-07 11:35:24 +05:30
RMidhunSuresh 545ff2ec32 Add explaining comment 2022-04-07 11:35:24 +05:30
RMidhunSuresh 5e702171ce Remove console.log 2022-04-07 11:35:24 +05:30
RMidhunSuresh cd4fce0c6f Populate shared map with collected icons 2022-04-07 11:35:24 +05:30
RMidhunSuresh 1a50effd86 Only extract into variables if file is svg 2022-04-07 11:35:24 +05:30
RMidhunSuresh b7a47ae901 Give function better name 2022-04-07 11:35:24 +05:30
RMidhunSuresh 0a186dd11b Fix css logic 2022-04-07 11:35:24 +05:30
RMidhunSuresh f07a3ea5b5 Remove css specific syntax from map 2022-04-07 11:35:24 +05:30
RMidhunSuresh 2d4ec5380e Initialize variables later 2022-04-07 11:35:24 +05:30
RMidhunSuresh 6b4bb762aa Remove unused variable 2022-04-07 11:35:24 +05:30
RMidhunSuresh 97ade0659c Add explaining comment 2022-04-07 11:35:24 +05:30
RMidhunSuresh b59d6970fc Fix code duplication in tests 2022-04-07 11:35:21 +05:30
RMidhunSuresh cbff912476 Improve code quality 2022-04-07 11:34:11 +05:30
RMidhunSuresh 3ae2b4dab4 Use two url() in test 2022-04-07 11:34:11 +05:30
RMidhunSuresh f897e5132c Implement url to variables plugin 2022-04-07 11:34:11 +05:30
R Midhun Suresh e0bc9b31a9
Merge pull request #709 from vector-im/compile-variables-improvement
Theming - postcss-compile-variables improvement
2022-04-07 11:31:59 +05:30
RMidhunSuresh f75ee86c0e Change comment 2022-04-06 12:30:26 +05:30
RMidhunSuresh 7f9af5b5fa Add icon to manifest 2022-04-06 12:30:26 +05:30
RMidhunSuresh b0f082e81f Add derived variables to source section 2022-04-06 12:30:26 +05:30
RMidhunSuresh d5b5e10230 Produce manifest.jsom 2022-04-06 12:30:26 +05:30
RMidhunSuresh 86c45b5b99 Emit runtime bundle 2022-04-06 12:30:26 +05:30
RMidhunSuresh 32eb95734a Add default themes to index html 2022-04-06 12:30:26 +05:30
RMidhunSuresh 1f6efb4db3 Write plugin code 2022-04-06 12:30:26 +05:30
RMidhunSuresh 48d0242c80 Also derive variables in URLs 2022-04-06 12:23:55 +05:30
Eric Eastwood 2401b7f453 Add way to test whether SDK works in ESM and CommonJS 2022-04-05 19:24:27 -05:00
Eric Eastwood dd06d78a72 Avoid ERR_REQUIRE_ESM errors when requiring SDK 2022-04-05 18:17:14 -05:00
Eric Eastwood 95d17303c3 Update Vite which includes fixes to importing `*.js?url` with `exports`
Update to Vite which includes https://github.com/vitejs/vite/pull/7098
2022-04-05 17:16:55 -05:00
Eric Eastwood d247bc4e28 Merge branch 'master' into madlittlemods/686-682-local-friendly-development-and-commonjs
Conflicts:
	package.json
	scripts/sdk/base-manifest.json
2022-04-05 17:15:30 -05:00
RMidhunSuresh 454345c9b2 Always set map 2022-04-05 15:08:35 +05:30
RMidhunSuresh 76789eacf1 Use array instead of Set 2022-04-01 20:43:42 +05:30
RMidhunSuresh 859449ed60 Write test for map population 2022-04-01 16:41:00 +05:30
RMidhunSuresh 918a3e42b1 Populate compiled variables map 2022-04-01 16:23:33 +05:30
RMidhunSuresh 4350d2f264 Don't derive variables for runtime theme 2022-04-01 16:20:58 +05:30
RMidhunSuresh 2015fa2d7a Move postcss-value-parser to dev dependency 2022-03-27 20:18:42 +05:30
RMidhunSuresh e8bd1f3390 Pass result as message 2022-03-27 20:06:26 +05:30
R Midhun Suresh 66304ed7e0
Merge pull request #701 from vector-im/css-compile-variables-plugin
Theming - Postcss plugin to compile variables
2022-03-24 12:14:46 +05:30
RMidhunSuresh 72785e7c3e Remove -- from everywhere 2022-03-23 20:39:24 +05:30
RMidhunSuresh 59ca8e6309 Add explanation of plugin 2022-03-23 17:25:12 +05:30
RMidhunSuresh 5d4323cd1d Remove stray "--" from code 2022-03-23 17:12:14 +05:30
RMidhunSuresh 19a6d669a9 Extract base variables from css 2022-03-14 23:26:37 +05:30
RMidhunSuresh bca1648df6 Move plugin to /scripts and create eslintrc 2022-03-14 11:35:10 +05:30
RMidhunSuresh 4020ade70c Remove redundant comment 2022-03-10 17:51:25 +05:30
RMidhunSuresh 2c068cc3ce typo 2022-03-10 17:42:12 +05:30
RMidhunSuresh 6f4a7e074a Change confusing doc 2022-03-10 17:27:12 +05:30
RMidhunSuresh 9f77df0bff Match regex only if declaration is a variable 2022-03-10 17:24:32 +05:30
RMidhunSuresh ff10297bf8 Explicitly convert to number 2022-03-10 17:22:02 +05:30
RMidhunSuresh f732164b5f Formatting change 2022-03-10 17:21:38 +05:30
RMidhunSuresh 5210123977 Document options 2022-03-10 17:19:04 +05:30
RMidhunSuresh 1663782954 Throw after fetching value 2022-03-10 16:05:13 +05:30
RMidhunSuresh 63c1f2a7a3 Add node as env to eslint 2022-03-09 17:22:45 +05:30
RMidhunSuresh 96fa83b508 Add license header 2022-03-09 17:22:11 +05:30
RMidhunSuresh 79f363fb9d Move code to callback and fix alias code 2022-03-09 17:20:05 +05:30
Bruno Windels ca211f929b
Merge pull request #702 from vector-im/bwindels/observablemapts
convert (Base)ObservableMap to typescript
2022-03-09 11:53:59 +01:00
Bruno Windels 6150e91c3f fix type error again 2022-03-09 11:51:11 +01:00
Bruno Windels 762925d4a5 fix type error 2022-03-09 11:44:49 +01:00
Bruno Windels 21080d2110 fix tests 2022-03-09 11:41:26 +01:00
Bruno Windels 6d7c983e8e convert (Base)ObservableMap to typescript 2022-03-09 11:33:49 +01:00
RMidhunSuresh a83850ebf3 Use postcss value parser to find variables 2022-03-09 11:48:53 +05:30
RMidhunSuresh 41f6b6ab6b Use startsWith instead of regex testing 2022-03-07 13:25:53 +05:30
RMidhunSuresh a5d46bb40c Move over tests to Hydrogen using impunity 2022-03-07 13:10:44 +05:30
RMidhunSuresh f170ef0206 Switch over to off-color 2022-03-07 11:38:39 +05:30
Ajay Bura e07abfa02a
Add missing type 2022-03-07 11:33:51 +05:30
RMidhunSuresh b6f5e68e9e Format file 2022-03-07 11:33:44 +05:30
RMidhunSuresh 92084e8005 Move all code under the Once event
Apparently the other events are common to all plugins.
2022-03-07 11:32:30 +05:30
Bruno Windels 8b8233ff00
Merge pull request #691 from vector-im/madlittlemods/only-crypto-in-secure-context
Only initialize `Crypto` when olm is provided
2022-03-03 17:33:50 +01:00
RMidhunSuresh 60d60e9572 WIP 2022-03-03 19:58:46 +05:30
Ajay Bura 61ce2f9e3d
Add observeNavigation in ViewModel 2022-03-03 15:36:25 +05:30
Eric Eastwood 2f4c639cef Only initialize Crypto when olm is provided
See https://github.com/vector-im/hydrogen-web/pull/691#discussion_r816988082
2022-03-02 03:17:59 -06:00
Eric Eastwood c09964dc30
Add `data-event-id="$xxx"` attributes to timeline items for easy selecting in end-to-end tests (#690)
Split out from https://github.com/vector-im/hydrogen-web/pull/653

Example test assertions: db6d3797d7/test/e2e-tests.js (L248-L252)

```js
// Make sure the $abc event on the page has "foobarbaz" text in it
assert.match(
  dom.document.querySelector(`[data-event-id="$abc"]`).outerHTML,
  new RegExp(`.*foobarbaz.*`)
);
```
2022-03-01 18:36:14 -06:00
Bruno Windels 2e1283d199
Merge pull request #670 from vector-im/bwindels/ts-olm
Convert olm code to typescript
2022-03-01 18:53:22 +01:00
Bruno Windels 62ce111938
Merge pull request #692 from ryushar/ryushar/typescriptify
Convert domain/avatar.js and domain/LogoutViewModel.js to Typescript
2022-03-01 18:50:19 +01:00
Bruno Windels 770f7aea00
Merge pull request #689 from vector-im/madlittlemods/add-more-html-elements
Add more HTML form and SVG elements
2022-03-01 18:43:34 +01:00
Bruno Windels b6d9993ed0 remove unused import 2022-03-01 17:08:49 +01:00
Bruno Windels 643ab1a5f3 cant export this for some reason 2022-03-01 15:48:42 +01:00
Bruno Windels 42141c7063 bump SDK version 2022-03-01 15:45:24 +01:00
Bruno Windels 1087d62705
Merge pull request #695 from vector-im/ajbura-patch-1
Export some more symbols from the SDK
2022-03-01 15:44:51 +01:00
Bruno Windels ee8e45926f also export observable value classes 2022-03-01 15:42:04 +01:00
Bruno Windels 4c50dbf7ec make SDK exports explicit 2022-03-01 15:41:44 +01:00
Ajay Bura 4a4856a29e
export module 2022-02-28 17:19:01 +05:30
Eric Eastwood 0023ab34ba Add a placeholder for upgrading vite to comment on 2022-02-26 05:19:59 -06:00
Eric Eastwood 8fb2b2755a Fix typos pointing to wrong files 2022-02-26 03:08:16 -06:00
Eric Eastwood cd007b40e1 Make the SDK friendly to locally link and develop on
Fix https://github.com/vector-im/hydrogen-web/issues/686
Fix https://github.com/vector-im/hydrogen-web/issues/682

Instead of deleting the whole `target/` directory, leave it alone so the symlink
driving the `npm link`/`yarn link` stays in tact.

Leave Vite builds in their build directories (`/lib-build`/`/asset-build`)
so you can `vite build --watch` to build on local changes and still have a
consisent place to reference in the `package.json` `exports`. Previously,
everything relied on `build.sh` which does a bunch of moving and renaming
and made it hard to rebuild on changes.

Add back support for CommonJS (adding the `package.json` `exports`).

The last piece is making sure the `?url` imports (`import workerPath from 'hydrogen-view-sdk/main.js?url';`)
work still. It looks like this may have just been solved via
https://github.com/vitejs/vite/issues/6725 -> https://github.com/vitejs/vite/pull/7073
(literally 2 days ago) and we just need to wait for the next Vite release 🎉
2022-02-26 01:12:00 -06:00
Tushar 17acda7741 typescriptify domain/LogoutViewModel.js 2022-02-25 16:45:07 +05:30
Tushar 7055f02f16 typescriptify domain/avatar.js 2022-02-25 15:52:54 +05:30
Eric Eastwood 0935f2d23a Only try to use window.crypto.subtle in secure contexts to avoid it throwing and stopping all JavaScript
Relevant error if you crypto is used in a non-secure context like a local LAN IP `http://192.168.1.151:3050/`
```
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'deriveBits')
	at new Crypto
	at new Platform
	at mountHydrogen
```

For my use-case with https://github.com/matrix-org/matrix-public-archive, I don't need crypto/encryption at all.

Docs:

 - https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts
 - https://developer.mozilla.org/en-US/docs/Web/API/Crypto/subtle
    - "Secure context: This feature is available only in secure contexts (HTTPS), in some or all supporting browsers."

---

Related to https://github.com/vector-im/hydrogen-web/issues/579
2022-02-25 01:59:48 -06:00
Eric Eastwood b993331e06 Add more HTML form and SVG elements
Split off from https://github.com/vector-im/hydrogen-web/pull/653

Personally using `select`, `option`, and `path` currently in https://github.com/matrix-org/matrix-public-archive
but added a few extra SVG elements that seemed common to me.
2022-02-25 01:40:52 -06:00
Bruno Windels 8adc5a9fae these were public actually 2022-02-18 17:24:55 +01:00
Bruno Windels 3f9f0e98c7 remove unused olm property in SenderKeyDecryption 2022-02-18 17:21:27 +01:00
Bruno Windels 82299e5aea
Update src/matrix/e2ee/olm/Decryption.ts
Co-authored-by: R Midhun Suresh <hi@midhun.dev>
2022-02-18 17:18:33 +01:00
Bruno Windels 3330530f68
Update src/matrix/e2ee/DecryptionResult.ts
Co-authored-by: R Midhun Suresh <hi@midhun.dev>
2022-02-18 17:18:25 +01:00
Bruno Windels 620409b3f0 fixup: ctor argument order
as it was an object before, order didn't matter
2022-02-18 17:17:24 +01:00
Bruno Windels 78e0bb1ff0 replace isPreKeyMessage with const enum 2022-02-18 17:00:56 +01:00
Bruno Windels 347edb5988 remove unused storage property 2022-02-18 16:47:47 +01:00
Bruno Windels 0ff1a01b42
Merge pull request #669 from vector-im/bwindels/contrib
Add guide for new contributers
2022-02-17 17:46:57 +01:00
Bruno Windels 91fd0e433a remove changelog notes remainder 2022-02-17 17:44:44 +01:00
Bruno Windels cdd6112971 finish adapting contribution guide 2022-02-17 17:39:45 +01:00
Bruno Windels ac48a5a4df bump SDK version to 0.0.8 2022-02-17 10:10:23 +01:00
Bruno Windels 49f6a2c2eb
Merge pull request #679 from vector-im/bwindels/fix-vm-ctor-default-options
always pass options to ViewModel constructor
2022-02-17 10:10:19 +01:00
Bruno Windels 2821f4d396
Merge pull request #680 from vector-im/bwindels/export-roomstatus
export RoomStatus in SDK
2022-02-17 09:51:12 +01:00
Bruno Windels 2472f11ec0 export RoomStatus 2022-02-17 09:47:57 +01:00
Bruno Windels 7f1fed6f8c always pass options to ViewModel constructor 2022-02-17 09:24:18 +01:00
Bruno Windels d971fd1a47
Merge pull request #678 from vector-im/fix-viewmodel-error
Check options exist on emitChange
2022-02-17 09:08:54 +01:00
RMidhunSuresh 498a43327f Check if options exist in emitChange 2022-02-17 11:30:04 +05:30
Bruno Windels d9acc83182
Merge pull request #675 from vector-im/bwindels/fix-lint-timeline-import
fix lint
2022-02-16 18:05:52 +01:00
Bruno Windels 60f5da60bb fix lint 2022-02-16 18:01:24 +01:00
Bruno Windels e3e90ed167 convert olm/Encryption to TS 2022-02-16 18:00:13 +01:00
Bruno Windels eb5ca200f2 missed rename here 2022-02-16 18:00:03 +01:00
Bruno Windels 61b264be3b bump sdk version to 0.0.7 2022-02-16 10:20:53 +01:00
Bruno Windels 37cec04e9c
Merge pull request #671 from vector-im/token-auth-registration
Implement token authenticated registration
2022-02-16 10:20:33 +01:00
RMidhunSuresh 7a9298328f Return _type from getter 2022-02-16 14:37:18 +05:30
RMidhunSuresh a76bcd1739 Changes in TokenAuth 2022-02-16 13:36:24 +05:30
RMidhunSuresh 60bc4450f3 Use type from server 2022-02-16 13:21:04 +05:30
RMidhunSuresh ed151c8567 Return token stage from createRegistrationStage 2022-02-16 12:33:59 +05:30
RMidhunSuresh c40801efd9 Implement the registration stage 2022-02-16 12:33:24 +05:30
Bruno Windels a4fd1615dd convert decryption 2022-02-15 18:21:29 +01:00
Bruno Windels 74c640f937 convert Session 2022-02-15 18:21:12 +01:00
Bruno Windels 7aeda70ff6 convert DecryptionResult 2022-02-15 18:20:14 +01:00
Bruno Windels c6dde63abd
Merge pull request #668 from vector-im/bwindels/ts-viewmodel
convert ViewModel to typescript
2022-02-15 15:38:22 +01:00
Bruno Windels dea1e7eaf3 bump sdk version 2022-02-15 11:31:50 +01:00
Bruno Windels 7179758c50 also here 2022-02-15 08:22:09 +01:00
Bruno Windels 1a159f9e9a WIP 2022-02-14 18:01:04 +01:00
Bruno Windels 1795f58ba5 rename imports 2022-02-14 17:53:59 +01:00
Bruno Windels 4d82dd22b6 convert ViewModel to typescript 2022-02-14 17:50:17 +01:00
Bruno Windels 460780d602
Merge pull request #666 from vector-im/madlittlemods/explicit-domparser-document-for-consistent-return-with-linkedom-ssr
Fix missing reply text when message body is parsed as HTML in `linkedom` (SSR)
2022-02-14 09:57:31 +01:00
Eric Eastwood dfed04166e Fix missing reply text when message body is parsed as HTML in [`linkedom`](https://github.com/WebReflection/linkedom) (SSR).
- [`linkedom`](https://github.com/WebReflection/linkedom) is being used https://github.com/matrix-org/matrix-public-archive to server-side render (SSR) Hydrogen (`hydrogen-view-sdk`)
 - This is being fixed by using a explicit HTML wrapper boilerplate with `DOMParser` to get a matching result in the browser and `linkedom`.

Currently `parseHTML` is only used for HTML content bodies in events. Events with replies have content bodies that look like `<mx-reply>Hello</mx-reply> What's up` so they're parsed as HTML to strip out the `<mx-reply>` part.

Before | After
---  |  ---
![](https://user-images.githubusercontent.com/558581/153692011-2f0e7114-fcb4-481f-b217-49f461b1740a.png) | ![](https://user-images.githubusercontent.com/558581/153692016-52582fdb-abd9-439d-9dce-3f04da6959db.png)

Before:
```js
// Browser (Chrome, Firefox)
new DOMParser().parseFromString(`<div>foo</div>`, "text/html").body.outerHTML;
// '<body><div>foo</div></body>'

// `linkedom` 
new DOMParser().parseFromString(`<div>foo</div>`, "text/html").body.outerHTML;
// '<body></body>'
```

After (consistent matching output):

```js
// Browser (Chrome, Firefox)
new DOMParser().parseFromString(`<!DOCTYPE html><html><body><div>foo</div></body></html>`, "text/html").body.outerHTML;
// '<body><div>foo</div></body>'

// `linkedom`
new DOMParser().parseFromString(`<!DOCTYPE html><html><body><div>foo</div></body></html>`, "text/html").body.outerHTML;
// '<body><div>foo</div></body>'
```

`linkedom` goal is to be close to the current DOM standard, but [not too close](https://github.com/WebReflection/linkedom#faq). Focused on the streamlined cases for server-side rendering (SSR).

Here is some context around getting `DOMParser` to interpret things better. The conclusion was to only support the explicit standard cases with a `<html><body></body></html>` specified instead of adding the magic HTML document creation and massaging that the browser does.

 - https://github.com/WebReflection/linkedom/issues/106
 - https://github.com/WebReflection/linkedom/pull/108

 ---

Part of https://github.com/vector-im/hydrogen-web/pull/653 to support server-side rendering Hydrogen for the [`matrix-public-archive`](https://github.com/matrix-org/matrix-public-archive) project.
2022-02-11 20:10:46 -06:00
Bruno Windels 75e2618f70
Merge pull request #664 from vector-im/bwindels/onlylogsummarykeys
dont log summary valued, as they can contain PII
2022-02-11 18:41:26 +01:00
Bruno Windels 9685ef4dd3 dont log summary valued, as they can contain PII 2022-02-11 18:39:37 +01:00
Bruno Windels 750f3cd8ff release v0.2.26 2022-02-11 17:20:54 +01:00
Bruno Windels 34ec0e2c82
Merge pull request #663 from vector-im/bwindels/fix-reply-not-loading
Fix replies from /context not loading in e2ee rooms
2022-02-11 17:20:06 +01:00
Bruno Windels ea8f3e5a6a remove argument that is already bound in BaseRoom, making decryption fail 2022-02-11 17:14:56 +01:00
Bruno Windels a184ad528f
Merge pull request #654 from vector-im/bwindels/create-room
Create rooms
2022-02-11 16:56:24 +01:00
Bruno Windels 57b1542688 use private topic field as public one got removed as not needed in view 2022-02-11 09:37:56 +01:00
Bruno Windels 175f869c83 fix lint 2022-02-10 20:07:27 +01:00
Bruno Windels a442b4b009 Merge branch 'master' into bwindels/create-room 2022-02-10 20:05:40 +01:00
Bruno Windels d65b25f084 also adjust m.direct if the room has already been replaced 2022-02-10 20:00:01 +01:00
Bruno Windels 2765f48a64 create user id array in m.direct if it doesn't exist already 2022-02-10 19:59:44 +01:00
Bruno Windels d2008a336b fix lint errors 2022-02-10 19:54:47 +01:00
Bruno Windels ff46d382ac adjust m.direct when creating a DM 2022-02-10 19:54:15 +01:00
Bruno Windels 3adb2c3254 fix ts errors 2022-02-10 16:44:40 +01:00
Bruno Windels 8526461d3c split up create code into separate files 2022-02-10 16:43:32 +01:00
Bruno Windels 15eecbb463 cleanup 2022-02-10 16:28:44 +01:00
Bruno Windels 30c8ea29b2 fix bug where the wrong left panel tile is removed when accepting invite
because when comparing a tile to itself it wasn't returned 0
2022-02-10 16:27:32 +01:00
Bruno Windels b0d790543a push to navigation in SessionViewModel rather than RVO 2022-02-10 14:57:48 +01:00
Bruno Windels 2c1b29e637 remove logging 2022-02-10 14:39:41 +01:00
Bruno Windels 75bbde598d also consider rooms without a name and just you and the other a DM
as we don't process m.direct account data yet
2022-02-10 14:39:18 +01:00
Bruno Windels 955a6bd6f9 styling for button in member details to open DM 2022-02-10 14:38:12 +01:00
Bruno Windels 147810864f add support to set alias and federation flag in create room 2022-02-10 14:09:18 +01:00
Bruno Windels 4c0167ed74 don't show spinner in left panel when room creation fails 2022-02-10 11:19:43 +01:00
Bruno Windels 024a6c06aa handle offline error nicer 2022-02-10 11:11:15 +01:00
Bruno Windels b5536830d0 improve RoomBeingCreatedView, allow removing the roombeingcreated 2022-02-10 11:07:29 +01:00
Bruno Windels 20493f9e87 cleanup 2022-02-10 11:07:13 +01:00
Bruno Windels e8c20c28b2 allow passing label into LoadingView
also doesn't need to be a template view, as it doesn't have bindings
or event handlers
2022-02-10 11:06:44 +01:00
Bruno Windels f12841b2d3 better error handling in RoomBeingCreated 2022-02-10 11:06:20 +01:00
Bruno Windels d6d1af13d0 rename RoomBeingCreated.localId to id 2022-02-10 11:03:52 +01:00
Bruno Windels bbb1683dbf fixup: login view styling 2022-02-10 09:40:42 +01:00
Bruno Windels fed42f13ad textarea styling 2022-02-10 09:40:30 +01:00
Bruno Windels 5f6308e7c4 fix homeserver field style in login view 2022-02-10 09:40:19 +01:00
Bruno Windels 74f7879cb6 fix unrelated bug: invite sorting order wasn't stable in left panel
as the timestamp is the same when you receive the invite during your
first sync
2022-02-10 09:40:03 +01:00
Bruno Windels 5c085efc10 create room view and view model 2022-02-09 19:02:51 +01:00
Bruno Windels a1e14c4eec rename to not have conflict between method name and instance of CreateRoomViewModel 2022-02-09 19:02:18 +01:00
Bruno Windels 4b1be30dc0 improve form-row classes so they can work with create room form 2022-02-09 19:01:35 +01:00
Bruno Windels 8523f6feaf setup navigation for create room form 2022-02-09 19:00:41 +01:00
Bruno Windels 83d2b58bad add avatar support to creating room 2022-02-09 19:00:00 +01:00
Bruno Windels afe8e17a6f remove debugging code 2022-02-08 17:00:06 +01:00
Bruno Windels 743f2270e5 have a single tile view that supports all 3 view models 2022-02-08 16:22:44 +01:00
Bruno Windels 5325b0b466 cleanup logging 2022-02-08 14:58:29 +01:00
Bruno Windels d7b024eac1 unrelated fix: encode user name in matrix.to link 2022-02-08 14:35:14 +01:00
Bruno Windels 45c8e3a793 mark room as DM based on synced state events,rather than just inviteData
as that does not work for rooms we create ourselves
2022-02-08 14:34:34 +01:00
Bruno Windels e04463c143 WIP for finding DM room 2022-02-07 18:58:53 +01:00
Bruno Windels 26fa2a5d60 add option 2022-02-07 18:58:43 +01:00
Bruno Windels e1fbd1242e WIP 4 2022-02-07 16:30:44 +01:00
Bruno Windels b868734378 change sdk version 2022-02-07 11:05:28 +01:00
Bruno Windels 0bb3cfcfad WIP3 2022-02-04 17:49:10 +01:00
Bruno Windels 3ff39a9549
Merge pull request #661 from vector-im/sdk-additions
Export more code from SDK
2022-02-04 17:43:23 +01:00
Bruno Windels 94709fd316
Merge pull request #623 from vector-im/registration
Bootstrap enough registration functionality for embedded-hydrogen work
2022-02-04 17:43:02 +01:00
RMidhunSuresh 4a0db9f984 Add required exports 2022-02-04 18:28:17 +05:30
RMidhunSuresh 28931f4103 Use async/await 2022-02-04 17:48:42 +05:30
RMidhunSuresh f7f32ac806 responseCodeReject may not exist 2022-02-04 17:39:52 +05:30
RMidhunSuresh a163cee18d Remove dead imports 2022-02-04 17:25:30 +05:30
RMidhunSuresh 0828ac12b1 Fix params 2022-02-04 17:25:15 +05:30
RMidhunSuresh b59f916824 Merge branch 'registration' of github.com:vector-im/hydrogen-web into registration 2022-02-04 17:16:32 +05:30
R Midhun Suresh 2ac63e78ca
mark method as internal
Co-authored-by: Bruno Windels <bruno@windels.cloud>
2022-02-04 17:16:15 +05:30
RMidhunSuresh 028b96e4c5 Let type also be undefined 2022-02-04 17:11:33 +05:30
RMidhunSuresh 22d5505a2b Create registration stage in Registration itself 2022-02-04 16:50:22 +05:30
RMidhunSuresh e66549a067 Remove dead code 2022-02-04 16:40:49 +05:30
RMidhunSuresh e8c480426a Remove error code 2022-02-04 16:37:43 +05:30
RMidhunSuresh 891375a885 Rename allowerErrors -> allowedStatusCodes 2022-02-04 16:35:47 +05:30
RMidhunSuresh 32af7e6f09 Make more changes
- make setter a method
- lazily create promise
2022-02-04 16:23:39 +05:30
Bruno Windels 0b04612d6c WIP2 2022-02-04 11:16:58 +01:00
RMidhunSuresh 3d8b9cce41 Fix responseCode in Request 2022-02-04 15:41:37 +05:30
Bruno Windels bc09ede09f WIP 2022-02-03 17:57:35 +01:00
RMidhunSuresh b6e1d4a7d5 Implement responseCode() 2022-02-03 19:41:14 +05:30
RMidhunSuresh 89a97537b0 Make methods private + some props readonly 2022-02-03 19:41:14 +05:30
RMidhunSuresh 8a3c0afba6 Fix incorrect types 2022-02-03 19:41:11 +05:30
RMidhunSuresh 0ad0ecfcc2 Check response code instead of existence of props 2022-02-03 19:40:25 +05:30
RMidhunSuresh c4894f2c24 completed is not always present 2022-02-03 19:40:25 +05:30
RMidhunSuresh e64f4ad7b2 Refactor code
- Move all code that does /register to Registration.ts
- RegistrationStage only deals with the generation of auth data
- Change API so that undefined is returned instead of string when
  registration is over
2022-02-03 19:40:25 +05:30
R Midhun Suresh 2aad5546bf No need for Object.assign here either
Co-authored-by: Bruno Windels <brunow@matrix.org>
2022-02-03 19:40:25 +05:30
RMidhunSuresh 7bacbec5e9 Remove type directory 2022-02-03 19:40:25 +05:30
RMidhunSuresh e13040a49e Don't mutate flows 2022-02-03 19:40:25 +05:30
R Midhun Suresh 30cb9f6d15 Use includes instead of elaborate find
Co-authored-by: Bruno Windels <brunow@matrix.org>
2022-02-03 19:40:25 +05:30
RMidhunSuresh a351a185a0 Give proper names 2022-02-03 19:40:25 +05:30
RMidhunSuresh fe0add01ee Use union of types for RegistrationResponse 2022-02-03 19:40:25 +05:30
RMidhunSuresh a249a1b2b5 Implement flow seclector 2022-02-03 19:40:25 +05:30
RMidhunSuresh 6798a5e429 Move types to types.ts 2022-02-03 19:40:25 +05:30
RMidhunSuresh 3a67da8830 Refactor type
- Change name
- Move union type down
2022-02-03 19:40:25 +05:30
RMidhunSuresh 1d4b079d0c Type RegistrationResponse 2022-02-03 19:40:25 +05:30
RMidhunSuresh 49ade61ef6 Fill in ts types + change names 2022-02-03 19:40:25 +05:30
RMidhunSuresh b482d478b4 Add a tos getter 2022-02-03 19:40:25 +05:30
RMidhunSuresh ac7108b882 Throw error instead of returning it 2022-02-03 19:40:25 +05:30
RMidhunSuresh 7bb7189c6a No need for this export 2022-02-03 19:40:25 +05:30
RMidhunSuresh 6eba60bd75 Use typescript style that was agreed on earlier 2022-02-03 19:40:25 +05:30
RMidhunSuresh 5de1fc1453 Remove unnecessary getters 2022-02-03 19:40:25 +05:30
RMidhunSuresh 2f3865d8cc firstStage should be a local variable 2022-02-03 19:40:25 +05:30
RMidhunSuresh 2d4c106542 REFACTOR: Inline method 2022-02-03 19:40:25 +05:30
RMidhunSuresh a91ba4370d Change type to show that username is optional 2022-02-03 19:40:25 +05:30
RMidhunSuresh 550a560f40 Remove space 2022-02-03 19:40:25 +05:30
RMidhunSuresh 5f11790f6b Object.assign is overkill here 2022-02-03 19:40:25 +05:30
RMidhunSuresh e8dbbd876c Give default values to parameters 2022-02-03 19:40:25 +05:30
RMidhunSuresh 755f934eb2 No need to explicitly pass in inhibitLogin 2022-02-03 19:40:25 +05:30
RMidhunSuresh 5e93e048ab Don't cache GET requests 2022-02-03 19:40:25 +05:30
RMidhunSuresh bb6a885116 Specify what errors are ignored in options 2022-02-03 19:40:25 +05:30
RMidhunSuresh 420c12f202 Copy over username only if it exists 2022-02-03 19:40:25 +05:30
RMidhunSuresh 792d5c62c5 Return username when registration is completed 2022-02-03 19:40:25 +05:30
RMidhunSuresh fa2e2bc8f3 Allow register without providing username 2022-02-03 19:40:25 +05:30
RMidhunSuresh 170d7a5e55 Add startRegistration method 2022-02-03 19:40:25 +05:30
RMidhunSuresh 8ab8726b8f Implement m.login.terms stage 2022-02-03 19:40:25 +05:30
RMidhunSuresh 18e2fc1089 Pass in params to BaseRegistrationStage 2022-02-03 19:40:25 +05:30
RMidhunSuresh a59b67ec45 Fix errors 2022-02-03 19:40:25 +05:30
RMidhunSuresh d76a059525 Temporary fix for 401 errors 2022-02-03 19:40:25 +05:30
RMidhunSuresh d28ab919bb Implement dummy registration logic 2022-02-03 19:40:25 +05:30
RMidhunSuresh eb146830ba Implement registration endpoint 2022-02-03 19:40:25 +05:30
RMidhunSuresh 618d02d838 fetch registration flows 2022-02-03 19:40:25 +05:30
Bruno Windels 348de312f9 draft code in matrix layer to create room 2022-02-02 10:19:49 +01:00
Bruno Windels 65dcf8bc36 release v0.2.25 2022-02-01 12:34:42 +01:00
Bruno Windels 2e3616e05d call cursor.update during backup field migration, needs new version 2022-02-01 12:31:10 +01:00
Bruno Windels 00c5e747d2 log total backed up keys during flush operation 2022-02-01 12:30:45 +01:00
Bruno Windels b29ecd339d add more logging to backup storage migration 2022-02-01 12:18:28 +01:00
Bruno Windels c6820eccab release v0.2.24 2022-02-01 11:58:47 +01:00
Bruno Windels 247d13f97a
Merge pull request #651 from vector-im/bwindels/write-session-backup
Session backup writing
2022-02-01 11:54:53 +01:00
Bruno Windels f4fa013ebc mark as not configured yet when re-enabling key backup 2022-02-01 11:32:53 +01:00
Bruno Windels f4bb420f35 mark key backup properly as disabled 2022-02-01 11:27:42 +01:00
Bruno Windels 02f06724d0 don't block reenabling 4s if already enabled 2022-02-01 11:26:00 +01:00
Bruno Windels fd4eb6b50d distinguish between "waiting to go online" vs "backup not configured" 2022-02-01 11:08:13 +01:00
Bruno Windels 997666164c remove unused enum variants 2022-01-31 17:37:44 +01:00
Bruno Windels 9c599d53aa allow to inject max delay in key backup 2022-01-31 17:31:01 +01:00
Bruno Windels 62acd458c6 also ask for new key if backup version is not found 2022-01-31 17:30:51 +01:00
Bruno Windels 17275a5390 backup 200 keys per request 2022-01-31 17:30:15 +01:00
Bruno Windels 830786b2fd fixes and cleanup 2022-01-31 16:26:14 +01:00
Bruno Windels 06a1421e97 add backupWriteStatus so binding can take multiple fields into account 2022-01-31 16:26:06 +01:00
Bruno Windels 6541aacf98 don't discount already finished keys in total for previous iterations 2022-01-31 16:23:48 +01:00
Bruno Windels dacaa86386 fix percentage calculation 2022-01-31 16:22:22 +01:00
Bruno Windels a757fb3696 better error handling in key backup, cleanup and not overuse observables 2022-01-31 14:37:05 +01:00
Bruno Windels 7eb0d347f5 flush key backup after coming online 2022-01-31 14:36:35 +01:00
Bruno Windels ae5cc17290 mark all inbound sessions to be backed up again when changing version 2022-01-31 14:36:04 +01:00
Bruno Windels d9e6164a5c fix ts errors 2022-01-28 16:40:32 +01:00
Bruno Windels a97d235cf5 flush after enabling key backup 2022-01-28 16:36:42 +01:00
Bruno Windels c9b5ce6508 clean up key backup vm using flatMap to avoid subscription handling 2022-01-28 16:36:13 +01:00
Bruno Windels e0df003aba add flatMap operator on observable value 2022-01-28 16:35:49 +01:00
Bruno Windels c340746a87 also remove text nodes when updating message body
fixes #649
2022-01-28 16:04:56 +01:00
Bruno Windels eabd303c8e count on the index if we're using one, don't always take the store 2022-01-28 15:14:58 +01:00
Bruno Windels bd2c70b923 adapt key backup view(model) to changes in session, show backup progress 2022-01-28 15:14:23 +01:00
Bruno Windels 504f420293 make keyBackup an observable and don't have separate needs-key flag 2022-01-28 15:13:58 +01:00
Bruno Windels eb134a6c47 only take into account non-backed up keys for counting 2022-01-28 13:18:03 +01:00
Bruno Windels 7d3e3b992b some more typing 2022-01-28 13:14:38 +01:00
Bruno Windels c47bdd5715 flush key backup when creating a new room key 2022-01-28 13:14:11 +01:00
Bruno Windels b692b3ec4f move key backup operation and flush bookkeeping inside KeyBackup
so we can flush from other places than Session
2022-01-28 13:13:23 +01:00
Bruno Windels ebc7f1ecd7 needs to be awaited 2022-01-28 13:11:52 +01:00
Bruno Windels b30db544a3 use idb key range to select non-backed up keys 2022-01-28 13:11:32 +01:00
Bruno Windels a499689bd8 also write room key that we create ourselves with RoomKey infrastructure
so all keys are written in one place and the flags are always correct
2022-01-28 13:10:48 +01:00
Bruno Windels c81dde53e7 store key source in inbound session 2022-01-28 10:03:30 +01:00
Bruno Windels dd2b41ff95 use backup flag in key backup rather than separate store 2022-01-27 16:07:18 +01:00
Bruno Windels 48e72f9b69 replace SessionsNeedingBackup store with backup field on inbound session 2022-01-27 16:00:46 +01:00
Bruno Windels 6f1484005b stop key backup when on the wrong version
users can then enter the new key in the settings to start backing up
again
2022-01-27 15:14:29 +01:00
Bruno Windels 0b4954a9ca log key backup upload requests 2022-01-27 14:20:04 +01:00
Bruno Windels bf08c0d850 deal with errors when enabling key backup
fixes #449
2022-01-27 14:19:37 +01:00
Bruno Windels e80acd4d57 add migration when backup is enabled 2022-01-26 16:30:40 +01:00
Bruno Windels 60ed276b8a add progress notification and cancellation to key backup flush 2022-01-26 15:19:31 +01:00
Bruno Windels 554aa45d48 add support for progress notifications in abortable operation 2022-01-26 15:18:23 +01:00
Bruno Windels 524090e27d support idb store/index.count 2022-01-26 15:12:11 +01:00
Bruno Windels a791641b34 move types to separate file 2022-01-26 12:10:20 +01:00
Bruno Windels 85155a43bb cleanup types 2022-01-26 10:17:31 +01:00
Bruno Windels cfb94206f9 move curve25519 code to separate file 2022-01-26 10:13:01 +01:00
Bruno Windels 86caa5f9b1 rename session backup to key backup to be consistent with RoomKey 2022-01-26 09:51:48 +01:00
Bruno Windels 933a1b4636 draft of session backup writing + some refactoring 2022-01-25 18:48:19 +01:00
Bruno Windels ffece4f357 move some validation of into session backup 2022-01-25 18:48:03 +01:00
Bruno Windels 8f4e3c62ce add hs endpoint for backup keys upload 2022-01-25 18:47:42 +01:00
Bruno Windels 290aaad63a add sessionsNeedingBackup store 2022-01-25 18:47:27 +01:00
Bruno Windels a3e294bb60 small cleanup 2022-01-25 18:45:39 +01:00
Bruno Windels 5d87d8bde3 change store.get return type when no value is found to undefined
IDBRequest.result is undefined according to the official TS type decls.
2022-01-25 18:43:44 +01:00
Bruno Windels 993a86ddb2 convert SessionBackup to typescript and pass in keyloader 2022-01-20 11:16:08 +01:00
Bruno Windels a4d924acd1 make KeyLoader use proper olm types 2022-01-20 11:15:48 +01:00
Bruno Windels 30438846e9
Merge pull request #645 from vector-im/bwindels/fix-mobile-multiline
remove enterkeyhint attribute as it prevents entering newlines on android
2022-01-18 10:39:35 +01:00
Bruno Windels e6fee75952 remove enterkeyhint attribute as it prevents entering newlines on android
on Android, by default (without the above attribute set to "send"), you
press enter twice to submit a field. The first time, enter, Android
seems to prevent sending logic by setting the key property on the event
to "Unidentified", but does insert a newline. The second consecutive enter,
it will be set to "Enter" and we'll send.

Having enterkeyhint to send will disable all of that. So we're going with
the default behaviour, which, IIRC, was a bit annoying on iOS as well.
2022-01-18 09:42:01 +01:00
Bruno Windels acc9167991
Merge pull request #644 from vector-im/bwindels/fix-images-in-replies
fix images not loading in replies
2022-01-17 16:51:09 +01:00
Bruno Windels b0e8506cb5 ensure images load in reply preview in timeline 2022-01-17 16:48:36 +01:00
Bruno Windels f379bf2341 ensure images load in reply preview in composer 2022-01-17 16:48:17 +01:00
Bruno Windels 454d2d3666
Merge pull request #643 from vector-im/bwindels/separate-logout-view
Show logout in separate view so it's clear something is happening
2022-01-17 16:40:49 +01:00
Bruno Windels 57bf730241 mention it's better to not close the app 2022-01-17 16:33:57 +01:00
Bruno Windels 4bc421527f also add extra classes to legacy spinner 2022-01-17 16:31:13 +01:00
Bruno Windels 05d23cc745 hook up logout view 2022-01-17 16:31:02 +01:00
Bruno Windels 4c5b884af7 create and hook up logout viewmodel, on /logout/<id> path 2022-01-17 16:30:22 +01:00
Bruno Windels c6c1d3b3d8 refactor logout in client so we don't need a fully loaded session
instead, we pass the session id in
this will make it easier to first dispose the client when leaving the
/session/<id> and just creating a client without fully loading it
to log out. This way sync is already not running anymore.
2022-01-17 16:29:01 +01:00
Bruno Windels 164d72830f create subclass for inline template views (e.g. without sub classing) 2022-01-17 16:25:48 +01:00
Bruno Windels c10435e242
Merge pull request #642 from vector-im/update-node-faq
Update node version in FAQ
2022-01-17 09:18:07 +01:00
RMidhunSuresh 2dc9b63051 Update node version in FAQ 2022-01-17 12:49:55 +05:30
Bruno Windels d673c8714e release v0.2.23 2022-01-14 19:19:18 +01:00
Bruno Windels 412db33c36 click here labels are so nineties 2022-01-14 19:18:12 +01:00
Bruno Windels 000c8b27c3
Merge pull request #637 from vector-im/bwindels/timeline-readme
add basic readme for updates in the timeline
2022-01-14 19:16:25 +01:00
Bruno Windels 46c61953f6
Merge pull request #612 from vector-im/threading-fallback-reply
Threading fallback - PR 2 - Support rich reply
2022-01-14 19:10:39 +01:00
Bruno Windels a8a8355ea4 fix unit test 2022-01-14 19:05:53 +01:00
Bruno Windels 3d00881508 don't look in remoteEntries when already found 2022-01-14 19:05:30 +01:00
Bruno Windels 7197e5427f don't emit an update when the context entry is loaded sync
also load context entries in parallel
2022-01-14 18:16:52 +01:00
Bruno Windels 3243ce2a90 fix unit test that failed after it finished
crashing the runner on node 16
2022-01-14 18:15:46 +01:00
Bruno Windels 65929194b0 fix lint warnings 2022-01-14 16:23:55 +01:00
Bruno Windels 184a16a194 also define param 2022-01-14 16:23:12 +01:00
Bruno Windels 8201a85c47 ensure these have a fn for tilesCreator 2022-01-14 16:20:38 +01:00
Bruno Windels 2321228981 use this._entry here (once updated by super.updateEntry) 2022-01-14 16:20:14 +01:00
Bruno Windels 5f99c2360c also try to create replyTile from ctor just in case update doesn't come 2022-01-14 16:12:43 +01:00
Bruno Windels ad335d5088 pass in tilesCreator everywhere, although not needed right now 2022-01-14 16:06:29 +01:00
Bruno Windels 1ea4a347e2 encode url components 2022-01-14 15:53:17 +01:00
Bruno Windels b578f4ac84 actually add LocationView 2022-01-14 15:50:19 +01:00
Bruno Windels 052ff02571 move TileView type too so we don't have to repeat imports 2022-01-14 15:47:22 +01:00
Bruno Windels 3c59004e72 Merge branch 'master' into threading-fallback-reply 2022-01-14 15:43:24 +01:00
Bruno Windels 17ebc8a066
Merge pull request #611 from vector-im/threading-fallback-relation
Threading fallback - PR 1 - Link events with their related event
2022-01-14 15:35:27 +01:00
Bruno Windels 9220b6675b
Merge pull request #641 from vector-im/bwindels/location-tile
add location tile view so we don't throw when a location is shared
2022-01-14 15:30:39 +01:00
Bruno Windels 18a76025c7 add location tile view so we don't throw when a location is shared 2022-01-14 15:27:46 +01:00
RMidhunSuresh dac2d5e685 Pass everything down into updateEntry 2022-01-14 19:26:23 +05:30
RMidhunSuresh 0af9f10166 don't store tilesCreator 2022-01-14 19:11:40 +05:30
RMidhunSuresh d18f4d341c store replyFlags on this 2022-01-14 18:31:22 +05:30
Bruno Windels b5a1c419ca
Merge pull request #640 from vector-im/bwindels/lazyloadimageswhenpartiallyinview
load image in timeline from when it is partially visible
2022-01-14 13:59:57 +01:00
Bruno Windels 1f9be978b7 load image in timeline from when it is partially visible 2022-01-14 13:57:11 +01:00
RMidhunSuresh 41fffdf155 Remove even more stray new lines 2022-01-14 18:17:49 +05:30
RMidhunSuresh 51215fda16 Rename tileCreator -> tilesCreator 2022-01-14 18:17:49 +05:30
RMidhunSuresh d639e169ec Move tileCreator to BaseMessageTile 2022-01-14 18:17:49 +05:30
RMidhunSuresh e1b9b1161d Split ifs and remove ?. abuse 2022-01-14 18:17:49 +05:30
RMidhunSuresh 846e637716 Remove stray newline 2022-01-14 18:17:49 +05:30
RMidhunSuresh 58dd25b58d track reply-tile 2022-01-14 18:17:49 +05:30
RMidhunSuresh a77b9d9027 Move update logic to BaseMessageTile 2022-01-14 18:17:49 +05:30
RMidhunSuresh ef5a377bc6 Hide reply option on pending tile 2022-01-14 18:17:49 +05:30
RMidhunSuresh 951af49e04 Emit change on reply tile 2022-01-14 18:17:49 +05:30
RMidhunSuresh 455b747a1c Don't check param for reply 2022-01-14 18:17:49 +05:30
RMidhunSuresh 28a534ee49 Fix reply nesting 2022-01-14 18:17:49 +05:30
RMidhunSuresh f9f7f6cc6f Fix test 2022-01-14 18:17:49 +05:30
RMidhunSuresh 7f91653208 Rename replyTextTile -> replyTile 2022-01-14 18:17:49 +05:30
RMidhunSuresh 086e0c0320 Inline methods 2022-01-14 18:17:49 +05:30
RMidhunSuresh 273c44424f Throw if viewClass returns undefined 2022-01-14 18:17:49 +05:30
RMidhunSuresh b134fa7409 Format swtich case properly 2022-01-14 18:17:49 +05:30
RMidhunSuresh fee6447e22 Don't call render() 2022-01-14 18:17:49 +05:30
RMidhunSuresh e99cd41ed0 Change check 2022-01-14 18:17:49 +05:30
RMidhunSuresh af5a008d0f Move links to vm 2022-01-14 18:17:49 +05:30
RMidhunSuresh 27a9f5dd02 Use DOMPurify to remove mx-reply 2022-01-14 18:17:49 +05:30
RMidhunSuresh cfefe6962a Remove stray space 2022-01-14 18:17:49 +05:30
RMidhunSuresh 88f9ad09a2 Move method as local function 2022-01-14 18:17:49 +05:30
RMidhunSuresh 0ae3c60d6d Remove .js file from rebase 2022-01-14 18:17:49 +05:30
RMidhunSuresh c34d574385 No need to export renderPart 2022-01-14 18:17:49 +05:30
RMidhunSuresh 2a124d4195 simplify css 2022-01-14 18:17:49 +05:30
RMidhunSuresh e352867f5a Remove unnecessary ctor 2022-01-14 18:17:49 +05:30
RMidhunSuresh f645065db7 Remove unused getter 2022-01-14 18:17:49 +05:30
RMidhunSuresh d69059de68 Use different flag 2022-01-14 18:17:49 +05:30
RMidhunSuresh 0c3f16e5f6 Use 's' flag with regex if available 2022-01-14 18:17:49 +05:30
RMidhunSuresh cba044eff1 Remove comment 2022-01-14 18:17:49 +05:30
RMidhunSuresh dee22f7120 Implement render flags 2022-01-14 18:17:49 +05:30
RMidhunSuresh 46b69b3873 Render error 2022-01-14 18:17:49 +05:30
RMidhunSuresh 687aa5a7e3 Remove dead code 2022-01-14 18:17:49 +05:30
RMidhunSuresh 4df3654166 Prevent reply previews from being nested 2022-01-14 18:17:49 +05:30
RMidhunSuresh 4d63b41127 Make reply preview flush left 2022-01-14 18:17:49 +05:30
RMidhunSuresh 1b9f970d7f WIP: Render the whole view instead of messageBody 2022-01-14 18:17:49 +05:30
RMidhunSuresh 7f1b3e25e8 Use t instead of tag 2022-01-14 18:17:49 +05:30
RMidhunSuresh f01d5d95d9 Reuse code from timeline view 2022-01-14 18:17:49 +05:30
RMidhunSuresh 89d6968139 Show decryption error as well 2022-01-14 18:17:49 +05:30
RMidhunSuresh 2773642406 No need to handle redaction specially 2022-01-14 18:17:49 +05:30
RMidhunSuresh 13cba84445 Remove mapSideEffect 2022-01-14 18:17:49 +05:30
RMidhunSuresh bb45d0eae9 Render non-text messages as well 2022-01-14 18:17:49 +05:30
RMidhunSuresh df22db256b No need to pass tileCreator as argument 2022-01-14 18:17:49 +05:30
RMidhunSuresh e0dc853d74 Fill matrix.to links 2022-01-14 18:17:49 +05:30
RMidhunSuresh 91912bdb8d Create tile using tileCreator 2022-01-14 18:17:49 +05:30
RMidhunSuresh 54004eef4d Integrate into update mechanism 2022-01-14 18:17:49 +05:30
RMidhunSuresh aa3bb9c6ef Remove allowReplies 2022-01-14 18:17:49 +05:30
RMidhunSuresh 73c5562fd3 Remove code from BaseTextTile 2022-01-14 18:17:49 +05:30
RMidhunSuresh 4a12acf157 Improve error code 2022-01-14 18:17:49 +05:30
RMidhunSuresh 67da746b48 Render error 2022-01-14 18:17:49 +05:30
RMidhunSuresh 545aae31d9 WIP 2022-01-14 18:17:49 +05:30
RMidhunSuresh 3aa29cfc65 Do not remove reply preview 2022-01-14 18:17:49 +05:30
RMidhunSuresh 99f4eb6843 Minimize manual dom manipulation where possible 2022-01-14 18:17:49 +05:30
RMidhunSuresh 61f4d0719f Refactor code 2022-01-14 18:17:49 +05:30
RMidhunSuresh d6233e7c77 Render static avatar 2022-01-14 18:17:49 +05:30
RMidhunSuresh 540aa6c546 Use contextEntry and pass avatarUrl 2022-01-14 18:17:49 +05:30
RMidhunSuresh 31573b3599 Render reply 2022-01-14 18:17:49 +05:30
RMidhunSuresh e88ee31991 Add getter for reply body 2022-01-14 18:17:49 +05:30
RMidhunSuresh f6cf3b378b Strip reply fallback 2022-01-14 18:17:49 +05:30
RMidhunSuresh 35a13842af Implement context endpoint 2022-01-14 18:17:49 +05:30
RMidhunSuresh 65f957f023 WIP 2022-01-14 18:17:49 +05:30
R Midhun Suresh 4fb0a84d0a
Return property from super
Co-authored-by: Bruno Windels <brunow@matrix.org>
2022-01-14 18:16:38 +05:30
RMidhunSuresh 30b8e5b5ea use withReply 2022-01-14 18:15:26 +05:30
RMidhunSuresh 8cd430ac07 Improve test logic 2022-01-14 17:48:25 +05:30
RMidhunSuresh 75012eda9c Fix tests 2022-01-14 17:28:31 +05:30
RMidhunSuresh e9a49fdf74 Use hsApi mock 2022-01-14 17:07:06 +05:30
RMidhunSuresh 315acf2fbc Remove dead code from test 2022-01-14 16:54:16 +05:30
RMidhunSuresh 310790c84e Use mock storage 2022-01-14 16:51:06 +05:30
RMidhunSuresh 277638b107 Override methods in NonPersistedEventEntry
This will prevent redactions to entries fetched from hs showing "message
is being redacted" and will instead show "message is redacted"
2022-01-14 16:15:16 +05:30
RMidhunSuresh b238357c53 Use emitUpdateForEntry 2022-01-14 16:14:42 +05:30
RMidhunSuresh 4fa32bac2f check only in remoteEntries 2022-01-14 16:14:06 +05:30
Bruno Windels 58f2192a7e add basic readme for updates in the timeline 2022-01-14 11:13:21 +01:00
RMidhunSuresh 3c28ee1adf Remove unused getter 2022-01-13 21:05:18 +05:30
RMidhunSuresh 2c4610c132 add param to emitUpdateForEntry 2022-01-13 19:20:37 +05:30
RMidhunSuresh 239d16747d Clean test code; try not to peek into internals 2022-01-13 19:14:28 +05:30
RMidhunSuresh 764541d3ca Remove unused method 2022-01-13 18:32:18 +05:30
RMidhunSuresh ca1831fef6 update contextForEntries 2022-01-13 14:38:05 +05:30
Bruno Windels 1ed8d48ced release SDK 0.0.4 2022-01-12 18:39:13 +01:00
Bruno Windels 48e6bba100
Merge pull request #634 from vector-im/bwindels/fix-sdk-build2
Adjust SDK to not do asset imports anymore in file provided from SDK
2022-01-12 18:37:28 +01:00
Bruno Windels 8c1596d869 update SDK docs to not use paths/vite anymore 2022-01-12 18:32:15 +01:00
Bruno Windels 93eca757d3 dont add paths/vite to sdk output, as it does not work 2022-01-12 18:31:55 +01:00
Bruno Windels 3f60ef8da7 release sdk version 0.0.3 2022-01-12 17:51:48 +01:00
Bruno Windels 5d15fce343
Merge pull request #633 from vector-im/bwindels/fix-sdk-build1
Attempt to fix SDK build and dev server errors for consuming app
2022-01-12 17:48:08 +01:00
Bruno Windels f526098293 also remove ts types, as we get errors for the untyped files
that don't exist
2022-01-12 17:41:00 +01:00
Bruno Windels d7290bf750 remove exports field to try and prevent vite bug resolving asset url
downside is that we can't export cjs version anymore
2022-01-12 17:14:52 +01:00
RMidhunSuresh 2f4c0623d0 Restore earlier name 2022-01-12 19:20:32 +05:30
RMidhunSuresh ed88184757 Remove statement 2022-01-12 19:14:38 +05:30
RMidhunSuresh d0f7570f5e Fix tests 2022-01-12 18:44:17 +05:30
Bruno Windels a5eb386f48
Merge pull request #632 from vector-im/bwindels/move-bs58-to-dev-deps
put bs58 in devDeps as we bundle it in the sdk
2022-01-12 10:13:31 +01:00
Bruno Windels b76f97be93 put bs58 in devDeps as we bundle it in the sdk 2022-01-12 10:11:04 +01:00
RMidhunSuresh acafae7d3a Implement offline support for context entries 2022-01-11 20:58:27 +05:30
RMidhunSuresh a59bf7c002 Fix looking in allEntries 2022-01-11 20:57:29 +05:30
RMidhunSuresh 5c1813888c Check in all entries for context 2022-01-11 14:57:22 +05:30
RMidhunSuresh 73733ce145 Guard entry from storage being processed by method 2022-01-11 14:49:59 +05:30
RMidhunSuresh bf6dfcfcad update comment 2022-01-11 13:28:35 +05:30
RMidhunSuresh f605608098 getTrackedEntry -> findLoadedEventById 2022-01-11 13:20:42 +05:30
RMidhunSuresh 31a8227e53 stylistic change 2022-01-11 13:14:13 +05:30
RMidhunSuresh 62dcb61536 Rename updateEntry -> emitUpdateForEntry 2022-01-11 13:11:50 +05:30
RMidhunSuresh fda211e7b3 Remove dead code 2022-01-11 13:10:40 +05:30
RMidhunSuresh 63b6564f70 Pass prop change 2022-01-11 11:54:41 +05:30
RMidhunSuresh 93bbeee400 Don't pass relatedEntry in param 2022-01-11 11:49:06 +05:30
RMidhunSuresh 66fa8d84a7 Make setAsContextOf private 2022-01-10 18:51:12 +05:30
RMidhunSuresh 091b55a265 Rename method and add comment 2022-01-10 18:05:33 +05:30
RMidhunSuresh ec8f6e8e0a use addLocalRelation 2022-01-10 12:58:45 +05:30
RMidhunSuresh 7ad73bb453 Move check down 2022-01-07 19:56:31 +05:30
RMidhunSuresh 3fecce6fe6 Fix tests 2022-01-07 19:39:51 +05:30
RMidhunSuresh 9d161a0bcf Refactor + put redaction in NonPersistedEventEntry 2022-01-07 19:38:57 +05:30
RMidhunSuresh 8cc04e4c25 Keep calls internal to class 2022-01-07 17:50:36 +05:30
RMidhunSuresh 0a09a50ab9 Move line into if 2022-01-07 17:29:17 +05:30
RMidhunSuresh c6484f1eac Replace entry in contextEntryNotInTimeline 2022-01-07 17:11:42 +05:30
Bruno Windels 68214156d9
Merge pull request #608 from vector-im/ts-migration-doc
Clarify approach to type data objects in doc
2022-01-06 14:39:06 +01:00
Bruno Windels 314843f5f2
Merge pull request #624 from vector-im/build-fix
Add hash-bang to fix sdk build error
2022-01-06 13:22:10 +01:00
RMidhunSuresh cfbb6d4250 Add explaining comment 2022-01-06 15:37:58 +05:30
RMidhunSuresh 7adce08eee add more jsdoc comments 2022-01-06 15:33:00 +05:30
RMidhunSuresh f76217dcce Change method name 2022-01-06 15:14:13 +05:30
RMidhunSuresh a2ab36480f Add jsdoc comment 2022-01-06 15:02:44 +05:30
RMidhunSuresh 90c9018aa4 Update comment 2022-01-06 12:07:10 +05:30
RMidhunSuresh 595deb3a3d Also copy over contextEntry from otherEntry 2022-01-06 12:07:10 +05:30
RMidhunSuresh 78f97c6532 Remove await from tests 2022-01-06 12:07:10 +05:30
RMidhunSuresh 9f1764c325 Update comment 2022-01-06 12:07:10 +05:30
RMidhunSuresh 4418700589 Add test for move code 2022-01-06 12:07:10 +05:30
RMidhunSuresh d2c7eec8e0 No need to delete before update on map 2022-01-06 12:07:10 +05:30
RMidhunSuresh 41cf6460d0 Remove dead code 2022-01-06 12:07:10 +05:30
RMidhunSuresh 8ec75ce4bb Rename methods 2022-01-06 12:07:10 +05:30
RMidhunSuresh a060d54468 Make tests pass 2022-01-06 12:07:10 +05:30
RMidhunSuresh 3fe824dbd1 Propagate updates 2022-01-06 12:07:10 +05:30
RMidhunSuresh 7ef79c92f5 Remove entry from map 2022-01-06 12:07:10 +05:30
RMidhunSuresh 2d5bb82077 Fix bug 2022-01-06 12:07:10 +05:30
RMidhunSuresh 6f8001bd82 Add tests 2022-01-06 12:07:10 +05:30
RMidhunSuresh 640a3fb9fa Check if contextEvent was found 2022-01-06 12:07:10 +05:30
RMidhunSuresh 05d2defa2d Rename fetchedEntries --> contextEntriesNotInTimeline 2022-01-06 12:07:10 +05:30
RMidhunSuresh c3bef6d4d2 Rename dependents --> contextForEntries 2022-01-06 12:07:10 +05:30
RMidhunSuresh d1818d2a57 Reuse code in getOrLoadEntry 2022-01-06 12:07:10 +05:30
RMidhunSuresh f5fadf700e Move event to remoteEntries if needed 2022-01-06 12:07:10 +05:30
RMidhunSuresh d924dbb723 Add explaining comment 2022-01-06 12:07:10 +05:30
RMidhunSuresh 544dca3b18 Use _updateEntry 2022-01-06 12:07:10 +05:30
RMidhunSuresh 39f68e8c2f Refactor out magic string 2022-01-06 12:07:10 +05:30
RMidhunSuresh 5c0bbdd4c8 Move methods into Timeline 2022-01-06 12:07:10 +05:30
RMidhunSuresh 51b7b21082 Implement readById() in TimelineReader 2022-01-06 12:07:10 +05:30
RMidhunSuresh 0da94e51e0 Use map and fetch from Map if available 2022-01-06 12:07:10 +05:30
RMidhunSuresh 4a6293dcdc Made code more readable 2022-01-06 12:07:10 +05:30
RMidhunSuresh 287212956b findAndUpdate instead of update 2022-01-06 12:07:10 +05:30
RMidhunSuresh 7a91dd9595 Improve comment 2022-01-06 12:07:10 +05:30
RMidhunSuresh 4a81e06e96 Track fetched entries for redactions 2022-01-06 12:07:10 +05:30
RMidhunSuresh ea89c272b9 Support redaction changes in remoteEntries 2022-01-06 12:07:10 +05:30
RMidhunSuresh c690de9f7b Support decryption on entries fetched from hs 2022-01-06 12:07:10 +05:30
RMidhunSuresh 7cc3d4b91a Emit updated entries 2022-01-06 12:07:10 +05:30
RMidhunSuresh 053dcf39a5 Use NonPersistedEventEntry 2022-01-06 12:07:10 +05:30
RMidhunSuresh d191b327c6 Change comment 2022-01-06 12:07:10 +05:30
RMidhunSuresh 06864a65b7 Add contextEventId 2022-01-06 12:07:10 +05:30
RMidhunSuresh 764e38f8c9 Use 'context' instead of 'related' 2022-01-06 12:07:10 +05:30
RMidhunSuresh 696980aca4 Parse display name and avatar of event 2022-01-06 12:07:10 +05:30
RMidhunSuresh 0c42f53a2f Implement context endpoint 2022-01-06 12:07:06 +05:30
RMidhunSuresh e901142661 await on loading related events 2022-01-06 11:59:58 +05:30
RMidhunSuresh 2265d198a6 Formatting fix 2022-01-06 11:59:58 +05:30
RMidhunSuresh b753507b8d WIP 2022-01-06 11:59:58 +05:30
Bruno Windels 196e3726cb
Merge pull request #630 from vector-im/bwindels/otk-count-cleanup
Missing OTK count in sync doesn't mean 0, but rather no change
2022-01-05 14:29:51 +01:00
Bruno Windels c9d11d6f19 missing otk count does not mean 0 but rather no change 2022-01-05 14:26:15 +01:00
Bruno Windels aabfbf507e typo in comments 2022-01-05 14:25:42 +01:00
RMidhunSuresh 205de7e5c5 Add hash-bang to fix build error 2021-12-27 15:51:25 +05:30
Bruno Windels 908f9a7ce3 try to export stylesheet 2021-12-22 18:04:30 +01:00
Bruno Windels 203a5fd88c
Merge pull request #622 from vector-im/bwindels/sdk-refactoring
Some API cleanup ahead of first SDK release
2021-12-22 17:58:20 +01:00
Bruno Windels 13e77636a9 export paths from vite.js as required by Platform, reorder ctor params
make it easier for SDK users
2021-12-22 17:48:08 +01:00
Bruno Windels 6247ced7b7 dont export, the ctor of these classes is not a public API 2021-12-22 17:24:58 +01:00
Bruno Windels ba27d20b24 only pass platform into Client
simplifying the API for SDK
2021-12-22 17:20:37 +01:00
Bruno Windels 9238961992 cache olm and olm worker promise inside Platform
as prep to call them every time a Client is created
2021-12-22 17:19:10 +01:00
Bruno Windels fe26f48c47 rename SessionContainer to Client 2021-12-22 17:09:52 +01:00
Bruno Windels b5fe65d0cc
Merge pull request #617 from vector-im/bwindels/sdk-build
SDK build
2021-12-22 16:54:03 +01:00
Bruno Windels 24afe1e496 add licenses to readme of things we actually bundle
rather than just depend on
2021-12-22 16:45:08 +01:00
Bruno Windels 5f389e654a add description 2021-12-22 16:37:53 +01:00
Bruno Windels c31215bc2a less logging during build 2021-12-22 16:31:19 +01:00
Bruno Windels c3ff571af7 update SDK doc, use it as sdk package readme 2021-12-22 16:31:19 +01:00
Bruno Windels 441fa13bfd change sdk package name to hydrogen-view-sdk
as we might want to also have a lower level sdk later on
2021-12-22 16:31:19 +01:00
Bruno Windels 3bee4b4585 bundle bs58 to avoid pain of bundle transitive dependency for lib users
bs58 depends on safe-buffer, which depends on buffer, which is a bit
of a pain to bundle as it is a built-in node module. You'd typically
replace buffer with a browser polyfill in your build system but:
 a) this is somewhat a pain to setup for simple apps
 b) the polyfill is way more than we need (6kb), so we prefer to bundle
    our minimal buffer replacement that uses Uint8Array. Since it is
    a transitive dependency, we need to bundle bs58 and all of its
    transitive dependencies (2.5kb) as well, so if users of hydrogen-sdk
    also use any of these, they'll be double included in their bundle.
2021-12-22 16:31:19 +01:00
Bruno Windels b48280905e include path/vite in sdk bundle 2021-12-22 16:31:19 +01:00
Bruno Windels 163dae647b move output of both lib and asset build around for coherent package 2021-12-22 16:31:19 +01:00
Bruno Windels c921091957 run two vite builds for the sdk build, assets & js separately 2021-12-22 16:31:19 +01:00
Bruno Windels 6add3f1da3 WIP 2021-12-22 16:31:19 +01:00
Bruno Windels ceb0b5793b somewhat works, but not everything we need
it's missing still:
 - non-css assets like the download sandbox and the olm worker aren't written for some reason
 - the es and cjs lib.js entry points end up in assets with a hash for some reason
 - in these entry files, apart from our exports, something is adding an import statement for every import that was found in the tree
 - all assets are hashed even though the config tries to disable that
 - tests are included
2021-12-22 16:31:18 +01:00
Bruno Windels 14b854ad4f make tsconfig file to build declaration files 2021-12-22 16:31:18 +01:00
Bruno Windels df6000c706 basic sdk build config file for es and cjs 2021-12-22 16:31:18 +01:00
Bruno Windels c11f0774eb move common parts of build config to separate file and merge with it 2021-12-22 16:31:18 +01:00
Bruno Windels f2b822e5d2 move deps that are not used for sdk to devDependencies 2021-12-22 16:31:18 +01:00
Bruno Windels 2d2005934a WIP 2021-12-22 16:31:18 +01:00
Bruno Windels 363cd5b046 include css 2021-12-22 16:31:18 +01:00
Bruno Windels 8922d2aaf2 prototype of sdk build 2021-12-22 16:31:18 +01:00
RMidhunSuresh 10368500f2 Fix formatting 2021-12-10 12:12:52 +05:30
RMidhunSuresh 5ef7ab32df Update doc 2021-12-10 12:09:18 +05:30
Bruno Windels dacdc1aec6
Merge pull request #597 from vector-im/ts-conversion-matrix-ssss
Convert matrix/ssss to typescript
2021-12-09 18:54:25 +01:00
Bruno Windels 589a002d67
Merge pull request #588 from vector-im/ts-conversion-matrix-net
Convert /matrix/net to typescript
2021-12-09 18:51:33 +01:00
Bruno Windels 21a41e192b Merge branch 'master' into ts-conversion-matrix-net 2021-12-09 18:49:54 +01:00
Bruno Windels 5ea29297cc fix typescript errors 2021-12-09 18:44:44 +01:00
Bruno Windels c5c08ea34b
Merge pull request #586 from vector-im/bwindels/log-signature-failure
log signature verification failure in logger, not console
2021-12-09 18:40:01 +01:00
Bruno Windels 8d315f2741 Merge branch 'master' into bwindels/log-signature-failure 2021-12-09 18:34:36 +01:00
Bruno Windels cd0d9dcbba
Merge pull request #548 from vector-im/bwindels/vite-mvp
Convert develop server and build system to using Vite
2021-12-09 18:29:06 +01:00
Bruno Windels ba84387722 remove commented out code 2021-12-09 18:15:22 +01:00
Bruno Windels 0ec86b6dc1 Merge branch 'master' into bwindels/vite-mvp 2021-12-09 18:07:17 +01:00
Bruno Windels 5c5193ef48 remove old build system and unused dependencies
some of these are for the ie11 legacy build, which has been
postponed. They will be brougth back when we bring back the legacy build
2021-12-09 18:04:11 +01:00
Bruno Windels d9ff4a8484 sw.js is not part of the sdk yet, so just put the path in index.html 2021-12-09 17:12:08 +01:00
Bruno Windels dea7e7b4f5 enable minification and source maps 2021-12-09 16:42:35 +01:00
Bruno Windels 62827b92b7 implement placeholder replacement so it still works with minification 2021-12-09 16:37:31 +01:00
Bruno Windels 9a82f88e1f log swSource as build fails in CI 2021-12-09 15:13:19 +01:00
Bruno Windels 23e0d3f2ff get notification badge icon url through import now we transpile the sw 2021-12-09 15:13:05 +01:00
Bruno Windels a4fac68393 use same method for setting version and build hash placeholder in sw
also better naming in service worker plugin
2021-12-09 14:36:12 +01:00
Bruno Windels f934262e35 also use global hash var here 2021-12-09 12:22:17 +01:00
Bruno Windels 14dffa4ad4 remove leftover logging 2021-12-09 12:21:34 +01:00
Bruno Windels 8e4da396ea replace global hash in given chunks 2021-12-09 12:15:17 +01:00
Bruno Windels c344032c0a transpile service worker and cleanup build plugin 2021-12-09 11:39:28 +01:00
Bruno Windels 180681b602 manifest ends up in assets folder, index.html in parent folder 2021-12-08 18:29:32 +01:00
Bruno Windels fb8149b6cf add base to manifest path, just for completeness 2021-12-08 18:29:07 +01:00
Bruno Windels 4c2c99fc07 actually remove lookbehind 2021-12-08 18:05:57 +01:00
Bruno Windels c8b0354d07 dont use lookbehind in regular expressions, safari & older firefoxes choke on them 2021-12-08 18:00:37 +01:00
Bruno Windels c87628b614 cleanup 2021-12-06 15:40:15 +01:00
Bruno Windels 5bd28da4f3 loading olm from the worker was broken, reading the wrong global 2021-12-06 15:35:08 +01:00
Bruno Windels 0e2a22f509 also look in chunks for cacheable assets for service worker 2021-12-06 15:34:39 +01:00
Bruno Windels 91e69a2bd0 fix icons in manifest not being found 2021-12-06 15:25:44 +01:00
Bruno Windels 155cd4c9bd make olmPath absolute if it isn't already 2021-12-06 13:49:14 +01:00
RMidhunSuresh 734ecccb9c Use object instead of Record here 2021-12-03 17:34:23 +05:30
Bruno Windels 9a3f74c6fb load service worker in production mode, adjust development flag 2021-12-03 10:42:38 +01:00
RMidhunSuresh e2abc312d3 Fix typescript errors 2021-12-03 11:48:01 +05:30
RMidhunSuresh d6378133d8 Remove length property 2021-12-03 11:40:26 +05:30
RMidhunSuresh 49a56efa82 Remove comment 2021-12-03 11:40:26 +05:30
RMidhunSuresh 640cd88b6e make type string 2021-12-03 11:40:26 +05:30
RMidhunSuresh 66b4f9bfe5 LogItem --> ILogItem 2021-12-03 11:40:26 +05:30
RMidhunSuresh 0541cf8f2b Change object to Record 2021-12-03 11:40:26 +05:30
RMidhunSuresh bf93bd79c9 types.js --> types 2021-12-03 11:40:26 +05:30
RMidhunSuresh f89b937ee7 Use object instead of Record 2021-12-03 11:40:26 +05:30
RMidhunSuresh 82de3c9867 Prefer type over interface 2021-12-03 11:40:26 +05:30
RMidhunSuresh b328c54da8 Change type from Ctor to Options 2021-12-03 11:40:26 +05:30
RMidhunSuresh e9cea73357 Remove comment 2021-12-03 11:40:26 +05:30
R Midhun Suresh 3fbf65355d Rename Ctor to Options
Co-authored-by: Bruno Windels <brunow@matrix.org>
2021-12-03 11:40:26 +05:30
RMidhunSuresh b5438f2ba8 Do not set content-length 2021-12-03 11:40:26 +05:30
RMidhunSuresh 4f43398db0 Fix promise resolve type 2021-12-03 11:40:26 +05:30
RMidhunSuresh 05121e32b1 Pull interface out for HomeServerApi 2021-12-03 11:40:26 +05:30
RMidhunSuresh a8870f2d24 Extract ctor types out 2021-12-03 11:40:26 +05:30
RMidhunSuresh 238b9aafb1 Convert replay.js to ts 2021-12-03 11:40:26 +05:30
RMidhunSuresh 2e6b909173 No need to pass undefined 2021-12-03 11:40:26 +05:30
RMidhunSuresh 4bdcafad4b Rename file to types.ts 2021-12-03 11:40:26 +05:30
RMidhunSuresh 69e67ad5ac Make more functions private 2021-12-03 11:40:26 +05:30
RMidhunSuresh 2dd050bd90 Change object to Record 2021-12-03 11:40:26 +05:30
RMidhunSuresh 9b315d1564 Replace object with Record 2021-12-03 11:40:26 +05:30
RMidhunSuresh 57d24dcf90 Treat wrapper hsapi as HomeServerApi 2021-12-03 11:40:26 +05:30
RMidhunSuresh 8387215efd Add comment 2021-12-03 11:40:26 +05:30
RMidhunSuresh 885abc59be Add return types 2021-12-03 11:40:26 +05:30
RMidhunSuresh 7403cbc389 WIP - HomeServerApi.js to ts conversion 2021-12-03 11:40:26 +05:30
RMidhunSuresh 145b40f28d Fomatting fix 2021-12-03 11:40:26 +05:30
RMidhunSuresh cf54b78af7 Convert RequestScheduler.js to ts 2021-12-03 11:40:26 +05:30
RMidhunSuresh 0aae31a450 Change year in copyright notice 2021-12-03 11:40:26 +05:30
RMidhunSuresh f120ce50e6 Convert Reconnector.js to ts 2021-12-03 11:40:26 +05:30
RMidhunSuresh 90e3fde35d Convert MediaRepository.js to ts 2021-12-03 11:40:26 +05:30
RMidhunSuresh ff53c2757d Convert HomeServerRequest.js to ts 2021-12-03 11:40:26 +05:30
RMidhunSuresh e1a823400a Convert ExponentialRetryDelay.js to ts 2021-12-03 11:40:26 +05:30
RMidhunSuresh 3a24019d96 Convert common.js to ts 2021-12-03 11:40:26 +05:30
RMidhunSuresh 9688a561b3 Move interface to common.ts 2021-12-03 11:40:26 +05:30
RMidhunSuresh 2a3b13ecce Add request type 2021-12-03 11:40:26 +05:30
RMidhunSuresh 9bffd31ee3 Remove typeof 2021-12-03 11:36:51 +05:30
R Midhun Suresh 6dcebde69d Alias namespace as Olm
Co-authored-by: Bruno Windels <brunow@matrix.org>
2021-12-03 11:36:51 +05:30
RMidhunSuresh e06a0e9e5a Use olm type from dependency 2021-12-03 11:36:51 +05:30
RMidhunSuresh 7362e38413 Convert interface to type 2021-12-03 11:36:51 +05:30
RMidhunSuresh d2c09933c7 Type encrypted data 2021-12-03 11:36:51 +05:30
RMidhunSuresh b2efcb9515 Convert SecretStorage.js to ts 2021-12-03 11:36:51 +05:30
RMidhunSuresh 814c0bed2e Convert recoveryKey.js to ts 2021-12-03 11:36:51 +05:30
RMidhunSuresh e45f66a199 Convert passphrase.js to ts 2021-12-03 11:36:51 +05:30
RMidhunSuresh dd4704b818 Fix imports 2021-12-03 11:36:49 +05:30
RMidhunSuresh 9b8ab9fd8d Convert index.js to index.ts 2021-12-03 11:35:12 +05:30
RMidhunSuresh f9f59fec39 Convert common.js to ts 2021-12-03 11:34:09 +05:30
Bruno Windels d91aaabeb3
Merge pull request #596 from vector-im/ts-conversion-matrix-sessioninfo
Convert matrix/sessioninfo to typescript
2021-12-02 09:29:11 +01:00
Bruno Windels 9042520916
Merge pull request #595 from vector-im/ts-conversion-matrix-push
Convert /matrix/push to typescript
2021-12-02 09:28:59 +01:00
Bruno Windels d3ab961364
Merge pull request #593 from vector-im/ts-conversion-matrix-login
Convert /matrix/login to typescript
2021-12-02 09:28:47 +01:00
RMidhunSuresh 0c46460861 Add comment 2021-12-02 11:20:40 +05:30
RMidhunSuresh 9f82e7f7fc Add proper type 2021-12-02 11:17:41 +05:30
RMidhunSuresh ef3456199c Fix formatting 2021-12-01 22:52:09 +05:30
R Midhun Suresh 928a5c27f3
Add rationale
Co-authored-by: Bruno Windels <brunow@matrix.org>
2021-12-01 22:50:59 +05:30
Bruno Windels bc86bf2d00 some more sdk impl notes 2021-12-01 18:11:09 +01:00
Bruno Windels fceca845a9 comment why we do this 2021-12-01 18:11:01 +01:00
Bruno Windels 09338d8aa8 bring back postcss plugins, apart from autoprefixer, which breaks vite 2021-12-01 18:10:25 +01:00
Bruno Windels a504e74f54 extract function for script tag url 2021-12-01 18:10:02 +01:00
Bruno Windels f83a0cec4e update postcss plugins so they all use the same version of postcss 2021-12-01 18:09:26 +01:00
Bruno Windels 69e34d03bd sort dependencies 2021-12-01 17:45:33 +01:00
Bruno Windels 261b17d36c fix lint 2021-12-01 17:27:13 +01:00
Bruno Windels 3fd2d39898 remove unused packages and move dev deps accordingly 2021-12-01 17:26:48 +01:00
Bruno Windels bb9362ee8b only import node-html-parser when running the unit tests 2021-12-01 17:26:00 +01:00
Bruno Windels 1a618dd106 only import fake-indexeddb in tests
as it is a devDependency and can end up in the legacy bundle
under circumstances
2021-12-01 17:25:07 +01:00
Bruno Windels 7fda78ff2f disable legacy build for now 2021-12-01 14:06:15 +01:00
Bruno Windels 70c1e4e3ed move doc paragraphs around 2021-12-01 14:06:05 +01:00
Bruno Windels b469d03677 config is at same level as paths 2021-12-01 14:05:50 +01:00
Bruno Windels de24034b22 remove secondary theme as vite puts them in one bundle 2021-12-01 13:30:58 +01:00
Bruno Windels 75bf410320 correct path of main.js for tests 2021-12-01 13:30:17 +01:00
Bruno Windels 7e1818b285 Merge branch 'master' into bwindels/vite-mvp 2021-12-01 12:30:33 +01:00
RMidhunSuresh 73ca2dfb77 Add Record and fix typo 2021-12-01 16:05:16 +05:30
Bruno Windels dce0ee5ace
Merge pull request #607 from vector-im/bwindels/fixpathsobserv
adjust path
2021-12-01 09:45:57 +01:00
Bruno Windels 85385a0aa7 adjust path 2021-12-01 09:43:58 +01:00
Bruno Windels 08314bd4b5
Merge pull request #606 from vector-im/bwindels/typescript-observable-2
Typescript conversion of yet more observables
2021-11-30 17:09:16 +01:00
Bruno Windels 8c3ae57497 fix Iterator vs IterableIterator confusion 2021-11-30 17:05:39 +01:00
Bruno Windels de8995fa7e fix handlers in test missing methods, now that observable list is typed 2021-11-30 16:58:56 +01:00
Bruno Windels 581ef47c78 fix conflicting sortedIndex declaration 2021-11-30 16:53:59 +01:00
Bruno Windels fc3eb7f57f Merge branch 'master' into bwindels/typescript-observable-2 2021-11-30 16:37:43 +01:00
Bruno Windels 49443d4f6e
Update TS-MIGRATION.md 2021-11-30 14:17:51 +00:00
Bruno Windels 2e57e99e34
clarify when to use type and interface 2021-11-30 14:15:25 +00:00
Bruno Windels ef712b16f5
Merge pull request #584 from vector-im/ts-conversion-utils
Convert /utils to typescript
2021-11-30 14:13:09 +01:00
Bruno Windels 19827a0b5b
Merge pull request #601 from vector-im/filter-token
Ensure unwanted data do not end up in logs
2021-11-30 10:02:07 +01:00
RMidhunSuresh 66fbc37ec4 Remove comments 2021-11-30 14:15:49 +05:30
RMidhunSuresh 6699b71bd5 transformer is optional 2021-11-30 13:38:25 +05:30
RMidhunSuresh fe77b71c97 use transformer function 2021-11-30 13:28:28 +05:30
Bruno Windels be5deea1d3
Merge pull request #605 from vector-im/bwindels/try-test-ci-failure
Report unit test failures on CI
2021-11-30 08:20:05 +01:00
Bruno Windels 3322827979 upgrade impunity to propagate exit code 2021-11-30 08:16:28 +01:00
Bruno Windels 7f115b3e4b
Merge pull request #602 from vector-im/bwindels/tests-node-16.12
update impunity to 1.0.8 to run tests on node >= 16.12
2021-11-29 10:53:47 +01:00
Bruno Windels a134e48ebb update impunity to 1.0.8 to run tests on node >= 16.12 2021-11-29 10:49:26 +01:00
RMidhunSuresh 104590e34d Use ! in test 2021-11-29 11:48:05 +05:30
RMidhunSuresh d981a85239 Filter token out of stack trace 2021-11-29 11:43:43 +05:30
Bruno Windels bc8b3d71d5
Merge pull request #600 from vector-im/bwindels/update-impunity
update to version that doesn't use a bash script anymore, which doesn…
2021-11-26 12:48:21 +01:00
Bruno Windels 2802164bb4 update to version that doesn't use a bash script anymore, which doesnt work on macos 2021-11-26 12:45:40 +01:00
Bruno Windels 876fcf532f release v0.2.22 2021-11-26 09:12:08 +01:00
Bruno Windels 92bf28e104
Merge pull request #599 from vector-im/bwindels/fix-emsonelogin
Don't fail login if dehydrated devices are not supported
2021-11-25 15:46:01 +01:00
Bruno Windels ae7d4d07df use .name so we don't need an import 2021-11-25 15:42:36 +01:00
Bruno Windels 229c584138 don't fail login if dehydrated devices are not supported 2021-11-25 15:38:13 +01:00
RMidhunSuresh bb18af414b Convert SessionInfoStorage.js to ts 2021-11-25 15:18:03 +05:30
RMidhunSuresh 3d9fbb685a Convert Pusher.js to ts 2021-11-25 13:23:05 +05:30
RMidhunSuresh 346e95c33c Change return type 2021-11-25 12:33:12 +05:30
RMidhunSuresh a31860dc5f Fix formatting 2021-11-24 14:55:44 +05:30
RMidhunSuresh c54ca168ed Convert SSOLoginHelper.js to ts 2021-11-24 14:49:08 +05:30
RMidhunSuresh a1367f8e72 Fix password login 2021-11-24 14:00:26 +05:30
RMidhunSuresh 64037cb32a Convert TokenLoginMethod to ts 2021-11-24 13:56:47 +05:30
RMidhunSuresh e4c443c73a Convert PasswordLoginMethod to ts 2021-11-24 13:47:26 +05:30
RMidhunSuresh 91f2a96403 Make LoginMethod an interface 2021-11-24 13:40:04 +05:30
Bruno Windels 93abbe83e8
Merge pull request #592 from vector-im/bwindels/lazylist-enhancements
Lazylist enhancements
2021-11-23 14:35:18 +01:00
Bruno Windels f444160c6a feels ok without overflow margin for now 2021-11-23 14:33:27 +01:00
Bruno Windels e4be1702c4 add comment for future test 2021-11-23 14:32:42 +01:00
Bruno Windels 7b38df45da i think this is fine now? 2021-11-23 14:31:23 +01:00
Bruno Windels e34a92e2ec fix copyright 2021-11-23 14:30:11 +01:00
Bruno Windels 35fb84c275 remove old js lazylist 2021-11-23 14:26:15 +01:00
Bruno Windels 9557178ffb padding needs to be on ul, not scroll container, or the list blows up 2021-11-23 14:25:35 +01:00
Bruno Windels 4be2f12a14 subscribe before calling list.length 2021-11-23 14:25:22 +01:00
Bruno Windels c64a9c1e23 snowpack/esbuild 0.9 doesn't support override keyword 2021-11-23 14:25:00 +01:00
Bruno Windels 7897ea88cd add some spaces and comments 2021-11-23 14:24:43 +01:00
Bruno Windels c22718811f more tests for queryMove 2021-11-23 08:56:33 +01:00
Bruno Windels 3aa3b7e160 fix end growing larger than totalLength when range shrinks in case of remove 2021-11-23 08:30:52 +01:00
Bruno Windels cf9f43ab9e WIP2 2021-11-22 20:35:57 +01:00
Bruno Windels 4a64d0ee17 WIP 2021-11-19 22:49:46 +01:00
RMidhunSuresh d625d57aa4 Fix lastIndex
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-11-19 12:32:00 +01:00
RMidhunSuresh bbeb909bdc Use createEnum
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-11-19 12:32:00 +01:00
RMidhunSuresh 33ac34b04e Do not break onListChanged
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-11-19 12:31:57 +01:00
RMidhunSuresh 5d54285640 Move ItemRange to separate file
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-11-19 12:12:50 +01:00
RMidhunSuresh aee135a6cd Jsdoc fix
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-11-19 12:06:40 +01:00
RMidhunSuresh da715c70b0 Remove forceRender
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-11-19 12:06:40 +01:00
RMidhunSuresh e10b494f0c Improve containsIndex
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-11-19 12:06:40 +01:00
RMidhunSuresh 3ae52ea1ca Fix bug in onAdd and onRemove
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-11-19 12:06:40 +01:00
RMidhunSuresh 1165683f69 Fix onRemove
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-11-19 12:06:40 +01:00
RMidhunSuresh 83ff2dd810 Fix onAdd
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-11-19 12:06:40 +01:00
RMidhunSuresh 587dd3848e Use existing render function for initial render
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-11-19 12:06:40 +01:00
RMidhunSuresh a02b6b68d3 Move common code from if-else
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-11-19 12:06:40 +01:00
RMidhunSuresh 168312627d Render only diff of ranges
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-11-19 12:06:40 +01:00
RMidhunSuresh 61402e798e WIP 2 2021-11-19 12:06:40 +01:00
RMidhunSuresh 1a28b4f887 WIP 2021-11-19 12:06:40 +01:00
RMidhunSuresh d4e923f9de Remove code from loadList
We don't need this method so best to leave it empty.

Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-11-19 12:06:40 +01:00
Bruno Windels c0f7f0a8f1
Merge pull request #590 from vector-im/bwindels/platform-tests
Search platform-specific code for tests too
2021-11-19 10:26:35 +01:00
Bruno Windels f1a6a4924e commit yarn.lock too 2021-11-19 10:23:59 +01:00
Bruno Windels ec71e30ecb add Platform as entry point so also platform dependant code gets searched for tests 2021-11-19 10:23:18 +01:00
Bruno Windels f23227fc8b use latest version of impunity with support for multiple entry points 2021-11-19 10:22:59 +01:00
Bruno Windels 5a747cd829
Merge pull request #589 from vector-im/bwindels/fix-sdk-example
packages processed by post-install need to be in dependencies, or the script fails
2021-11-18 15:26:08 +01:00
Bruno Windels 6980921dab
some impl notes for SDK 2021-11-18 14:16:10 +00:00
RMidhunSuresh 8fcfd713e0 Use IAbortable 2021-11-17 20:28:44 +05:30
RMidhunSuresh ea2842f37f Return empty string 2021-11-17 20:28:44 +05:30
RMidhunSuresh 64a9892ee2 Use generic T in LockMap 2021-11-17 20:28:44 +05:30
RMidhunSuresh 048547828d Remove type Func 2021-11-17 20:28:44 +05:30
R Midhun Suresh a14a8c3a07 Create interface IDisposable
Co-authored-by: Bruno Windels <brunow@matrix.org>
2021-11-17 20:28:44 +05:30
RMidhunSuresh 08ef84d112 Mention return type 2021-11-17 20:28:44 +05:30
RMidhunSuresh 5a0c06473c Use undefined instead of null 2021-11-17 20:28:44 +05:30
RMidhunSuresh 1beb153f21 func --> Func 2021-11-17 20:28:44 +05:30
RMidhunSuresh 0c424cb77f Fix imports 2021-11-17 20:28:44 +05:30
RMidhunSuresh ebd1caf6d1 Convert enum.js to ts 2021-11-17 20:28:44 +05:30
RMidhunSuresh 0e18247184 Use constant type 2021-11-17 20:28:44 +05:30
RMidhunSuresh a945edfe07 Convert pbkdf2.js to ts 2021-11-17 20:28:44 +05:30
RMidhunSuresh 6c2aa1bf61 Convert hkdf.js to ts 2021-11-17 20:28:44 +05:30
RMidhunSuresh afecac3e3c Convert timeout.js to ts 2021-11-17 20:28:44 +05:30
RMidhunSuresh 8a169d5ddc Convert sortedIndex.js to ts 2021-11-17 20:28:44 +05:30
RMidhunSuresh ea0adb4407 Convert RetainedValue.js to ts 2021-11-17 20:28:44 +05:30
RMidhunSuresh 88ec1b575d Convert mergeMap.js to ts 2021-11-17 20:28:44 +05:30
RMidhunSuresh 1549d8add0 Convert LockMap to ts 2021-11-17 20:28:44 +05:30
RMidhunSuresh c8eb7ea7ac Convert Lock.js to ts 2021-11-17 20:28:44 +05:30
RMidhunSuresh a3460d8c2a Convert formatSize to ts 2021-11-17 20:28:44 +05:30
RMidhunSuresh 4ce7634201 Convert error.js to ts 2021-11-17 20:28:44 +05:30
RMidhunSuresh ef53a12f7a Fix imports 2021-11-17 20:28:44 +05:30
RMidhunSuresh 7d12c2ba54 Add return types 2021-11-17 20:28:44 +05:30
RMidhunSuresh 7270918b65 Convert Disposables to typescript 2021-11-17 20:28:44 +05:30
RMidhunSuresh dd74ed1957 Add types to disposeValue 2021-11-17 20:28:44 +05:30
RMidhunSuresh 7772643b0d Disposables.js --> Disposables.ts 2021-11-17 20:28:44 +05:30
Bruno Windels 0a433b90e3 packages processed by post-install need to be in dependencies, or the script fails 2021-11-17 15:09:25 +01:00
Bruno Windels efccc1e19e
Merge pull request #583 from vector-im/ts-conversion-logging
Convert src/logging to typescript
2021-11-17 14:37:42 +01:00
Bruno Windels 692ae25e76 remove unused method 2021-11-17 14:35:26 +01:00
Bruno Windels b5e9eb26ba reduce size of ILogItem interface further 2021-11-17 14:33:58 +01:00
Bruno Windels 4030a4918d explicitly check for undefined 2021-11-17 13:57:11 +01:00
Bruno Windels 41a10d9697 explicitly check for undefined 2021-11-17 13:56:20 +01:00
Bruno Windels fde0163b97 remove unneeded union type and simplify code 2021-11-17 13:53:27 +01:00
Bruno Windels 42e5fb33ba remove more non-public methods from ILogItem interface 2021-11-17 13:50:56 +01:00
Bruno Windels 526a818269 only used internally 2021-11-17 13:42:49 +01:00
Bruno Windels afc538e875 explicitly check for type, rather than truthy 2021-11-17 12:58:08 +01:00
Bruno Windels 74fb15e426 add future todo note 2021-11-17 12:54:44 +01:00
Bruno Windels 46dd78162f no need to dig into internals here 2021-11-17 12:54:32 +01:00
Bruno Windels 276d8d4a42 check for undefined, no need for ! 2021-11-17 12:39:57 +01:00
Bruno Windels b1d20178f8 add explicit void return type 2021-11-17 12:37:50 +01:00
Bruno Windels 5f362cbdbd remove dead code 2021-11-17 11:54:29 +01:00
Bruno Windels 695996d6e2 add ILogger and ILogExport interface, to give export correct return type
also move logging related types to own file
2021-11-17 11:39:12 +01:00
RMidhunSuresh 1b13f32d94 Remove resolved todo comment 2021-11-17 15:39:21 +05:30
Bruno Windels 3ee7e73ff0 item is optional here 2021-11-17 11:08:44 +01:00
Bruno Windels 90d7b73dd4 non-persisted queued items don't have an id yet, find them by ref equality 2021-11-17 11:08:29 +01:00
Bruno Windels f93bdd962a might as well use generic here 2021-11-17 10:50:55 +01:00
Bruno Windels 1942c31eff still finish item when not returning from sync callback 2021-11-17 10:42:54 +01:00
RMidhunSuresh d01271fb15 _run return T or void depending on boolean 2021-11-17 13:22:19 +05:30
RMidhunSuresh 07a1130db3 children can be array of ISerializedItem 2021-11-17 12:02:12 +05:30
RMidhunSuresh 835da58b53 Remove ! 2021-11-17 11:59:50 +05:30
R Midhun Suresh 9c8f96e233
value is optional
Co-authored-by: Bruno Windels <brunow@matrix.org>
2021-11-17 11:43:59 +05:30
RMidhunSuresh b0ab8cd77f Space before { 2021-11-17 11:40:43 +05:30
RMidhunSuresh 8fce29caf7 Explicitly check for undefined 2021-11-17 11:38:33 +05:30
RMidhunSuresh 14eaa57434 No need for type assertion here 2021-11-16 13:14:11 +05:30
RMidhunSuresh 58105824d9 Fix error in reduce 2021-11-16 13:08:13 +05:30
RMidhunSuresh 4704a70cb7 Remove todo comment 2021-11-16 13:06:47 +05:30
RMidhunSuresh 34a8463bf9 Fix jsdoc return type 2021-11-16 12:43:23 +05:30
RMidhunSuresh e339e730f4 Remove todo comment 2021-11-16 12:42:50 +05:30
RMidhunSuresh 286747c23c Add type annotation for ctor 2021-11-16 12:41:03 +05:30
RMidhunSuresh 3ee1607298 Convert utils to typescript 2021-11-16 12:32:49 +05:30
RMidhunSuresh 4161d31642 Convert NullLogger to typescript 2021-11-16 12:23:06 +05:30
RMidhunSuresh 4c1d7a8f2d Use generics over returning unknown 2021-11-15 22:47:38 +05:30
Bruno Windels 2da450d69d log signature verification failure in logger, not console 2021-11-15 15:27:57 +01:00
RMidhunSuresh fe69f84c85 Use undefined in LogItem.serialize 2021-11-15 19:32:16 +05:30
RMidhunSuresh ba5f2032ba Make properties in LogItem optional, not null 2021-11-15 19:17:49 +05:30
RMidhunSuresh 7097ba07d1 Replace LogLabelOrNull type with undefined 2021-11-15 18:59:33 +05:30
RMidhunSuresh 30a384fe1e Make LogFilter optional 2021-11-15 18:44:25 +05:30
RMidhunSuresh 520e0f1b89 Use interface ILogItem 2021-11-15 17:29:08 +05:30
RMidhunSuresh a7d059b3ed Fix imports 2021-11-14 19:42:18 +05:30
RMidhunSuresh bba44abf52 Convert console logger to ts 2021-11-14 16:24:16 +05:30
RMidhunSuresh 39d0708cca Add comment 2021-11-14 15:58:51 +05:30
RMidhunSuresh 2d8b719ab0 Add void return types as well 2021-11-14 15:55:42 +05:30
RMidhunSuresh 5efa27c2a3 Add more type annotations 2021-11-14 15:48:59 +05:30
RMidhunSuresh 67e8fc0c43 Add return types to methods in BaseLogger 2021-11-12 23:27:35 +05:30
RMidhunSuresh 8e42e3f21f Add types to returns in LogFilter.ts 2021-11-12 23:17:21 +05:30
RMidhunSuresh 29a8260514 Add explicit types for return in methods 2021-11-12 23:12:15 +05:30
RMidhunSuresh 8c7a765e11 Convert IDBLogger to ts 2021-11-12 15:06:21 +05:30
RMidhunSuresh f3d0f88f95 Make error public 2021-11-12 15:06:11 +05:30
RMidhunSuresh 2ddd2d16ed IDBLogger.js --> IDBLogger.ts 2021-11-11 16:50:46 +05:30
RMidhunSuresh 9fed2ca41b Use undefined instead of null 2021-11-11 16:25:14 +05:30
RMidhunSuresh eb7c5c4437 Use undefined only instead of both undefined and null 2021-11-11 16:08:25 +05:30
RMidhunSuresh 09851600f7 Remove unwanted types 2021-11-11 15:35:51 +05:30
RMidhunSuresh 425a3c85a9 Make error prop private and expose via getter 2021-11-11 13:24:52 +05:30
RMidhunSuresh 0f7a78ee25 Make return type explicit 2021-11-11 13:05:12 +05:30
Bruno Windels 7148f6fd41 wip to not babel deps 2021-11-10 19:10:23 +01:00
Bruno Windels e83781b26a make ie11 compatible 2021-11-10 19:10:06 +01:00
RMidhunSuresh cd7dccd804 Move interface to top 2021-11-10 19:13:35 +05:30
RMidhunSuresh 7a68c971aa Make field readonly 2021-11-10 19:07:24 +05:30
RMidhunSuresh cfa7708b57 Use type imports 2021-11-10 18:51:46 +05:30
Bruno Windels c47f872f6f
Merge pull request #578 from vector-im/fix-progressbar
Fix progressbar not working on attachment uploads
2021-11-10 11:17:02 +01:00
RMidhunSuresh ef2aad8956 Annotate LogFilter 2021-11-10 15:04:07 +05:30
RMidhunSuresh ab126729e0 Use LogLevel as type instead of number 2021-11-10 14:49:59 +05:30
RMidhunSuresh e3c85c585e Log callbacks can return more than Promises 2021-11-10 14:42:43 +05:30
RMidhunSuresh 0b4eca4724 Create alias for LogLevel | null 2021-11-10 14:29:23 +05:30
RMidhunSuresh 142d3ef543 Split LogItemValues into union of types 2021-11-10 13:45:37 +05:30
RMidhunSuresh ceb52eedaf Fix imports and add type annotations 2021-11-10 12:36:56 +05:30
RMidhunSuresh 772f7a2757 Account for duration being null 2021-11-10 12:17:43 +05:30
RMidhunSuresh db792ab5a9 Add type annotations to LogItem 2021-11-10 12:06:50 +05:30
RMidhunSuresh 97ec680af2 Remove .js files 2021-11-10 12:06:22 +05:30
RMidhunSuresh ba4d5453a2 Move type LogCallback to LogItem 2021-11-10 12:05:29 +05:30
Bruno Windels 36a982f7e2 WIP to run rollup twice, first with vite and babel, then to inline deps 2021-11-09 18:10:12 +01:00
RMidhunSuresh 2a5d30d749 Convert to enum 2021-11-09 22:32:02 +05:30
Bruno Windels 122528f9a9 also transpile typescript files
note that these have already been converted to javascript by vite
2021-11-09 17:59:58 +01:00
RMidhunSuresh 55401a746c Move type alias to LogItem
and add more type annotations
2021-11-09 22:28:26 +05:30
RMidhunSuresh 8cbc81b8bb Annotate method arguments 2021-11-09 20:57:47 +05:30
Bruno Windels da7f66a531 setup babel for legacy build as input transform plugin 2021-11-09 14:52:03 +01:00
RMidhunSuresh 7893a121c0 Initialize in field 2021-11-09 17:36:18 +05:30
RMidhunSuresh 4c5d028509 any --> unknown 2021-11-09 17:34:16 +05:30
RMidhunSuresh eef116e26b annotate labelOrValues 2021-11-09 17:19:46 +05:30
RMidhunSuresh 8fba3f4ca9 Add explaining comment 2021-11-09 15:39:24 +05:30
RMidhunSuresh 839d3fb689 Throw on export() in ConsoleLogger 2021-11-09 13:53:07 +05:30
RMidhunSuresh 377cc4ca1f Make BaseLogger abstract 2021-11-09 13:52:41 +05:30
RMidhunSuresh 030c46264b type annotate fields 2021-11-09 13:00:37 +05:30
RMidhunSuresh dad37dece3 .js --> .ts 2021-11-09 11:46:05 +05:30
RMidhunSuresh 57e2c4ea45 No need for handler to be async 2021-11-08 22:20:56 +05:30
RMidhunSuresh c1a8ffd814 respond with only for GET requests 2021-11-08 18:03:20 +05:30
Bruno Windels b95c918dc6
Merge pull request #580 from vector-im/bwindels/scroll-composer
add scrollbar when > 5 lines in composer
2021-11-08 12:25:39 +01:00
Bruno Windels e9586711e0 add scrollbar when > 5 lines in composer 2021-11-08 11:19:24 +01:00
Bruno Windels ffef4936f9 update caniuse defs again after rebase 2021-11-08 11:14:00 +01:00
Bruno Windels fcde507183 WIP 2021-11-08 11:13:02 +01:00
Bruno Windels 3b72157e64 apparently, package-overrides should use esm
otherwise they don't get transpiled for some reason
2021-11-08 11:12:19 +01:00
Bruno Windels 7dce579ac3 make babel plugin run after commonjs one added by vite 2021-11-08 11:12:19 +01:00
Bruno Windels 16918ddb7d ie11 build wip 2021-11-08 11:12:13 +01:00
Bruno Windels b65782e13c ignore tsc errors on vite-specific imports
or not understood by tsc in any case
2021-11-08 11:10:34 +01:00
Bruno Windels eb60f6717a add comment for improvements 2021-11-08 11:10:34 +01:00
Bruno Windels 923a1a2057 use vite define option to inject version number everywhere 2021-11-08 11:10:34 +01:00
Bruno Windels 216afd45cc vite/rollup plugin to inject and transform manifest & service worker 2021-11-08 11:10:34 +01:00
Bruno Windels 3fe1c0cdc3 tweak build and start command to use vite 2021-11-08 11:10:34 +01:00
Bruno Windels afadd25885 tweak build settings somewhat for now 2021-11-08 11:10:34 +01:00
Bruno Windels e2b20f466d remove unneeded package override 2021-11-08 11:10:34 +01:00
Bruno Windels 01712c3f23 make tests run again 2021-11-08 11:10:34 +01:00
Bruno Windels b6fda8865f make all dependencies use vite and remove post-install script / lib dir 2021-11-08 11:10:34 +01:00
Bruno Windels db3e8a9c6b rearrange assets and main.js to make them run with vite 2021-11-08 11:10:23 +01:00
RMidhunSuresh d1491cc203 More checks before returning 2021-11-08 15:11:41 +05:30
RMidhunSuresh d31371b486 Return on upload in sw 2021-11-08 14:37:32 +05:30
Bruno Windels 2afcddbf49 release v0.2.21 2021-11-05 21:08:49 +01:00
Bruno Windels 25fb645c4b
Merge pull request #577 from vector-im/bwindels/update-caniuse
update caniuse
2021-11-05 21:07:39 +01:00
Bruno Windels 5c689ac5b1 yarn wants deps in different order 2021-11-05 21:02:46 +01:00
Bruno Windels e1c8088de2 update caniuse browser definitions 2021-11-05 21:02:16 +01:00
Bruno Windels d40037ef49
Merge pull request #576 from vector-im/bwindels/setup-new-dehydrated-device-when-claiming
Setup new dehydrated device when claiming
2021-11-05 20:56:23 +01:00
Bruno Windels faa0246e28 setup new dehydrated device when claiming one 2021-11-05 20:53:04 +01:00
Bruno Windels 0749073120 clone key as olm clears it 2021-11-05 20:52:50 +01:00
Bruno Windels 2dccd36a6d
Merge pull request #575 from vector-im/bwindels/composer-layout-improvements
Composer layout improvements
2021-11-05 19:24:34 +01:00
Bruno Windels 23494ab630 bottom align send button, adjust paddings 2021-11-05 19:05:52 +01:00
Bruno Windels 2f15c9a4a7 show either attachment or send button, depending on composer value 2021-11-05 19:05:35 +01:00
Bruno Windels c3203fdacd explain this value 2021-11-05 19:05:09 +01:00
Bruno Windels 222c616148
Merge pull request #574 from vector-im/bwindels/multiline-composer-fixups
Multiline composer fixups
2021-11-05 18:44:54 +01:00
Bruno Windels 44e7e25cab clear height while sending or clearing, also fix #572 in the process 2021-11-05 18:36:59 +01:00
Bruno Windels fc1b9abe66 don't add line when hitting enter to send 2021-11-05 18:36:38 +01:00
Bruno Windels 365c8d0953
Merge pull request #566 from vector-im/composer-improvements
Support for multiline messages
2021-11-05 18:20:02 +01:00
Bruno Windels 8ffd98162c don't make the composer shrink for now, timeline loses scroll position 2021-11-05 17:48:44 +01:00
Bruno Windels c671596c6f only schedule one resize callback per frame 2021-11-05 17:48:08 +01:00
Bruno Windels b22437840d don't set explicit height, rely on rows=1 for default height 2021-11-05 17:47:20 +01:00
Bruno Windels 4c5fe824c2
Merge pull request #569 from vector-im/fix-400
Show redacted tile for redacted messages in encrypted rooms
2021-11-05 15:57:36 +01:00
Bruno Windels 1fee773313
Merge pull request #571 from vector-im/fix-570
Make progress bar work for image uploads in Chrome
2021-11-05 15:56:33 +01:00
Bruno Windels 6be952491a
Merge pull request #573 from vector-im/fix-517-2
Continue filling gaps that return only non-rendered events in the first backfill
2021-11-05 15:45:45 +01:00
Bruno Windels 781147bf0e add some comments and rename for clarity 2021-11-05 15:42:07 +01:00
RMidhunSuresh a4cd40c2f8 Keep filling gap until sibling changes 2021-11-05 17:25:29 +05:30
RMidhunSuresh 96a2dd7c72 Add event handler before executing open
Signed-off-by: RMidhunSuresh <hi@midhun.dev>
2021-11-04 00:02:50 +05:30
RMidhunSuresh f6b7dcbad7 Show redacted tile 2021-11-03 20:03:47 +05:30
Bruno Windels b011c3df03 release v0.2.20 2021-11-03 02:38:52 +01:00
Bruno Windels 014acbfaf5
Merge pull request #564 from vector-im/bwindels/dehydrated-device
Device dehydration support
2021-11-03 00:52:46 +01:00
Bruno Windels ee9c9b33ca fix lint again 2021-11-03 02:36:53 +01:00
Bruno Windels b2d8f5f023 fix lint 2021-11-03 02:35:22 +01:00
Bruno Windels 790b9cbc13 require a flag to enable account setup during login
as it blocks login from progressing, any sdk usage should enable
to indicate they are listening for the AccountSetup status.
2021-11-03 02:28:01 +01:00
Bruno Windels bffe34fe0a await write key 2021-11-03 02:20:27 +01:00
Bruno Windels a8022077f6 add minimal logging 2021-11-03 02:20:11 +01:00
Bruno Windels 80a98f04c7 pickle clears the key, so slice it before calling so we can reuse for 4s 2021-11-03 02:08:27 +01:00
Bruno Windels 8a36eb4532 check mac of dehydrated key to match default 4s key mac before adopting 2021-11-03 02:07:57 +01:00
Matthew Hodgson abef4f0f79
Merge pull request #568 from vector-im/initial-github-action-for-docker-images
Add GH Action to build the docker image
2021-11-02 00:19:45 +00:00
Ben Banfield-Zanin 05fe68823a
Add GH Action to build the docker image
Builds the docker image on pushes for: tags, PRs, master.

Publishes docker images as per:
* `latest` & `<TAG>` for tags
* `master` for `master`

Same as https://github.com/matrix-org/lb/pull/8 & https://github.com/matrix-org/matrix-content-scanner/pull/46
2021-11-01 14:55:32 +00:00
Bruno Windels 567cdd5510 WIP for enabling session backup from dehydration key 2021-10-29 19:17:31 +02:00
Bruno Windels 6d9d8797fe use same UI as in settings to pick between recovery key and passphrase 2021-10-29 16:40:35 +02:00
Bruno Windels 44a26fd340 key backup: add disable button,and enabling add dehydrated device option 2021-10-29 15:48:28 +02:00
Bruno Windels 3b3751c827 remove dehydrated device test button from settings 2021-10-29 15:47:14 +02:00
Bruno Windels 6273d723f1 fix typo 2021-10-29 15:24:24 +02:00
RMidhunSuresh 6863fef7e5 Resize composer with text
Signed-off-by: RMidhunSuresh <hi@midhun.dev>
2021-10-29 17:00:02 +05:30
RMidhunSuresh 3a6e74ae1c Add css style
Signed-off-by: RMidhunSuresh <hi@midhun.dev>
2021-10-29 15:11:34 +05:30
RMidhunSuresh 16bec0a656 Convert input to textarea
Signed-off-by: RMidhunSuresh <hi@midhun.dev>
2021-10-29 15:11:12 +05:30
Bruno Windels c3dfdde626 be forgiving when dispose has already been called 2021-10-28 12:04:42 +02:00
Bruno Windels 544019f67d ensure olm Account in dehydrated device is freed on error 2021-10-28 11:52:32 +02:00
Bruno Windels bef12c7a8f prevent double free on olm account when logging out
by ensuring we only dispose the e2ee/Account once, as well as
the Session and other classes
2021-10-28 11:48:25 +02:00
Bruno Windels 3ef37c15c7 remove import session button as it's not supported anymore 2021-10-28 11:47:54 +02:00
Bruno Windels 68a6113c26 add logout button to session load error screen 2021-10-28 11:47:31 +02:00
Bruno Windels cbccca20d0 remove leftover logging 2021-10-27 18:09:31 +02:00
Bruno Windels e3378d5636 use correct device_id in signatures for dehydrating device
completely replace device id for dehydrating device
so we don't have to pass it down the stack
2021-10-27 18:08:50 +02:00
Bruno Windels c89e414bb5 WIP3 2021-10-27 15:08:53 +02:00
Bruno Windels 718b410253 WIP2 2021-10-27 10:26:36 +02:00
Bruno Windels faf4ea6434 WIP 2021-10-26 18:47:46 +02:00
Bruno Windels abb802b881 release v0.2.19 2021-10-26 15:38:50 +02:00
Bruno Windels d9ecf38e42
Merge pull request #563 from vector-im/bwindels/exportlogsonsessionloadfail
add export logs button when session fails to load
2021-10-26 15:35:50 +02:00
Bruno Windels 7ef19e0ead add export logs button when session fails to load 2021-10-26 15:30:52 +02:00
Bruno Windels c621ccf679 release v0.2.18 2021-10-26 15:10:44 +02:00
Bruno Windels 0f0719eaa2
Merge pull request #560 from vector-im/bwindels/logout
Add Log out
2021-10-26 15:10:21 +02:00
Bruno Windels 5b889f0b32
Merge pull request #562 from vector-im/bwindels/diagnose-561
return static string when member is missing and add logging
2021-10-26 15:10:08 +02:00
Bruno Windels 82a0c1024c return static string when member is missing and add logging 2021-10-26 15:08:51 +02:00
Bruno Windels af85fe3892 confirm before logging out 2021-10-26 14:48:37 +02:00
Bruno Windels f998041748 add logout button in settings 2021-10-26 12:49:31 +02:00
Bruno Windels 2b884e73db remove action buttons on session picker
and now that we're adding logout, none of them are something we want to support really
2021-10-26 11:43:38 +02:00
Bruno Windels e3c5def536 release v0.2.17 2021-10-26 11:32:42 +02:00
Bruno Windels fae4493abc
Merge pull request #554 from vector-im/bwindels/fix-551
Only keep a limited amount of olm InboundGroupSession objects in memory to prevent OOM
2021-10-26 11:30:10 +02:00
Bruno Windels 67dd929951 put key session check in method 2021-10-26 11:14:46 +02:00
Bruno Windels 805c2657f2 remove unrelated file 2021-10-26 11:07:17 +02:00
Bruno Windels ab2f15b5a2 prevent cache hiding better keys in storage (+ tests) 2021-10-25 19:17:13 +02:00
Bruno Windels 3c2604b384 test that sessions get free'd correctly 2021-10-25 17:33:33 +02:00
Bruno Windels 12b5bd3a4f
Merge pull request #553 from moritzdietz/moritzdietz/update-faq
Update FAQ: Add note about published builds
2021-10-25 17:21:27 +02:00
Bruno Windels 74e8bc3bda write unit tests 2021-10-25 17:19:48 +02:00
Bruno Windels 6bbce06d93 start writing tests for key loader 2021-10-22 19:01:20 +02:00
Bruno Windels 22361bdf42 don't need to dispose room keys anymore, they are owned by the loader 2021-10-22 18:08:09 +02:00
Bruno Windels 076f450ec7 this can be const 2021-10-22 18:01:26 +02:00
Bruno Windels 6d8ec69a4d fix lint 2021-10-22 18:01:17 +02:00
Bruno Windels b7e3a54e15 remove now usused code 2021-10-22 17:51:00 +02:00
Bruno Windels 2943cb525f add comment about possible future optimization 2021-10-22 17:50:45 +02:00
Bruno Windels 1278288a42 cleanup RoomKey to changes and better naming 2021-10-22 17:50:30 +02:00
Bruno Windels 66a93ee108 adapt Session and RoomEncryption to megolm/Decryption API changes 2021-10-22 17:48:53 +02:00
Bruno Windels ac23119838 convert SessionDecryption to TS and adapt to use KeyLoader 2021-10-22 17:48:35 +02:00
Bruno Windels b55930f084 convert ReplayDetectionEntry to typescript 2021-10-22 17:47:29 +02:00
Bruno Windels d6e243321b convert megolm/Decryption to typescript and adapt to KeyLoader 2021-10-22 17:46:39 +02:00
Bruno Windels 2ddb3fbf72 cleanup 2021-10-22 17:45:55 +02:00
Bruno Windels 45dc2162dc fix unit tests 2021-10-22 17:30:20 +02:00
Bruno Windels 77d10c93d6 convert groupby and megolm decryption utils to typescript 2021-10-21 14:40:51 +02:00
Bruno Windels 66a77519d7 implement key caching in KeyLoader
merging session cache into it so we can better manage and recycle
keys without exposing too low-level public methods on BaseLRUCache.

Using refCount instead of inUse flag as a key can of course be used
by multiple useKey calls at the same time.
2021-10-21 11:12:54 +02:00
Bruno Windels 3bafc89855 remove unused draft code 2021-10-20 15:25:11 +02:00
Bruno Windels 4fa285e85a convert LRUCache to ts 2021-10-20 15:24:58 +02:00
Bruno Windels 041cedbc58 fix typescript extension change 2021-10-20 15:24:39 +02:00
Bruno Windels cbf82fcd29 cleanup code so far 2021-10-20 15:14:17 +02:00
Bruno Windels 5dc0c8c0b3 make 'better' better 2021-10-20 13:38:54 +02:00
Moritz Dietz c83f78044e
Update FAQ to highlight about the existence of published builds 2021-10-20 12:46:37 +02:00
Bruno Windels d7407ecf66 WIP 2021-10-20 11:39:01 +02:00
Bruno Windels 82aac93f36
Update SDK.md 2021-10-20 07:05:39 +00:00
Bruno Windels c92d6ecbb6
Merge pull request #549 from danger89/patch-1
Add HTML title & icon
2021-10-08 10:39:08 +02:00
Melroy van den Berg a20fe2b5a6
Add HTML title & icon
Add missing HTML title and icon
2021-10-06 20:38:39 +02:00
Danila Fedorin 3d2c74a760 Add type annotations to SortedArray 2021-10-03 22:19:46 -07:00
Danila Fedorin 7b2e452cd5 Rename SortedArray to TypeScript 2021-10-03 22:19:46 -07:00
Danila Fedorin 1363af24a7 Add type annotations to MappedList 2021-10-03 22:19:46 -07:00
Danila Fedorin 84187ce109 Make updater optional in BaseObservableList 2021-10-03 22:19:44 -07:00
Danila Fedorin 0466b49520 Rename MappedList to TypeScript 2021-10-03 22:18:12 -07:00
Danila Fedorin 3b131f2db6 Add type annotations to ConcatList 2021-10-03 22:18:12 -07:00
Danila Fedorin 588da9b719 Relax types on BaseObservableList and add helper for tests 2021-10-03 22:18:12 -07:00
Danila Fedorin ddca467e30 Rename ConcatList to TypeScript 2021-10-03 22:18:12 -07:00
Danila Fedorin 8466a910da Add type annotations to AsyncMappedList 2021-10-03 22:18:12 -07:00
Danila Fedorin 0e6c59983f Generalize BaseMappedList to allow mappers to promises 2021-10-03 22:18:10 -07:00
Danila Fedorin e6de873b6e Rename AsyncMappedList to TypeScript 2021-10-03 22:16:46 -07:00
Danila Fedorin b148f3ca9e Add type annotations to ObservableArray 2021-10-03 22:16:46 -07:00
Danila Fedorin 348a9c83f5 Rename ObservableArray to TypeScript 2021-10-03 22:16:46 -07:00
Bruno Windels 6517704850
Merge pull request #545 from vector-im/bwindels/fixbuild
the build currently does not like override for some reason
2021-10-01 14:09:07 +02:00
Bruno Windels cc58d27122 the build currently does not like override for some reason 2021-10-01 14:07:58 +02:00
Bruno Windels 39d5073f49 remove user and password 2021-10-01 13:37:37 +02:00
Bruno Windels 191cb78d8f
Merge pull request #544 from vector-im/bwindels/sdk-export
Provide very basic SDK interface
2021-10-01 13:33:13 +02:00
Bruno Windels fbfda03138 don't let tsc check build system specific files 2021-10-01 13:32:09 +02:00
Bruno Windels 23da4ae2dd point to SDK docs from FAQ 2021-10-01 13:27:58 +02:00
Bruno Windels 675ee59e50 Add instructions and example code for basic SDK usage 2021-10-01 13:26:14 +02:00
Bruno Windels 193a0e1a4f move UI docs to doc folder 2021-10-01 13:26:03 +02:00
Bruno Windels c1d20cb9f9 also call this -Path 2021-10-01 12:56:36 +02:00
Bruno Windels d2eeaab5f5 get assets paths with import "asset?url" specifically for vitejs 2021-10-01 12:49:41 +02:00
Bruno Windels aac0e74b9d also export LoadStatus 2021-10-01 12:49:30 +02:00
Bruno Windels f89d169ef3 provide library entry point that provides convenient reexports of the public classes 2021-10-01 11:30:42 +02:00
Bruno Windels de22a0790f
Merge pull request #543 from vector-im/bwindels/typescript-observable
Typescript conversion of base observables
2021-10-01 10:10:20 +02:00
Bruno Windels 1c06e36c1c add override keyword 2021-10-01 10:07:17 +02:00
Bruno Windels 3de3481765 prefer optional syntax over '| null' 2021-10-01 10:05:56 +02:00
Bruno Windels 6b50a63e95 missing space 2021-10-01 10:02:32 +02:00
Bruno Windels 393e2f809e make subscription handle return undefined, so we can reassign optional members in one statement 2021-10-01 10:01:52 +02:00
Bruno Windels a0f443ccc3
Merge pull request #521 from DanilaFe/typescript-observable
Start migrating Observable code to TypeScript.
2021-10-01 09:54:29 +02:00
Bruno Windels 155207ed95
Merge pull request #392 from MidhunSureshR/documentation
Documentation for Hydrogen
2021-09-30 18:38:43 +02:00
Bruno Windels 255e479d47
Merge pull request #541 from vector-im/bwindels/fix-540
scroll room list to top when entering query first
2021-09-30 17:24:00 +02:00
Bruno Windels e42739ec81 scroll room list to top when entering query first 2021-09-30 17:19:42 +02:00
Bruno Windels 8911588de9 release v0.2.16 2021-09-30 12:48:45 +02:00
Bruno Windels 3fba2c6513
Merge pull request #525 from vector-im/bwindels/fix-sssskey-conversion
fix sssskey not being migrated properly
2021-09-30 10:19:31 +02:00
Bruno Windels 89add8b684 fix sssskey not being migrated properly 2021-09-30 10:18:03 +02:00
Bruno Windels 31a70e1b8e release v0.2.15 2021-09-30 09:52:35 +02:00
Bruno Windels dcb08f5266
Merge pull request #524 from vector-im/bwindels/fix-492
clear relations for room when forgetting room
2021-09-30 09:51:59 +02:00
Bruno Windels d10d27c1d6 clear relations for room when forgetting room 2021-09-30 09:49:45 +02:00
Bruno Windels 4cebe26186
Merge pull request #523 from vector-im/bwindels/clear-cache-after-515
clear history cache to purge potential timeline corruption from #515
2021-09-30 09:41:24 +02:00
Bruno Windels f8f4bb4eac
Merge pull request #520 from vector-im/bwindels/fix-139
Keep backup of e2ee identity in localStorage when idb gets cleared
2021-09-30 09:28:56 +02:00
Bruno Windels 42f1603d81 use correct prefix to remove local storage value 2021-09-30 09:25:35 +02:00
Bruno Windels 6ea835c2d1
Merge pull request #522 from xunzhou/master
Required pkg for aarch64 docker build
2021-09-30 08:48:47 +02:00
Bruno Windels ae68264db4 don't use switch where single if/else works 2021-09-30 08:42:00 +02:00
Bruno Windels ccda93cc82 remove leftover logging 2021-09-30 08:40:49 +02:00
Bruno Windels 3556878a1e clear history cache to purge potential timeline corruption from #515 2021-09-30 08:37:33 +02:00
xunzhou 17f24942da Required pkg for aarch64 docker build 2021-09-29 19:31:39 -07:00
Danila Fedorin c80dfb10a2 Add type annotations to BaseMappedList 2021-09-29 18:41:30 -07:00
Danila Fedorin 99164eb0d8 Rename BaseMappedList to TypeScript 2021-09-29 18:17:38 -07:00
Danila Fedorin bf53449f66 Add type annotations to common 2021-09-29 18:13:49 -07:00
Danila Fedorin e53f3d23d5 Rename common to TypeScript 2021-09-29 18:10:09 -07:00
Danila Fedorin 64ba656043 Update ListView and TimelineListView 2021-09-29 18:08:13 -07:00
Danila Fedorin 414280ada9 Add type annotations to BaseObservableList 2021-09-29 18:05:30 -07:00
Danila Fedorin 3952c3b969 Rename BaseObservableList to TypeScript 2021-09-29 17:46:51 -07:00
Danila Fedorin ab6ce62551 Add type annotations to ObservableValue 2021-09-29 17:43:17 -07:00
Danila Fedorin a7360f409e Rename ObservableValue to TypeScript 2021-09-29 17:42:57 -07:00
Danila Fedorin 319027e2e3 Add type annotations to BaseObservable 2021-09-29 17:42:57 -07:00
Danila Fedorin d73dea797a Rename BaseObservable to TypeScript 2021-09-29 17:18:22 -07:00
Bruno Windels e0b9a3fa50 create e2ee identity also when storage got cleared without backup 2021-09-29 20:07:42 +02:00
Bruno Windels a1c66738db migration to initialize & restore e2ee identity backup 2021-09-29 19:39:26 +02:00
Bruno Windels 3a064d6796 a IDBRequest when opening the database does not have a source 2021-09-29 19:21:42 +02:00
Bruno Windels 2ef7251079 move ssssKey to e2ee prefix as well so it gets backed up too 2021-09-29 19:21:06 +02:00
Bruno Windels 77bd0d3f3c store e2ee session values as well in localStorage 2021-09-29 11:49:58 +02:00
Bruno Windels 004aa5d3dc
Merge pull request #519 from vector-im/bwindels/rickfixes
Fix immediate errors for sdk usage
2021-09-29 11:12:17 +02:00
Bruno Windels 1b87eaf21e move packages needed for post-install script to (non-dev) deps 2021-09-29 11:02:59 +02:00
Bruno Windels cd071e47e0 release v0.2.14 2021-09-28 14:59:40 +02:00
Bruno Windels 9a96a5b7bb
Merge pull request #516 from vector-im/bwindels/fix-515
Fix interpreting hex keys as decimal
2021-09-28 14:25:33 +02:00
Bruno Windels e31d3abc97 fix ts errors in TimelineEventStore unit tests 2021-09-28 14:20:54 +02:00
Bruno Windels ec2f1b9833 add unit tests for findExistingKeys 2021-09-28 14:20:21 +02:00
Bruno Windels edc3a1d33c convert storage mock to TS and add utility for mock raw database 2021-09-28 14:19:59 +02:00
Bruno Windels 9036b21b5c don't interpret hex as decimal when decoding the key
this fixes #515 as it was causing the gap not to be closed,
because the fragment id was never equal.
2021-09-28 11:34:55 +02:00
Bruno Windels 0846fcc05d add more logging during gap filling 2021-09-27 16:34:12 +02:00
Bruno Windels f55f450850 fix findExistingKeys too many (existing but not requested) keys 2021-09-27 16:27:52 +02:00
Bruno Windels cc0b938a6d release v0.2.13 2021-09-24 18:45:33 +02:00
Bruno Windels 7c44fb8cd4
Merge pull request #510 from vector-im/bwindels/fix-499
move over word-break lines
2021-09-24 18:44:41 +02:00
Bruno Windels 6982f55cd7 move over word-break lines
this didn't get moved over when converting the timeline tile css to css grid
2021-09-24 18:42:47 +02:00
Bruno Windels e2d7954846
Merge pull request #509 from vector-im/bwindels/fix-menupositioning
automatically position popups using a simpler algorithm
2021-09-24 18:32:36 +02:00
Bruno Windels 71bd797dd4 automatically position popups using a simpler algorithm 2021-09-24 18:28:06 +02:00
Bruno Windels a7b6fe4b22
Merge pull request #508 from vector-im/bwindels/fix-393
don't (re)link fragments in fill, close gap if overlap w linked fragment
2021-09-24 15:42:49 +02:00
Bruno Windels b75e2fe0ce decode straight to EventKey object 2021-09-24 15:40:33 +02:00
Bruno Windels 0d798178b0 log neighbor fragment id when closing gap 2021-09-23 18:50:40 +02:00
Bruno Windels c6ed0abfd7 eventIds is missing 2021-09-23 18:49:23 +02:00
Bruno Windels e6f7f213ec don't (re)link fragments in fill, close gap if overlap w linked fragment 2021-09-23 18:02:05 +02:00
Bruno Windels 45917eae1d
Merge pull request #494 from vector-im/DanilaFe/backfill-changes
Unit tests for GapWriter, using a new timeline mock utility
2021-09-23 10:15:37 +02:00
Bruno Windels 4b7cb6da9e make backfill limit explicit 2021-09-23 10:10:22 +02:00
Bruno Windels dd71fdbe08 add comment 2021-09-23 10:04:58 +02:00
Bruno Windels 6c12f0f66f release v0.2.12 2021-09-22 10:45:46 +02:00
Bruno Windels 0848306cb0
Merge pull request #506 from vector-im/bwindels/fix-504
Drop events that have been synced before
2021-09-22 10:44:55 +02:00
Bruno Windels 498c00fe3c no need for try catch here as we already catch in getKeys 2021-09-22 10:38:29 +02:00
Bruno Windels ac5a4c2bc6 pass log item everywhere to tryInsert 2021-09-22 10:33:40 +02:00
Bruno Windels b58e10521f don't log tryInsert failures anymore as everything is logged in Store 2021-09-22 10:23:28 +02:00
Bruno Windels 1963635dd7 also log index keys for a value when write fails in Store 2021-09-22 10:22:52 +02:00
Bruno Windels a19d93dbef don't swallow anything that isn't a request error 2021-09-22 09:36:26 +02:00
Bruno Windels 704d7b32da add tests 2021-09-21 21:04:29 +02:00
Bruno Windels 6cded5319a change timelineEventStore.insert to tryInsert 2021-09-21 21:04:10 +02:00
Bruno Windels 12add19c31 add Store.tryAdd, which prevent abort on ConstraintError 2021-09-21 21:03:29 +02:00
Bruno Windels 0d486a14f6 add the logger property to the null logger as well, forgot this before 2021-09-21 21:01:35 +02:00
Bruno Windels 2d2521cd9a add prototype to show we can prevent the txn from being aborted on error 2021-09-21 20:58:16 +02:00
Danila Fedorin 92dcc6c980 Remove duplicated lines 2021-09-21 09:39:09 -07:00
Bruno Windels b8548afb71 export temporary vm typ def 2021-09-21 10:41:01 +02:00
Bruno Windels 07c6bf7055
Merge pull request #503 from vector-im/bwindels/fix-reactions-vm-null
use mapped value rather than reading parent vm again
2021-09-21 10:09:05 +02:00
Bruno Windels a020febf8d use mapped value rather than reading parent vm again 2021-09-21 10:02:17 +02:00
Danila Fedorin a3a743881d Make test adjustments requested in PR. 2021-09-20 19:37:39 -07:00
Danila Fedorin 50c8b995c3 Undo GapWriter algorithm changes 2021-09-20 18:41:01 -07:00
Bruno Windels 0d6881ad22
Merge pull request #502 from vector-im/fix-util-ts-import
Fix util import
2021-09-20 10:19:45 +02:00
Danila Fedorin 9f6c48cf0c Merge branch 'master' into DanilaFe/backfill-changes 2021-09-17 15:19:16 -07:00
Danila Fedorin 6d84cc0a81 Fix util import 2021-09-17 14:51:20 -07:00
Danila Fedorin 820b048272 Finish up the more difficult tests 2021-09-17 10:57:51 -07:00
Bruno Windels 08fe430c4b release v0.2.11 2021-09-17 18:38:30 +02:00
Bruno Windels 4759b44233
Merge pull request #500 from vector-im/bwindels/idb-write-logging
IDB write error logging
2021-09-17 18:37:55 +02:00
Bruno Windels 53a0d29244 example how to get more context for the failure of a write operation 2021-09-17 18:25:28 +02:00
Bruno Windels f5467a653c fix stores returning the delete promise which isn't returned anymore
I checked these aren't awaited in any js file
2021-09-17 18:24:24 +02:00
Bruno Windels ad45016b87 process write errors on complete or abort in transaction 2021-09-17 18:23:31 +02:00
Bruno Windels 533b0f40d3 pass write errors in a store to the transaction 2021-09-17 18:21:48 +02:00
Bruno Windels aeedb948cc pass logger to Storage and make it available in Transaction 2021-09-17 18:19:26 +02:00
Danila Fedorin 82c35355b6 Start translating GapWriter tests to using MockTimeline 2021-09-16 23:54:13 -07:00
Danila Fedorin 7d27b46873 Make the response of TimelineMock look like a room sync response 2021-09-16 23:53:38 -07:00
Danila Fedorin bcfca9ad9a Make event ID function public 2021-09-16 21:53:56 -07:00
Bruno Windels cce7606960 release v0.2.10 2021-09-16 22:43:38 +02:00
Bruno Windels 5cf8c3c7d2
Merge pull request #498 from vector-im/bwindels/fix-replies
Fix replies after releasing scroll improvements
2021-09-16 22:43:15 +02:00
Bruno Windels 41089e2806 remove leftover logging 2021-09-16 22:30:03 +02:00
Bruno Windels c4477a81ea don't create the avatar node when we won't need it 2021-09-16 22:29:48 +02:00
Bruno Windels f02d52ce4c with !interactive, an array of an array is not a valid Child value
why didn't this fail before?
2021-09-16 22:29:13 +02:00
Bruno Windels bae8dc7dd7 changes should be true when replyVM is not set and we set an entry 2021-09-16 22:28:19 +02:00
Bruno Windels ccc5aac025 release v0.2.9 2021-09-16 18:20:25 +02:00
Bruno Windels 356af3ae7c
Merge pull request #497 from vector-im/bwindels/timeline-height-fix
Fix timeline height on mobile
2021-09-16 18:19:58 +02:00
Bruno Windels e978202f36 add min-height: 0 2021-09-16 17:48:29 +02:00
Bruno Windels 9a9b206bf5
Merge pull request #486 from vector-im/bwindels/fix-scroll-jumps
Fix scroll jumps and loading of gaps not at top of the timeline
2021-09-16 17:14:17 +02:00
Bruno Windels a62627f6db fix lint warning 2021-09-16 16:39:17 +02:00
Bruno Windels 381a12db20 load 20 entries initially in timeline, otherwise it flickers a bit 2021-09-16 16:34:13 +02:00
Bruno Windels 898d32c6da use quotes in css url path 2021-09-16 16:34:01 +02:00
Bruno Windels 6ec2712eec remove debug logging 2021-09-16 16:33:09 +02:00
Bruno Windels c9f79343ef remove obsolete comment 2021-09-16 16:32:59 +02:00
Bruno Windels b1994918fe
Merge pull request #488 from vector-im/bwindels/list-and-timeline-view-to-ts
Convert ListView and TimelineView to typescript
2021-09-16 16:08:34 +02:00
Bruno Windels a6bcfac597 rename UIView to IView 2021-09-16 15:58:48 +02:00
Bruno Windels b71a26b04a avoid using ! in ListView 2021-09-16 15:56:57 +02:00
Bruno Windels 81ec8dca8c improve typing in utils 2021-09-16 15:51:45 +02:00
Bruno Windels d9ddeaf107 fix TS errors in TimelineView 2021-09-16 15:49:03 +02:00
Bruno Windels 00aa40ea7b copy Daniel's conversion of TemplateView to TypeScript from microui
with some minor type adjustments
2021-09-16 15:46:02 +02:00
Bruno Windels ea4d833a43 reuse ViewNode in Child type 2021-09-16 15:39:25 +02:00
Bruno Windels 040efa970c make className binding always have a value (may be undefined through T) 2021-09-16 15:39:17 +02:00
Bruno Windels 68fb093c9e don't require mount args in mountView, like in UIView interface 2021-09-16 15:23:48 +02:00
Bruno Windels 060f4aa297 change extension in preparation for TS conversion 2021-09-16 14:02:36 +02:00
Bruno Windels 65f69a121b copy Daniel's conversion of BaseUpdateView to TypeScript from microui 2021-09-16 14:01:33 +02:00
Bruno Windels 69672dd99c copy Daniel's conversion of html.js to TypeScript from microui 2021-09-16 10:45:06 +02:00
Bruno Windels cce8207870 copy Daniel's conversion of EventEmitter to TypeScript from microui 2021-09-16 10:23:03 +02:00
Danila Fedorin bbd174cd67 Add a class to mock timeline requests 2021-09-15 16:15:18 -07:00
Bruno Windels 2396a84c99 leave out svg editor markup from icon 2021-09-15 18:39:04 +02:00
Bruno Windels e4101ece65 add "jump down" button in timeline 2021-09-15 18:30:08 +02:00
Bruno Windels 1df12b8c89 only allow pixel gaps of < 1px for stick to bottom to prevent eleweb bug
https://github.com/matrix-org/matrix-react-sdk/pull/6751
2021-09-15 17:23:53 +02:00
Bruno Windels 2c415e37e7 where ResizeObserver is support, restore anchored node on resize 2021-09-15 17:23:28 +02:00
Bruno Windels 04edff29cf give more top padding to gaps that appear in the middle of the timeline 2021-09-15 15:57:31 +02:00
Danila Fedorin d2b604e1dd Stop using `at` to fix tests. 2021-09-14 15:57:32 -07:00
Danila Fedorin df273c5e2c Store more events from backfill 2021-09-14 15:40:15 -07:00
Danila Fedorin b2b5690739 Add more tests 2021-09-14 13:54:14 -07:00
Danila Fedorin f8117b6f98 Lift transaction property to QueryTarget 2021-09-14 11:18:24 -07:00
Danila Fedorin 41e568f783 Add more tests and extract common test code 2021-09-14 11:15:13 -07:00
Danila Fedorin 31577cd496 Draft first two tests 2021-09-14 10:24:18 -07:00
Bruno Windels 247d6a2148 add comments 2021-09-14 17:01:30 +02:00
Danila Fedorin b3df37b0bc Add the beginning of a tests function for GapWriter 2021-09-13 17:01:32 -07:00
Danila Fedorin 713f675f3a Mock IDBKeyRange, too 2021-09-13 17:00:49 -07:00
Danila Fedorin 71694787cd Add an IDBFactory mock parameter 2021-09-13 16:55:55 -07:00
Bruno Windels feb0cf7e39 fix viewport changing width when timeline starts filling the viewport
otherwise centered tiles like announcementview jump a bit vertically
2021-09-13 15:40:15 +02:00
Bruno Windels 906e5886e1 fix tiny jump when timeline starts filling viewport 2021-09-13 15:39:56 +02:00
Bruno Windels ecccadb77e avoid requestIdleCallback as it is not supported on Safari and IE11 2021-09-13 14:53:34 +02:00
Bruno Windels 8858cffc55 fallback from scrollBy() to setting scrollTop on IE11 2021-09-13 14:53:08 +02:00
Bruno Windels 02b8b37b4c disable native scroll anchoring as it interferes with our impl 2021-09-13 13:11:25 +02:00
Bruno Windels 5c40b75eab don't override newly requested ranges when retrying loadattop 2021-09-10 15:25:19 +02:00
Bruno Windels 9411e6f065 WIP 2021-09-10 14:47:05 +02:00
Bruno Windels 98cc1e2715 don't try to load more when end of timeline reached 2021-09-10 14:17:40 +02:00
Bruno Windels fe4bb5db40 remove comment 2021-09-09 17:15:28 +02:00
Bruno Windels d1242c4b67 make gaps taller 2021-09-09 17:15:06 +02:00
Bruno Windels d0f122a2be WIP-ish, batch visible range requests, allow setting empty ranges and...
don't try to notify when the tile becomes invisible again,
we don't need it currently
2021-09-09 17:14:16 +02:00
Bruno Windels 98678b991b no need to store visible state on gap tile & don't fill if edge reached 2021-09-08 16:39:46 +02:00
Bruno Windels ab67ac00b1 restore bottom of timeline initially after attach to DOM
this will also load more items if the viewport isn't filled
2021-09-08 12:06:17 +02:00
Bruno Windels e89f60bac0 fill gap tiles when they become visible 2021-09-08 12:05:59 +02:00
Bruno Windels 7578bfa3d9 let tiles know when they become visible & load more tiles close to top 2021-09-08 12:05:19 +02:00
Bruno Windels f4b4638ea8 actually, pass in just the tile instances for expressing the range 2021-09-07 19:10:53 +02:00
Bruno Windels b3cd2a0e03 express the visible range with EventKeys rather than list indices
This is less ambiguous in case the DOM and the ObservableList would be
out of sync.
2021-09-07 17:48:49 +02:00
Bruno Windels c78a83d398 restore most bottom tile in VP on any list change
and tell view model visible range so it can load more or fill gaps, ...
2021-09-07 15:17:27 +02:00
Bruno Windels 44ea65dd3e
Merge pull request #458 from MidhunSureshR/fix-457
Opening member-details panel should not open/close grid view
2021-09-07 12:16:24 +02:00
Bruno Windels ad4ec5f04c convert TimelineView to typescript 2021-09-06 17:51:32 +02:00
Bruno Windels 632d29795a convert ListView to typescript 2021-09-06 17:12:14 +02:00
RMidhunSuresh df74d0c814 Remove array from logic
Signed-off-by: RMidhunSuresh <hi@midhun.dev>
2021-09-06 17:41:36 +05:30
Bruno Windels c6b020a9e7 Rename TimelineList to TimelineView for consistency 2021-09-06 13:31:43 +02:00
Bruno Windels 9592d286c2
Merge pull request #460 from vector-im/bwindels/ts-eventkey-direction
Convert EventKey & Direction to typescript + some ts tidbits
2021-09-06 13:11:54 +02:00
Bruno Windels 5953cdf0cc turns out we do write these 2021-09-06 13:10:36 +02:00
Bruno Windels 35f8c40a0c fix rebase mistake 2021-09-06 13:09:16 +02:00
Bruno Windels ae5a4f0051 start doc with additional steps for migration
once the base migration is done
2021-09-06 13:07:47 +02:00
Bruno Windels 886a60c12a set target version of TS to ES2020 2021-09-06 13:07:47 +02:00
Bruno Windels 7cb686ce8e convert EventKey to ts 2021-09-06 13:07:41 +02:00
Bruno Windels 85c8415acd convert Direction to ts 2021-09-06 13:06:20 +02:00
Bruno Windels ed082c9869
Merge pull request #475 from vector-im/snowpack-ts-storage-4
Snowpack + Typescript conversion (Part 4)
2021-09-06 13:05:08 +02:00
Bruno Windels 36da02c14e use generics here to say return type of method is same as callback 2021-09-06 13:01:32 +02:00
Bruno Windels 360b4db17a
Merge pull request #474 from vector-im/snowpack-ts-storage-3
Snowpack + Typescript conversion (Part 3)
2021-09-06 12:55:46 +02:00
Bruno Windels ad28f1f9a3 split out Entry type for TimelineEventStore 2021-09-06 12:51:28 +02:00
Bruno Windels cd98cac4e4 split out Entry type for OlmSessionStore 2021-09-06 12:46:44 +02:00
Bruno Windels 5d8e66a3f3
Merge pull request #473 from vector-im/snowpack-ts-storage-2
Snowpack + Typescript conversion (Part 2)
2021-09-06 11:06:46 +02:00
RMidhunSuresh 4f4bc8f805 Add test
Signed-off-by: RMidhunSuresh <hi@midhun.dev>
2021-09-06 14:20:33 +05:30
RMidhunSuresh a54413625e No need to pass in roomId
Signed-off-by: RMidhunSuresh <hi@midhun.dev>
2021-09-06 12:59:30 +05:30
RMidhunSuresh f841efeef4 Make check more generic
Signed-off-by: RMidhunSuresh <hi@midhun.dev>
2021-09-06 12:41:17 +05:30
Bruno Windels a0d66a71f7
Merge pull request #485 from vector-im/bwindels/make-ssss-key-settings-less-confusing
put switch button below input so it looks less like the big green lin…
2021-09-01 15:46:46 +02:00
Bruno Windels 6c3a64f020 put switch button below input so it looks less like the big green link is related to the first paragraph 2021-09-01 15:44:14 +02:00
Bruno Windels 27db00ab98
Merge pull request #484 from vector-im/bwindels/fix-483
Ignore redacting reactions on already redacted events
2021-09-01 14:23:13 +02:00
Bruno Windels 6cec583661 fix #483 2021-09-01 14:21:21 +02:00
Bruno Windels 795a57dde1
Merge pull request #481 from jryans/format-key-null
Check for null keys in settings view model
2021-09-01 10:37:01 +02:00
Danila Fedorin 5b9fd5de94 Import BaseLogger instead of explicitly defining 'any' type 2021-08-31 16:01:13 -07:00
Danila Fedorin 78fb8fdadf Make export types more precise 2021-08-31 15:59:50 -07:00
Danila Fedorin 2262e6be30 Use store name enum for saving stores 2021-08-31 15:44:03 -07:00
Danila Fedorin de3807f690 Fix IDBKeyRange type 2021-08-31 15:35:01 -07:00
Danila Fedorin 4eabb7c074 Fix newly emerging type errors in schema 2021-08-31 15:32:33 -07:00
Danila Fedorin 7f8089eaff Merge branch 'snowpack-ts-storage-3' into snowpack-ts-storage-4 2021-08-31 15:26:26 -07:00
Danila Fedorin f34ee53a12 Avoid casting to any when a more specific type is possible 2021-08-31 15:14:21 -07:00
Danila Fedorin bef02d238f Split keys out of stored data types 2021-08-31 15:12:09 -07:00
Danila Fedorin f5943ced97 Remove IDBValidKey references 2021-08-31 14:48:38 -07:00
Danila Fedorin 1fcc147da7 Add type to the IDBKeyRange field 2021-08-31 12:16:16 -07:00
Danila Fedorin 056c7d40eb Rename RoomEvent to TimelineEvent 2021-08-31 12:10:36 -07:00
Danila Fedorin 16d3ed579b Reduce IDBValidKey returns to the public API 2021-08-31 11:47:54 -07:00
Danila Fedorin ce20d40ff7 Revert the return-promise change 2021-08-31 11:31:17 -07:00
Danila Fedorin eb3f5f1ec2 Merge branch 'master' into snowpack-ts-storage-2 2021-08-31 11:13:10 -07:00
J. Ryan Stinnett 3e5cc07440 Move check to fingerprintKey 2021-08-31 17:14:14 +01:00
J. Ryan Stinnett 284ec8a74f Check for null keys in settings view model
The keys to format in the settings view model might be null. This avoid crashing
the UI by testing for this case.
2021-08-31 16:11:33 +01:00
Bruno Windels 0a13c12b1f
Update FAQ.md
mention olm issues when integrating for now
2021-08-31 12:16:59 +00:00
Bruno Windels cd900ab842
Merge pull request #456 from vector-im/snowpack-ts-storage-1
Snowpack  + Typescript conversion (Part 1)
2021-08-31 08:48:14 +02:00
Bruno Windels 995ed23b3e tell TS we're certain to have a txn 2021-08-31 08:43:39 +02:00
Bruno Windels 3d66d71c97 fix conflict 2021-08-31 08:39:12 +02:00
Bruno Windels f466266a5f bring back extra caution 2021-08-31 08:16:37 +02:00
Bruno Windels 3ded5b20d3 dedupe some code here 2021-08-31 08:16:27 +02:00
Bruno Windels 8d414970c6 release v0.2.8 2021-08-30 15:23:09 +02:00
Bruno Windels a8f89c16bd
Merge pull request #472 from vector-im/bwindels/fix-new-join-detect-one-write-phase
Fix missing new joins preventing key shares
2021-08-30 15:17:19 +02:00
Bruno Windels 7fb5412176 keep comment where it was 2021-08-30 15:12:25 +02:00
Bruno Windels 0d6ae19d99 use same code to add room to identity in migration as in device tracker 2021-08-30 15:05:57 +02:00
Bruno Windels a437332482 whitespace 2021-08-30 14:52:20 +02:00
Bruno Windels 0ca46bf2ac don't log here as we log at a lower level, and don't fail on abort 2021-08-30 14:52:04 +02:00
Bruno Windels ddb6753f8d fix refactor error 2021-08-27 20:05:53 +02:00
Bruno Windels 6a6762f036 ensure memberwriter works with undefined for state/timeline events array 2021-08-27 20:05:34 +02:00
Bruno Windels 8e6bd6a7a1 add missing room ids to identities for tracked rooms & clear outbound session 2021-08-27 19:39:24 +02:00
Bruno Windels fa555bedf0 log storage migration 2021-08-27 19:35:11 +02:00
Bruno Windels 0c05e97465 abort upgrade txn on error 2021-08-27 19:07:27 +02:00
Danila Fedorin 9252f3bede Migrate StorageFactory to TypeScript 2021-08-27 09:30:16 -07:00
Danila Fedorin 5db9d1493a Migrate export to TypeScript 2021-08-27 09:30:16 -07:00
Danila Fedorin 04e39ef9e2 Migrate quirks to TypeScript 2021-08-27 09:30:16 -07:00
Danila Fedorin 34b173a057 Migrate schema to TypeScript 2021-08-27 09:30:16 -07:00
Danila Fedorin eae820f91b Migrate Storage to TypeScript 2021-08-27 09:30:15 -07:00
Danila Fedorin 5f3c9cda97 Migrate Transaction to TypeScript 2021-08-27 09:30:15 -07:00
Danila Fedorin 100aee9dcc Migrate InboundGroupSessionStore to TypeScript 2021-08-27 09:30:10 -07:00
Danila Fedorin 914abda7c9 Migrate OlmSessionStore to TypeScript 2021-08-27 09:30:10 -07:00
Danila Fedorin 3cd0d1f423 Migrate DeviceIdentityStore to TypeScript 2021-08-27 09:30:10 -07:00
Danila Fedorin 8d44df83c4 Migrate UserIdentityStore to TypeScript 2021-08-27 09:30:10 -07:00
Danila Fedorin b46ae152d6 Migrate PendingEventStore.js to TypeScript 2021-08-27 09:30:10 -07:00
Danila Fedorin fad6d63c14 Migrate TimelineFragmentStore.js to TypeScript 2021-08-27 09:30:10 -07:00
Danila Fedorin 742ab28099 Migrate AccountDataStore.js to TypeScript. 2021-08-27 09:30:10 -07:00
Danila Fedorin 77f75fd968 Migrate OperationStore to TypeScript 2021-08-27 09:30:10 -07:00
Danila Fedorin 8c966627bc Migrate GroupSessionDecryptionStore to TypeScript 2021-08-27 09:30:10 -07:00
Danila Fedorin 33d94b9497 Migrate OutboundGroupSessionStore to TypeScript 2021-08-27 09:30:10 -07:00
Danila Fedorin 5d4454734a Migrate RoomStateStore.js to TypeScript 2021-08-27 09:30:10 -07:00
Danila Fedorin af70269169 Merge branch 'snowpack-ts-storage-1' into snowpack-ts-storage-2 2021-08-27 09:29:53 -07:00
Danila Fedorin 4c4687a05f Avoid unsafe (and error-prone) cast 2021-08-27 09:29:02 -07:00
Bruno Windels a61f052fe8 fix lint 2021-08-27 17:23:32 +02:00
Bruno Windels 826de7e9cb Write all members of a sync in one go
so state member events written but not causing a memberChange.hasJoined
don't prevent timeline member events for the same user from doing so
2021-08-27 17:11:29 +02:00
Danila Fedorin b081aa171c Merge branch 'snowpack-ts-storage-1' into snowpack-ts-storage-2 2021-08-26 17:08:00 -07:00
Danila Fedorin 904a2cbe74 Merge branch 'master' into snowpack-ts-storage-1 2021-08-26 17:07:25 -07:00
Danila Fedorin a95704528f Merge branch 'snowpack-ts-storage-1' into snowpack-ts-storage-2 2021-08-26 17:01:23 -07:00
Danila Fedorin b7d232d56d Remove unnecessary cast and restrict constructor parameter type 2021-08-26 16:56:03 -07:00
Bruno Windels d0c1ddb51b add failing test 2021-08-26 15:18:31 +02:00
Danila Fedorin 6d6821319e Merge branch 'snowpack-ts-storage-1' into snowpack-ts-storage-2 2021-08-24 11:19:36 -07:00
Danila Fedorin 1707df71df Try to reduce repitition in CursorIterator 2021-08-24 11:11:30 -07:00
Bruno Windels 25d0cbe9a2
Merge pull request #468 from vector-im/bwindels/fixlint
Cleanup: fix lint and remove dead code
2021-08-24 15:35:40 +02:00
Bruno Windels cb9606a87b remove dead code for incomplete memory store 2021-08-24 15:33:41 +02:00
Bruno Windels e105bc4237 fix lint warnings 2021-08-24 15:31:18 +02:00
Danila Fedorin e610087a79 Merge branch 'snowpack-ts-storage-1' into snowpack-ts-storage-2 2021-08-20 12:51:23 -07:00
Danila Fedorin a2ff02e6c0 Try using an enum for store names. 2021-08-20 12:33:06 -07:00
Danila Fedorin 69953e5277 Migrate TimelineRelationStore.js to TypeScript 2021-08-20 11:23:27 -07:00
Danila Fedorin e3b1d034f0 Migrate TimelineEventStore.js to TypeScript 2021-08-20 11:23:21 -07:00
Danila Fedorin 7de704ef86 Migrate InviteStore.js to TypeScript. 2021-08-20 11:23:14 -07:00
Danila Fedorin 7c56ac7746 Migrate RoomMemberStore.js to TypeScript 2021-08-20 11:23:08 -07:00
Danila Fedorin 38a38e8287 Migrate common.ts 2021-08-20 11:23:01 -07:00
Danila Fedorin e284224cc8 Migrate RoomSummaryStore.js to TypeScript. 2021-08-20 11:22:53 -07:00
Danila Fedorin 144e391c82 Migrate SessionStore.js to TypeScript. 2021-08-20 11:22:45 -07:00
Danila Fedorin be4ad66b12 Merge branch 'snowpack-ts-storage-1' into snowpack-ts-storage-2-fix 2021-08-20 11:21:03 -07:00
Danila Fedorin 0b8acb51a4 Switch errors to using nulls 2021-08-20 10:41:15 -07:00
Danila Fedorin 50b7a8a3fd Add a comment explaining CursorIterator 2021-08-20 10:34:06 -07:00
Danila Fedorin 94ff76711c Use 'in' to be more idiomatic 2021-08-20 10:04:22 -07:00
Danila Fedorin 19bababa68 Use method syntax in QueryTarget. 2021-08-19 17:40:10 -07:00
Danila Fedorin 704a8d99c7 Add missing return types to QueryTarget 2021-08-19 17:39:53 -07:00
RMidhunSuresh 2ceaebbd1f Don't find roomId in getter
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-08-18 18:17:37 +05:30
RMidhunSuresh 7e177f6953 Modify open-room action to fix bug
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-08-18 17:43:40 +05:30
Danila Fedorin db66570d7a Migrate Store.js to TypeScript 2021-08-17 16:33:07 -07:00
Danila Fedorin c4e8ed8851 Migrate QueryTarget.js to TypeScript 2021-08-17 16:30:05 -07:00
Danila Fedorin 28ee87cd2f Migrate error.js to TypeScript 2021-08-17 16:30:05 -07:00
Danila Fedorin aa9839ee40 Seemingly fix a bug in utils.ts 2021-08-17 16:30:05 -07:00
Danila Fedorin cd9fe360a4 Start migrating utils.js to TypeScript 2021-08-17 16:30:05 -07:00
Danila Fedorin 5579c018d1 Migrate common.js to TypeScript
Add initial stab at annotating common

Add missing return types and semicolons
2021-08-17 16:30:05 -07:00
Danila Fedorin 8ec8de67b8 Merge branch 'snowpack-mvp' into snowpack-ts-storage-1 2021-08-17 16:29:47 -07:00
RMidhunSuresh 9e9099f5d0 Restructure and add syntax
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-06-08 23:05:33 +05:30
RMidhunSuresh 4d79279f42 Add some content
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
2021-06-08 21:13:52 +05:30
461 changed files with 20393 additions and 12129 deletions

View File

@ -1 +0,0 @@
src/matrix/storage/memory

View File

@ -13,5 +13,13 @@ module.exports = {
"no-empty": "off",
"no-prototype-builtins": "off",
"no-unused-vars": "warn"
},
"globals": {
"DEFINE_VERSION": "readonly",
"DEFINE_GLOBAL_HASH": "readonly",
// only available in sw.js
"DEFINE_UNHASHED_PRECACHED_ASSETS": "readonly",
"DEFINE_HASHED_PRECACHED_ASSETS": "readonly",
"DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS": "readonly"
}
};

44
.github/workflows/docker-publish.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name: Container Image
on:
push:
branches: [ master ]
tags: [ 'v*' ]
pull_request:
branches: [ master ]
env:
IMAGE_NAME: ${{ github.repository }}
REGISTRY: ghcr.io
jobs:
push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

5
.gitignore vendored
View File

@ -1,5 +1,6 @@
*.sublime-project
*.sublime-workspace
.DS_Store
node_modules
fetchlogs
sessionexports
@ -7,4 +8,6 @@ bundle.js
target
lib
*.tar.gz
.eslintcache
.eslintcache
.tmp
tmp/

View File

@ -19,6 +19,7 @@ module.exports = {
],
rules: {
"@typescript-eslint/no-floating-promises": 2,
"@typescript-eslint/no-misused-promises": 2
"@typescript-eslint/no-misused-promises": 2,
"semi": ["error", "always"]
}
};

18
.woodpecker.yml Normal file
View File

@ -0,0 +1,18 @@
pipeline:
buildfrontend:
image: node:16
commands:
- yarn install --prefer-offline --frozen-lockfile
- yarn test
- yarn run lint-ci
- yarn run tsc
- yarn build
deploy:
image: python
when:
event: push
branch: master
commands:
- make ci-deploy
secrets: [ GITEA_WRITE_DEPLOY_KEY, LIBREPAGES_DEPLOY_SECRET ]

150
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,150 @@
Contributing code to hydrogen-web
==================================
Everyone is welcome to contribute code to hydrogen-web, provided that they are
willing to license their contributions under the same license as the project
itself. We follow a simple 'inbound=outbound' model for contributions: the act
of submitting an 'inbound' contribution means that the contributor agrees to
license the code under the same terms as the project's overall 'outbound'
license - in this case, Apache Software License v2 (see
[LICENSE](LICENSE)).
How to contribute
-----------------
The preferred and easiest way to contribute changes to the project is to fork
it on github, and then create a pull request to ask us to pull your changes
into our repo (https://help.github.com/articles/using-pull-requests/)
We use GitHub's pull request workflow to review the contribution, and either
ask you to make any refinements needed or merge it and make them ourselves.
Things that should go into your PR description:
* References to any bugs fixed by the change (in GitHub's `Fixes` notation)
* Describe the why and what is changing in the PR description so it's easy for
onlookers and reviewers to onboard and context switch.
* If your PR makes visual changes, include both **before** and **after** screenshots
to easily compare and discuss what's changing.
* Include a step-by-step testing strategy so that a reviewer can check out the
code locally and easily get to the point of testing your change.
* Add comments to the diff for the reviewer that might help them to understand
why the change is necessary or how they might better understand and review it.
We use continuous integration, and all pull requests get automatically tested:
if your change breaks the build, then the PR will show that there are failed
checks, so please check back after a few minutes.
Tests
-----
If your PR is a feature then we require that the PR also includes tests.
These need to test that your feature works as expected and ideally test edge cases too.
Tests are written as unit tests by exporting a `tests` function from the file to be tested.
The function returns an object where the key is the test label, and the value is a
function that accepts an [assert](https://nodejs.org/api/assert.html) object, and return a Promise or nothing.
Note that there is currently a limitation that files that are not indirectly included from `src/platform/web/main.js` won't be found by the runner.
You can run the tests by running `yarn test`.
This uses the [impunity](https://github.com/bwindels/impunity) runner.
We don't require tests for bug fixes.
In the future we may formalise this more.
Code style
----------
The js-sdk aims to target TypeScript/ES6. All new files should be written in
TypeScript and existing files should use ES6 principles where possible.
Please disable any automatic formatting tools you may have active.
If present, you'll be asked to undo any unrelated whitespace changes during code review.
Members should not be exported as a default export in general.
In general, avoid using `export default`.
The remaining code-style for hydrogen is [in the process of being documented](codestyle.md), but
contributors are encouraged to read the
[code style document for matrix-react-sdk](https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md)
and follow the principles set out there.
Please ensure your changes match the cosmetic style of the existing project,
and ***never*** mix cosmetic and functional changes in the same commit, as it
makes it horribly hard to review otherwise.
Attribution
-----------
If you change or create a file, feel free to add yourself to the copyright holders
in the license header of that file.
Sign off
--------
In order to have a concrete record that your contribution is intentional
and you agree to license it under the same terms as the project's license, we've
adopted the same lightweight approach that the Linux Kernel
(https://www.kernel.org/doc/Documentation/SubmittingPatches), Docker
(https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other
projects use: the DCO (Developer Certificate of Origin:
http://developercertificate.org/). This is a simple declaration that you wrote
the contribution or otherwise have the right to contribute it to Matrix:
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
If you agree to this for your contribution, then all that's needed is to
include the line in your commit or pull request comment:
```
Signed-off-by: Your Name <your@email.example.org>
```
We accept contributions under a legally identifiable name, such as your name on
government documentation or common-law names (names claimed by legitimate usage
or repute). Unfortunately, we cannot accept anonymous contributions at this
time.
Git allows you to add this signoff automatically when using the `-s` flag to
`git commit`, which uses the name and email set in your `user.name` and
`user.email` git configs.
If you forgot to sign off your commits before making your pull request and are
on Git 2.17+ you can mass signoff using rebase:
```
git rebase --signoff origin/develop
```

View File

@ -1,5 +1,5 @@
FROM docker.io/node:alpine as builder
RUN apk add --no-cache git
RUN apk add --no-cache git python3 build-base
COPY . /app
WORKDIR /app
RUN yarn install \

View File

@ -1,5 +1,5 @@
FROM docker.io/node:alpine
RUN apk add --no-cache git
RUN apk add --no-cache git python3 build-base
COPY . /code
WORKDIR /code
RUN yarn install

14
Makefile Normal file
View File

@ -0,0 +1,14 @@
ci-deploy: ## Deploy from CI/CD. Only call from within CI
@if [ "${CI}" != "woodpecker" ]; \
then echo "Only call from within CI. Will re-write your local Git configuration. To override, set export CI=woodpecker"; \
exit 1; \
fi
git config --global user.email "${CI_COMMIT_AUTHOR_EMAIL}"
git config --global user.name "${CI_COMMIT_AUTHOR}"
./scripts/ci.sh --commit-files librepages target "${CI_COMMIT_AUTHOR} <${CI_COMMIT_AUTHOR_EMAIL}>"
./scripts/ci.sh --init "$$GITEA_WRITE_DEPLOY_KEY"
./scripts/ci.sh --deploy ${LIBREPAGES_DEPLOY_SECRET} librepages
./scripts/ci.sh --clean
help: ## Prints help for targets with comments
@cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

View File

@ -1,3 +1,5 @@
[![status-badge](https://ci.batsense.net/api/badges/mystiq/hydrogen-web/status.svg)](https://ci.batsense.net/mystiq/hydrogen-web)
# Hydrogen
A minimal [Matrix](https://matrix.org/) chat client, focused on performance, offline functionality, and broad browser support. This is work in progress and not yet ready for primetime. Bug reports are welcome, but please don't file any feature requests or other missing things to be on par with Element Web.
@ -10,13 +12,34 @@ Hydrogen's goals are:
- It is a standalone webapp, but can also be easily embedded into an existing website/webapp to add chat capabilities.
- Loading (unused) parts of the application after initial page load should be supported
For embedded usage, see the [SDK instructions](doc/SDK.md).
If you find this interesting, come and discuss on [`#hydrogen:matrix.org`](https://matrix.to/#/#hydrogen:matrix.org).
# How to use
Hydrogen is deployed to [hydrogen.element.io](https://hydrogen.element.io). You can run it locally `yarn install` (only the first time) and `yarn start` in the terminal, and point your browser to `http://localhost:3000`. If you prefer, you can also [use docker](doc/docker.md).
Hydrogen is deployed to [hydrogen.element.io](https://hydrogen.element.io). You can also deploy Hydrogen on your own web server:
Hydrogen uses symbolic links in the codebase, so if you are on Windows, have a look at [making git & symlinks work](https://github.com/git-for-windows/git/wiki/Symbolic-Links) there.
1. Download the [latest release package](https://github.com/vector-im/hydrogen-web/releases).
1. Extract the package to the public directory of your web server.
1. If this is your first deploy:
1. copy `config.sample.json` to `config.json` and if needed, make any modifications (unless you've set up your own [sygnal](https://github.com/matrix-org/sygnal) instance, you don't need to change anything in the `push` section).
1. Disable caching entirely on the server for:
- `index.html`
- `sw.js`
- `config.json`
- All theme manifests referenced in the `themeManifests` of `config.json`, these files are typically called `theme-{name}.json`.
These resources will still be cached client-side by the service worker. Because of this; you'll still need to refresh the app twice before config.json changes are applied.
## Set up a dev environment
You can run Hydrogen locally by the following commands in the terminal:
- `yarn install` (only the first time)
- `yarn start` in the terminal
Now point your browser to `http://localhost:3000`. If you prefer, you can also [use docker](doc/docker.md).
# FAQ

View File

@ -8,3 +8,5 @@
otherwise it becomes hard to remember what was a default/named export
- should we return promises from storage mutation calls? probably not, as we don't await them anywhere. only read calls should return promises?
- we don't anymore
- don't use these features, as they are not widely enough supported.
- [lookbehind in regular expressions](https://caniuse.com/js-regexp-lookbehind)

View File

@ -28,12 +28,8 @@ You can only verify by comparing keys manually currently. In Element, go to your
## I want to host my own Hydrogen, how do I do that?
There are no published builds at this point. You need to checkout the version you want to build, or master if you want to run bleeding edge, and run `yarn install` and then `yarn build` in a console (and install nodejs > 14 and yarn if you haven't yet). Now you should find all the files needed to host Hydrogen in the `target/` folder, just copy them all over to your server. As always, don't host your client on the same [origin](https://web.dev/same-origin-policy/#what's-considered-same-origin) as your homeserver.
Published builds can be found at https://github.com/vector-im/hydrogen-web/releases. For building your own, you need to checkout the version you want to build, or master if you want to run bleeding edge, and run `yarn install` and then `yarn build` in a console (and install nodejs >= 15 and yarn if you haven't yet). Now you should find all the files needed to host Hydrogen in the `target/` folder, just copy them all over to your server. As always, don't host your client on the same [origin](https://web.dev/same-origin-policy/#what's-considered-same-origin) as your homeserver.
## I want to embed Hydrogen in my website, how should I do that?
There are no npm modules yet published for Hydrogen. The easiest is probably to setup your website project, do yarn/npm init if you haven't yet, then add the hydrogen repo as a git http dependency, and import the files/classes you want to use from Hydrogen.
For example, for a single room chat, you could create an instance of `Platform`, you create a new `SessionContainer` with it, call `startWithLogin` on it, observe `sessionContainer.loadStatus` to know when initial sync is done, then do `sessionContainer.session.rooms.get('roomid')` and you create a `RoomViewModel` with it and pass that to a `RoomView`. Then you call `document.appendChild(roomView.mount())` and you should see a syncing room.
Feel free to ask for pointers in #hydrogen:matrix.org as the documentation is still lacking considerably. Note that at this early, pre 1.0 stage of the project, there is no promise of API stability yet.
Hydrogen aims to be usable as an SDK, and while it is still early days, you can find some documentation how to do that in [SDK.md](SDK.md).

11
doc/IMPORT-ISSUES.md Normal file
View File

@ -0,0 +1,11 @@
## How to import common-js dependency using ES6 syntax
---
Until [#6632](https://github.com/vitejs/vite/issues/6632) is fixed, such imports should be done as follows:
```ts
import * as pkg from "off-color";
// @ts-ignore
const offColor = pkg.offColor ?? pkg.default.offColor;
```
This way build, dev server and unit tests should all work.

116
doc/SDK.md Normal file
View File

@ -0,0 +1,116 @@
# Hydrogen View SDK
The Hydrogen view SDK allows developers to integrate parts of the Hydrogen application into the UI of their own application. Hydrogen is written with the MVVM pattern, so to construct a view, you'd first construct a view model, which you then pass into the view. For most view models, you will first need a running client.
## Example
The Hydrogen SDK requires some assets to be shipped along with your app for things like downloading attachments, and end-to-end encryption. A convenient way to make this happen is provided by the SDK (importing `hydrogen-view-sdk/paths/vite`) but depends on your build system. Currently, only [vite](https://vitejs.dev/) is supported, so that's what we'll be using in the example below.
You can create a vite project using the following commands:
```sh
# you can pick "vanilla-ts" here for project type if you're not using react or vue
yarn create vite
cd <your-project-name>
yarn
yarn add hydrogen-view-sdk
```
You should see a `index.html` in the project root directory, containing an element with `id="app"`. Add the attribute `class="hydrogen"` to this element, as the CSS we'll include from the SDK assumes for now that the app is rendered in an element with this classname.
If you go into the `src` directory, you should see a `main.ts` file. If you put this code in there, you should see a basic timeline after login and initial sync have finished (might take a while before you see anything on the screen actually).
You'll need to provide the username and password of a user that is already in the [#element-dev:matrix.org](https://matrix.to/#/#element-dev:matrix.org) room (or change the room id).
```ts
import {
Platform,
Client,
LoadStatus,
createNavigation,
createRouter,
RoomViewModel,
TimelineView,
viewClassForTile
} from "hydrogen-view-sdk";
import downloadSandboxPath from 'hydrogen-view-sdk/download-sandbox.html?url';
import workerPath from 'hydrogen-view-sdk/main.js?url';
import olmWasmPath from '@matrix-org/olm/olm.wasm?url';
import olmJsPath from '@matrix-org/olm/olm.js?url';
import olmLegacyJsPath from '@matrix-org/olm/olm_legacy.js?url';
const assetPaths = {
downloadSandbox: downloadSandboxPath,
worker: workerPath,
olm: {
wasm: olmWasmPath,
legacyBundle: olmLegacyJsPath,
wasmBundle: olmJsPath
}
};
import "hydrogen-view-sdk/assets/theme-element-light.css";
// OR import "hydrogen-view-sdk/assets/theme-element-dark.css";
async function main() {
const app = document.querySelector<HTMLDivElement>('#app')!
const config = {};
const platform = new Platform({container: app, assetPaths, config, options: { development: import.meta.env.DEV }});
const navigation = createNavigation();
platform.setNavigation(navigation);
const urlRouter = createRouter({
navigation: navigation,
history: platform.history
});
urlRouter.attach();
const client = new Client(platform);
const loginOptions = await client.queryLogin("matrix.org").result;
client.startWithLogin(loginOptions.password("username", "password"));
await client.loadStatus.waitFor((status: string) => {
return status === LoadStatus.Ready ||
status === LoadStatus.Error ||
status === LoadStatus.LoginFailed;
}).promise;
if (client.loginFailure) {
alert("login failed: " + client.loginFailure);
} else if (client.loadError) {
alert("load failed: " + client.loadError.message);
} else {
const {session} = client;
// looks for room corresponding to #element-dev:matrix.org, assuming it is already joined
const room = session.rooms.get("!bEWtlqtDwCLFIAKAcv:matrix.org");
const vm = new RoomViewModel({
room,
ownUserId: session.userId,
platform,
urlCreator: urlRouter,
navigation,
});
await vm.load();
const view = new TimelineView(vm.timelineViewModel, viewClassForTile);
app.appendChild(view.mount());
}
}
main();
```
## Typescript support
Typescript support is not yet available while we're converting the Hydrogen codebase to Typescript.
In your `src` directory, you'll need to add a `.d.ts` (can be called anything, e.g. `deps.d.ts`)
containing this snippet to make Typescript not complain that `hydrogen-view-sdk` doesn't have types:
```ts
declare module "hydrogen-view-sdk";
```
## API Stability
This library follows semantic versioning; there is no API stability promised as long as the major version is still 0. Once 1.0.0 is released, breaking changes will be released with a change in major versioning.
## Third-party licenses
This package bundles the bs58 package ([license](https://github.com/cryptocoinjs/bs58/blob/master/LICENSE)), and the Inter font ([license](https://github.com/rsms/inter/blob/master/LICENSE.txt)).

204
doc/THEMING.md Normal file
View File

@ -0,0 +1,204 @@
# Theming Documentation
## Basic Architecture
A **theme collection** in Hydrogen is represented by a `manifest.json` file and a `theme.css` file.
The manifest specifies variants (eg: dark,light ...) each of which is a **theme** and maps to a single css file in the build output.
Each such theme is produced by changing the values of variables in the base `theme.css` file with those specified in the variant section of the manifest:
![](images/theming-architecture.png)
More in depth explanations can be found in later sections.
## Structure of `manifest.json`
[See theme.ts](../src/platform/types/theme.ts)
## Variables
CSS variables specific to a particular variant are specified in the `variants` section of the manifest:
```json=
"variants": {
"light": {
...
"variables": {
"background-color-primary": "#fff",
"text-color": "#2E2F32",
}
},
"dark": {
...
"variables": {
"background-color-primary": "#21262b",
"text-color": "#fff",
}
}
}
```
These variables will appear in the css file (theme.css):
```css=
body {
background-color: var(--background-color-primary);
color: var(--text-color);
}
```
During the build process, this would result in the creation of two css files (one for each variant) where the variables are substitued with the corresponding values specified in the manifest:
*element-light.css*:
```css=
body {
background-color: #fff;
color: #2E2F32;
}
```
*element-dark.css*:
```css=
body {
background-color: #21262b;
color: #fff;
}
```
## Derived Variables
In addition to simple substitution of variables in the stylesheet, it is also possible to instruct the build system to first produce a new value from the base variable value before the substitution.
Such derived variables have the form `base_css_variable--operation-arg` and can be read as:
apply `operation` to `base_css_variable` with argument `arg`.
Continuing with the previous example, it possible to specify:
```css=
.left-panel {
/* background color should be 20% more darker
than background-color-primary */
background-color: var(--background-color-primary--darker-20);
}
```
Currently supported operations are:
| Operation | Argument | Operates On |
| -------- | -------- | -------- |
| darker | percentage | color |
| lighter | percentage | color |
## Aliases
It is possible give aliases to variables in the `theme.css` file:
```css=
:root {
font-size: 10px;
/* Theme aliases */
--icon-color: var(--background-color-secondary--darker-40);
}
```
It is possible to further derive from these aliased variables:
```css=
div {
background: var(--icon-color--darker-20);
--my-alias: var(--icon-color--darker-20);
/* Derive from aliased variable */
color: var(--my-alias--lighter-15);
}
```
## Colorizing svgs
Along with a change in color-scheme, it may be necessary to change the colors in the svg icons and images.
This can be done by supplying the preferred colors with query parameters:
`my-awesome-logo.svg?primary=base-variable-1&secondary=base-variable-2`
This instructs the build system to colorize the svg with the given primary and secondary colors.
`base-variable-1` and `base-variable-2` are the css-variables specified in the `variables` section of the manifest.
For colorizing svgs, the source svg must use `#ff00ff` as the primary color and `#00ffff` as the secondary color:
| ![](images/svg-icon-example.png) | ![](images/coloring-process.png) |
| :--: |:--: |
| **original source image** | **transformation process** |
## Creating your own theme variant in Hydrogen
If you're looking to change the color-scheme of the existing Element theme, you only need to add your own variant to the existing `manifest.json`.
The steps are fairly simple:
1. Copy over an existing variant to the variants section of the manifest.
2. Change `dark`, `default` and `name` fields.
3. Give new values to each variable in the `variables` section.
4. Build hydrogen.
## Creating your own theme collection in Hydrogen
If a theme variant does not solve your needs, you can create a new theme collection with a different base `theme.css` file.
1. Create a directory for your new theme-collection under `src/platform/web/ui/css/themes/`.
2. Create `manifest.json` and `theme.css` files within the newly created directory.
3. Populate `manifest.json` with the base css variables you wish to use.
4. Write styles in your `theme.css` file using the base variables, derived variables and colorized svg icons.
5. Tell the build system where to find this theme-collection by providing the location of this directory to the `themeBuilder` plugin in `vite.config.js`:
```json=
...
themeBuilder({
themeConfig: {
themes: {
element: "./src/platform/web/ui/css/themes/element",
awesome: "path/to/theme-directory"
},
default: "element",
},
compiledVariables,
}),
...
```
6. Build Hydrogen.
## Changing the default theme
To change the default theme used in Hydrogen, modify the `defaultTheme` field in `config.json` file (which can be found in the build output):
```json=
"defaultTheme": {
"light": theme-id,
"dark": theme-id
}
```
Here *theme-id* is of the form `theme-variant` where `theme` is the key used when specifying the manifest location of the theme collection in `vite.config.js` and `variant` is the key used in variants section of the manifest.
Some examples of theme-ids are `element-dark` and `element-light`.
To find the theme-id of some theme, you can look at the built-asset section of the manifest in the build output.
This default theme will render as "Default" option in the theme-chooser dropdown. If the device preference is for dark theme, the dark default is selected and vice versa.
**You'll need to reload twice so that Hydrogen picks up the config changes!**
# Derived Theme(Collection)
This allows users to theme Hydrogen without the need for rebuilding. Derived theme collections can be thought of as extensions (derivations) of some existing build time theme.
## Creating a derived theme:
Here's how you create a new derived theme:
1. You create a new theme manifest file (eg: theme-awesome.json) and mention which build time theme you're basing your new theme on using the `extends` field. The base css file of the mentioned theme is used for your new theme.
2. You configure the theme manifest as usual by populating the `variants` field with your desired colors.
3. You add your new theme manifest to the list of themes in `config.json`.
Refresh Hydrogen twice (once to refresh cache, and once to load) and the new theme should show up in the theme chooser.
## How does it work?
For every theme collection in hydrogen, the build process emits a runtime css file which like the built theme css file contains variables in the css code. But unlike the theme css file, the runtime css file lacks the definition for these variables:
CSS for the built theme:
```css
:root {
--background-color-primary: #f2f20f;
}
body {
background-color: var(--background-color-primary);
}
```
and the corresponding runtime theme:
```css
/* Notice the lack of definiton for --background-color-primary here! */
body {
background-color: var(--background-color-primary);
}
```
When hydrogen loads a derived theme, it takes the runtime css file of the extended theme and dynamically adds the variable definition based on the values specified in the manifest. Icons are also colored dynamically and injected as variables using Data URIs.

38
doc/TS-MIGRATION.md Normal file
View File

@ -0,0 +1,38 @@
# Typescript style guide
## Use `type` rather than `interface` for named parameters and POJO return values.
`type` and `interface` can be used somewhat interchangeably, but let's use `type` to describe data and `interface` to describe (polymorphic) behaviour.
Good examples of data are option objects to have named parameters, and POJO (plain old javascript objects) without any methods, just fields.
Also see [this playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBACghgJwgO2AeTMAlge2QZygF4oBvAKCiqmTgFsIAuKfYBLZAcwG5LqATCABs4IAPzNkAVzoAjCAl4BfcuVCQoAYQAWWIfwzY8hEvCSpDuAlABkZPlQDGOITgTNW7LstWOR+QjMUYHtqKGcCNilHYDcAChxMK3xmIIsk4wBKewcoFRVyPzgArV19KAgAD2AUfkDEYNDqCM9o2IQEjIJmHT0DLvxsijCw-ClIDsSjAkzeEebjEIYAuE5oEgADABJSKeSAOloGJSgsQh29433nVwQlDbnqfKA)
## Use `type foo = { [key: string]: any }` for types that you intend to fill in later.
For instance, if you have a method such as:
```js
function load(options) {
// ...
}
```
and you intend to type options at some later point, do:
```ts
type Options = { [key: string]: any}
```
This makes it much easier to add the necessary type information at a later time.
## Use `object` or `Record<string, any>` to describe a type that accepts any javascript object.
Sometimes a function or method may genuinely need to accept any object; eg:
```js
function encodeBody(body) {
// ...
}
```
In this scenario:
- Use `object` if you know that you will not access any property
- Use `Record<string, any>` if you need to access some property
Both usages prevent the type from accepting primitives (eg: string, boolean...).
If using `Record`, ensure that you have guards to check that the properties really do exist.

3
doc/UI/index.md Normal file
View File

@ -0,0 +1,3 @@
# Index for UI code
1. [Rendering DOM elements](./render-dom-elements.md)

View File

@ -0,0 +1,47 @@
tldr; Use `tag` from `ui/general/html.js` to quickly create DOM elements.
## Syntax
---
The general syntax is as follows:
```js
tag.tag_name({attribute1: value, attribute2: value, ...}, [child_elements]);
```
**tag_name** can be any one of the following:
```
br, a, ol, ul, li, div, h1, h2, h3, h4, h5, h6,
p, strong, em, span, img, section, main, article, aside,
pre, button, time, input, textarea, label, form, progress, output, video
```
<br />
eg:
Here is an example HTML segment followed with the code to create it in Hydrogen.
```html
<section class="main-section">
<h1>Demo</h1>
<button class="btn_cool">Click me</button>
</section>
```
```js
tag.section({className: "main-section"},[
tag.h1("Demo"),
tag.button({className:"btn_cool"}, "Click me")
]);
```
<br />
**Note:** In views based on `TemplateView`, you will see `t` used instead of `tag`.
`t` is is `TemplateBuilder` object passed to the render function in `TemplateView`.
Although syntactically similar, they are not functionally equivalent.
Primarily `t` **supports** bindings and event handlers while `tag` **does not**.
```js
// The onClick here wont work!!
tag.button({className:"awesome-btn", onClick: () => this.foo()});
render(t, vm){
// The onClick works here.
t.button({className:"awesome-btn", onClick: () => this.foo()});
}
```

206
doc/UI/ui.md Normal file
View File

@ -0,0 +1,206 @@
## IView components
The [interface](https://github.com/vector-im/hydrogen-web/blob/master/src/platform/web/ui/general/types.ts) adopted by view components is agnostic of how they are rendered to the DOM. This has several benefits:
- it allows Hydrogen to not ship a [heavy view framework](https://bundlephobia.com/package/react-dom@18.2.0) that may or may not be used by its SDK users, and also keep bundle size of the app down.
- Given the interface is quite simple, is should be easy to integrate this interface into the render lifecycle of other frameworks.
- The main implementations used in Hydrogen are [`ListView`](https://github.com/vector-im/hydrogen-web/blob/master/src/platform/web/ui/general/ListView.ts) (rendering [`ObservableList`](https://github.com/vector-im/hydrogen-web/blob/master/src/observable/list/BaseObservableList.ts)s) and [`TemplateView`](https://github.com/vector-im/hydrogen-web/blob/master/src/platform/web/ui/general/TemplateView.ts) (templating and one-way databinding), each only a few 100 lines of code and tailored towards their specific use-case. They work straight with the DOM API and have no other dependencies.
- a common inteface allows us to mix and match between these different implementations (and gradually shift if need be in the future) with the code.
## Templates
### Template language
Templates use a mini-DSL language in pure javascript to express declarative templates. This is basically a very thin wrapper around `document.createElement`, `document.createTextNode`, `node.setAttribute` and `node.appendChild` to quickly create DOM trees. The general syntax is as follows:
```js
t.tag_name({attribute1: value, attribute2: value, ...}, [child_elements]);
t.tag_name(child_element);
t.tag_name([child_elements]);
```
**tag_name** can be [most HTML or SVG tags](https://github.com/vector-im/hydrogen-web/blob/master/src/platform/web/ui/general/html.ts#L102-L110).
eg:
Here is an example HTML segment followed with the code to create it in Hydrogen.
```html
<section class="main-section">
<h1>Demo</h1>
<button class="btn_cool">Click me</button>
</section>
```
```js
t.section({className: "main-section"},[
t.h1("Demo"),
t.button({className:"btn_cool"}, "Click me")
]);
```
All these functions return DOM element nodes, e.g. the result of `document.createElement`.
### TemplateView
`TemplateView` builds on top of templating by adopting the IView component model and adding event handling attributes, sub views and one-way databinding.
In views based on `TemplateView`, you will see a render method with a `t` argument.
`t` is `TemplateBuilder` object passed to the render function in `TemplateView`. It also takes a data object to render and bind to, often called `vm`, short for view model from the MVVM pattern Hydrogen uses.
You either subclass `TemplateView` and override the `render` method:
```js
class MyView extends TemplateView {
render(t, vm) {
return t.div(...);
}
}
```
Or you pass a render function to `InlineTemplateView`:
```js
new InlineTemplateView(vm, (t, vm) => {
return t.div(...);
});
```
**Note:** the render function is only called once to build the initial DOM tree and setup bindings, etc ... Any subsequent updates to the DOM of a component happens through bindings.
#### Event handlers
Any attribute starting with `on` and having a function as a value will be attached as an event listener on the given node. The event handler will be removed during unmounting.
```js
t.button({onClick: evt => {
vm.doSomething(evt.target.value);
}}, "Click me");
```
#### Subviews
`t.view(instance)` will mount the sub view (can be any IView) and return its root node so it can be attached in the DOM tree.
All subviews will be unmounted when the parent view gets unmounted.
```js
t.div({className: "Container"}, t.view(new ChildView(vm.childViewModel)));
```
#### One-way data-binding
A binding couples a part of the DOM to a value on the view model. The view model emits an update when any of its properties change, to which the view can subscribe. When an update is received by the view, it will reevaluate all the bindings, and update the DOM accordingly.
A binding can appear in many places where a static value can usually be used in the template tree.
To create a binding, you pass a function that maps the view value to a static value.
##### Text binding
```js
t.p(["I've got ", vm => vm.counter, " beans"])
```
##### Attribute binding
```js
t.button({disabled: vm => vm.isBusy}, "Submit");
```
##### Class-name binding
```js
t.div({className: {
button: true,
active: vm => vm.isActive
}})
```
##### Subview binding
So far, all the bindings can only change node values within our tree, but don't change the structure of the DOM. A sub view binding allows you to conditionally add a subview based on the result of a binding function.
All sub view bindings return a DOM (element or comment) node and can be directly added to the DOM tree by including them in your template.
###### map
`t.mapView` allows you to choose a view based on the result of the binding function:
```js
t.mapView(vm => vm.count, count => {
return count > 5 ? new LargeView(count) : new SmallView(count);
});
```
Every time the first or binding function returns a different value, the second function is run to create a new view to replace the previous view.
You can also return `null` or `undefined` from the second function to indicate a view should not be rendered. In this case a comment node will be used as a placeholder.
There is also a `t.map` which will create a new template view (with the same value) and you directly provide a render function for it:
```js
t.map(vm => vm.shape, (shape, t, vm) => {
switch (shape) {
case "rect": return t.rect();
case "circle": return t.circle();
}
})
```
###### if
`t.ifView` will render the subview if the binding returns a truthy value:
```js
t.ifView(vm => vm.isActive, vm => new View(vm.someValue));
```
You equally have `t.if`, which creates a `TemplateView` and passes you the `TemplateBuilder`:
```js
t.if(vm => vm.isActive, (t, vm) => t.div("active!"));
```
##### Side-effects
Sometimes you want to imperatively modify your DOM tree based on the value of a binding.
`mapSideEffect` makes this easy to do:
```js
let node = t.div();
t.mapSideEffect(vm => vm.color, (color, oldColor) => node.style.background = color);
return node;
```
**Note:** you shouldn't add any bindings, subviews or event handlers from the side-effect callback,
the safest is to not use the `t` argument at all.
If you do, they will be added every time the callback is run and only cleaned up when the view is unmounted.
#### `tag` vs `t`
If you don't need a view component with data-binding, sub views and event handler attributes, the template language also is available in `ui/general/html.js` without any of these bells and whistles, exported as `tag`. As opposed to static templates with `tag`, you always use
`TemplateView` as an instance of a class, as there is some extra state to keep track (bindings, event handlers and subviews).
Although syntactically similar, `TemplateBuilder` and `tag` are not functionally equivalent.
Primarily `t` **supports** bindings and event handlers while `tag` **does not**. This is because to remove event listeners, we need to keep track of them, and thus we need to keep this state somewhere which
we can't do with a simple function call but we can insite the TemplateView class.
```js
// The onClick here wont work!!
tag.button({className:"awesome-btn", onClick: () => this.foo()});
class MyView extends TemplateView {
render(t, vm){
// The onClick works here.
t.button({className:"awesome-btn", onClick: () => this.foo()});
}
}
```
## ListView
A view component that renders and updates a list of sub views for every item in a `ObservableList`.
```js
const list = new ListView({
list: someObservableList
}, listValue => return new ChildView(listValue))
```
As items are added, removed, moved (change position) and updated, the DOM will be kept in sync.
There is also a `LazyListView` that only renders items in and around the current viewport, with the restriction that all items in the list must be rendered with the same height.
### Sub view updates
Unless the `parentProvidesUpdates` option in the constructor is set to `false`, the ListView will call the `update` method on the child `IView` component when it receives an update event for one of the items in the `ObservableList`.
This way, not every sub view has to have an individual listener on it's view model (a value from the observable list), and all updates go from the observable list to the list view, who then notifies the correct sub view.

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

109
doc/impl-thoughts/SDK.md Normal file
View File

@ -0,0 +1,109 @@
SDK:
- we need to compile src/lib.ts to javascript, with a d.ts file generated as well. We need to compile to javascript once for cjs and once of es modules. The package.json looks like this:
```
"main": "./dist/index.cjs",
"exports": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"types": "dist/index.d.ts",
```
we don't need to bundle for the sdk case! we might need to do some transpilation to just plain ES6 (e.g. don't assume ?. and ??) we could use a browserslist query for this e.g. `node 14`. esbuild seems to support this as well, tldraw uses esbuild for their build.
one advantage of not bundling the files for the sdk is that you can still use import overrides in the consuming project build settings. is that an idiomatic way of doing things though?
this way we will support typescript, non-esm javascript and esm javascript using libhydrogen as an SDK
got this from https://medium.com/dazn-tech/publishing-npm-packages-as-native-es-modules-41ffbc0a9dea
how about the assets?
we also need to build the app
we need to be able to version libhydrogen independently from hydrogen the app? as any api breaking changes will need a major version increase. we probably want to end up with a monorepo where the app uses the sdk as well and we just use the local code with yarn link?
## Assets
we want to provide scss/sass files, but also css that can be included
https://github.com/webpack/webpack/issues/7353 seems to imply that we just need to include the assets in the published files and from there on it is the consumer of libhydrogen's problem.
how does all of this tie in with vite?
we want to have hydrogenapp be a consumer of libhydrogen, potentially as two packages in a monorepo ... but we want the SDK to expose views and stylesheets... without having an index.html (which would be in hydrogenapp). this seems a bit odd...?
what would be in hydrogenapp actually? just an index.html file?
I'm not sure it makes sense to have them be 2 different packages in a monorepo, they should really be two artifacts from the same directory.
the stylesheets included in libhydrogen are from the same main.css file as is used in the app
https://www.freecodecamp.org/news/build-a-css-library-with-vitejs/
basically, we import the sass file from src/lib.ts so it is included in the assets there too, and we also create a plugin that emits a file for every sass file as suggested in the link above?
we probably want two different build commands for the app and the sdk though, we could have a parent vite config that both build configs extend from?
### Dependency assets
our dependencies should not be bundled for the SDK case. So if we import aesjs, it would be up to the build system of the consuming project to make that import work.
the paths.ts thingy ... we want to make it easy for people to setup the assets for our dependencies (olm), some assets are also part of the sdk itself. it might make sense to make all of the assets there part of the sdk (e.g. bundle olm.wasm and friends?) although shipping crypto, etc ...
perhaps we should have an include file per build system that treats own assets and dep assets the same by including the package name as wel for our own deps:
```js
import _downloadSandboxPath from "@matrix-org/hydrogen-sdk/download-sandbox.html?url";
import _serviceWorkerPath from "@matrix-org/hydrogen-sdk/sw.js?url"; // not yet sure this is the way to do it
import olmWasmPath from "@matrix-org/olm/olm.wasm?url";
import olmJsPath from "@matrix-org/olm/olm.js?url";
import olmLegacyJsPath from "@matrix-org/olm/olm_legacy.js?url";
export const olmPaths = {
wasm: olmWasmPath,
legacyBundle: olmLegacyJsPath,
wasmBundle: olmJsPath,
};
export const downloadSandboxPath = _downloadSandboxPath;
```
we could put this file per build system, as ESM, in dist as well so you can include it to get the paths
## Tooling
- `vite` a more high-level build tool that takes your index.html and turns it into optimized assets that you can host for production, as well as a very fast dev server. is used to have good default settings for our tools, typescript support, and also deals with asset compiling. good dev server. Would be nice to have the same tool for dev and prod. vite has good support for using `import` for anything that is not javascript, where we had an issue with `snowpack` (to get the prod path of an asset).
- `rollup`: inlines
- `lerna` is used to handle multi-package monorepos
- `esbuild`: a js/ts build tool that we could use for building the lower level sdk where no other assets are involved, `vite` uses it for fast dev builds (`rollup` for prod). For now we won't extract a lower level sdk though.
## TODO
- finish vite app build (without IE11 for now?)
- create vite config to build src/lib.ts in cjs and esm, inheriting from a common base config with the app config
- this will create a dist folder with
- the whole source tree in es and cjs format
- an es file to import get the asset paths as they are expected by Platform, per build system
- assets from hydrogen itself:
- css files and any resource used therein
- download-sandbox.html
- a type declaration file (index.d.ts)
## Questions
- can rollup not bundle the source tree and leave modules intact?
- if we can use a function that creates a chunk per file to pass to manualChunks and disable chunk hashing we can probably do this. See https://rollupjs.org/guide/en/#outputmanualchunks
looks like we should be able to disable chunk name hashing with chunkFileNames https://rollupjs.org/guide/en/#outputoptions-object
we should test this with a vite test config
we also need to compile down to ES6, both for the app and for the sdk

View File

@ -1,19 +1,24 @@
{
"name": "hydrogen-web",
"version": "0.2.7",
"version": "0.3.1",
"description": "A javascript matrix client prototype, trying to minize RAM usage by offloading as much as possible to IndexedDB",
"main": "index.js",
"directories": {
"doc": "doc"
},
"enginesStrict": {
"node": ">=15"
},
"scripts": {
"lint": "eslint --cache src/",
"lint-ts": "eslint src/ -c .ts-eslintrc.js --ext .ts",
"lint-ci": "eslint src/",
"test": "impunity --entry-point src/main.js --force-esm-dirs lib/ src/",
"start": "snowpack dev --port 3000",
"build": "node --experimental-modules scripts/build.mjs",
"postinstall": "node ./scripts/post-install.js"
"test": "impunity --entry-point src/platform/web/main.js src/platform/web/Platform.js --force-esm-dirs lib/ src/ --root-dir src/",
"test:postcss": "impunity --entry-point scripts/postcss/tests/css-compile-variables.test.js scripts/postcss/tests/css-url-to-variables.test.js",
"test:sdk": "yarn build:sdk && cd ./scripts/sdk/test/ && yarn --no-lockfile && node test-sdk-in-esm-vite-build-env.js && node test-sdk-in-commonjs-env.js",
"start": "vite --port 3000",
"build": "vite build && ./scripts/cleanup.sh",
"build:sdk": "./scripts/sdk/build.sh",
"watch:sdk": "./scripts/sdk/build.sh && yarn run vite build -c vite.sdk-lib-config.js --watch"
},
"repository": {
"type": "git",
@ -26,46 +31,36 @@
},
"homepage": "https://github.com/vector-im/hydrogen-web/#readme",
"devDependencies": {
"@babel/core": "^7.11.1",
"@babel/preset-env": "^7.11.0",
"@rollup/plugin-babel": "^5.1.0",
"@rollup/plugin-commonjs": "^15.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-multi-entry": "^4.0.0",
"@rollup/plugin-node-resolve": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^4.29.2",
"@typescript-eslint/parser": "^4.29.2",
"autoprefixer": "^10.2.6",
"cheerio": "^1.0.0-rc.3",
"commander": "^6.0.0",
"acorn": "^8.6.0",
"acorn-walk": "^8.2.0",
"aes-js": "^3.1.2",
"bs58": "^4.0.1",
"core-js": "^3.6.5",
"es6-promise": "https://github.com/bwindels/es6-promise.git#bwindels/expose-flush",
"escodegen": "^2.0.0",
"eslint": "^7.32.0",
"fake-indexeddb": "^3.1.2",
"finalhandler": "^1.1.1",
"impunity": "^1.0.1",
"impunity": "^1.0.9",
"mdn-polyfills": "^5.20.0",
"merge-options": "^3.0.4",
"node-html-parser": "^4.0.0",
"postcss": "^8.1.1",
"postcss-css-variables": "^0.17.0",
"postcss-flexbugs-fixes": "^4.2.1",
"postcss-import": "^12.0.1",
"postcss-url": "^8.0.0",
"postcss-css-variables": "^0.18.0",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-value-parser": "^4.2.0",
"regenerator-runtime": "^0.13.7",
"rollup": "^2.26.4",
"rollup-plugin-cleanup": "^3.1.1",
"serve-static": "^1.13.2",
"snowpack": "^3.8.3",
"typescript": "^4.3.5",
"svgo": "^2.8.0",
"text-encoding": "^0.7.0",
"typescript": "^4.7.0",
"vite": "^2.9.8",
"xxhashjs": "^0.2.2"
},
"dependencies": {
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"aes-js": "^3.1.2",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz",
"another-json": "^0.2.0",
"base64-arraybuffer": "^0.2.0",
"bs58": "^4.0.1",
"dompurify": "^2.3.0",
"es6-promise": "https://github.com/bwindels/es6-promise.git#bwindels/expose-flush",
"text-encoding": "^0.7.0"
"off-color": "^2.0.0"
}
}

View File

@ -0,0 +1,100 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<script type="text/javascript">
class IDBError extends Error {
constructor(errorEvent) {
const request = errorEvent.target;
const {error} = request;
super(error.message);
this.name = error.name;
this.errorEvent = errorEvent;
}
preventAbort() {
this.errorEvent.preventDefault();
}
}
class AbortError extends Error {
get name() { return "AbortError"; }
}
function reqAsPromise(req) {
return new Promise(function (resolve, reject) {
req.onsuccess = function(e) {
resolve(e.target.result);
};
req.onerror = function(e) {
reject(new IDBError(e));
};
});
}
function txnAsPromise(txn) {
return new Promise((resolve, reject) => {
txn.addEventListener("complete", () => resolve());
txn.addEventListener("abort", event => {
reject(new AbortError());
});
});
}
function Storage(databaseName) {
this._databaseName = databaseName;
this._database = null;
}
Storage.prototype = {
open: function() {
const req = window.indexedDB.open(this._databaseName);
const self = this;
req.onupgradeneeded = function(ev) {
const db = ev.target.result;
const oldVersion = ev.oldVersion;
self._createStores(db, oldVersion);
};
return reqAsPromise(req).then(function() {
self._database = req.result;
});
},
readWriteTxn: function(storeName) {
return this._database.transaction([storeName], "readwrite");
},
readTxn: function(storeName) {
return this._database.transaction([storeName], "readonly");
},
_createStores: function(db) {
db.createObjectStore("foos", {keyPath: "id"});
}
};
async function main() {
const storage = new Storage("idb-continue-on-constrainterror");
await storage.open();
const txn1 = storage.readWriteTxn("foos");
const store = txn1.objectStore("foos");
await reqAsPromise(store.clear());
console.log("first foo read back", await reqAsPromise(store.get(5)));
await reqAsPromise(store.add({id: 5, name: "Mr Foo"}));
try {
await reqAsPromise(store.add({id: 5, name: "bar"}));
} catch (err) {
console.log("we did get an error", err.name);
err.preventAbort();
}
await txnAsPromise(txn1);
const txn2 = storage.readTxn("foos");
const store2 = txn2.objectStore("foos");
console.log("got name from second txn", await reqAsPromise(store2.get(5)));
}
main().catch(err => console.error(err));
</script>
</body>
</html>

18
scripts/.eslintrc.js Normal file
View File

@ -0,0 +1,18 @@
module.exports = {
"env": {
"node": true,
"es6": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"rules": {
"no-console": "off",
"no-empty": "off",
"no-prototype-builtins": "off",
"no-unused-vars": "warn"
},
};

View File

@ -0,0 +1,51 @@
const fs = require('fs/promises');
const path = require('path');
module.exports = function injectWebManifest(manifestFile) {
let root;
let base;
let manifestHref;
return {
name: "hydrogen:injectWebManifest",
apply: "build",
configResolved: config => {
root = config.root;
base = config.base;
},
transformIndexHtml: {
transform(html) {
return [{
tag: "link",
attrs: {rel: "manifest", href: manifestHref},
injectTo: "head"
}];
},
},
generateBundle: async function() {
const absoluteManifestFile = path.resolve(root, manifestFile);
const manifestDir = path.dirname(absoluteManifestFile);
const json = await fs.readFile(absoluteManifestFile, {encoding: "utf8"});
const manifest = JSON.parse(json);
for (const icon of manifest.icons) {
const iconFileName = path.resolve(manifestDir, icon.src);
const imgData = await fs.readFile(iconFileName);
const ref = this.emitFile({
type: "asset",
name: path.basename(iconFileName),
source: imgData
});
// we take the basename as getFileName gives the filename
// relative to the output dir, but the manifest is an asset
// just like they icon, so we assume they end up in the same dir
icon.src = path.basename(this.getFileName(ref));
}
const outputName = path.basename(absoluteManifestFile);
const manifestRef = this.emitFile({
type: "asset",
name: outputName,
source: JSON.stringify(manifest)
});
manifestHref = base + this.getFileName(manifestRef);
}
};
}

View File

@ -0,0 +1,376 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const path = require('path').posix;
const {optimize} = require('svgo');
async function readCSSSource(location) {
const fs = require("fs").promises;
const resolvedLocation = path.resolve(__dirname, "../../", `${location}/theme.css`);
const data = await fs.readFile(resolvedLocation);
return data;
}
function getRootSectionWithVariables(variables) {
return `:root{\n${Object.entries(variables).reduce((acc, [key, value]) => acc + `--${key}: ${value};\n`, "")} }\n\n`;
}
function appendVariablesToCSS(variables, cssSource) {
return cssSource + getRootSectionWithVariables(variables);
}
function addThemesToConfig(bundle, manifestLocations, defaultThemes) {
for (const [fileName, info] of Object.entries(bundle)) {
if (fileName === "config.json") {
const source = new TextDecoder().decode(info.source);
const config = JSON.parse(source);
config["themeManifests"] = manifestLocations;
config["defaultTheme"] = defaultThemes;
info.source = new TextEncoder().encode(JSON.stringify(config, undefined, 2));
}
}
}
/**
* Returns an object where keys are the svg file names and the values
* are the svg code (optimized)
* @param {*} icons Object where keys are css variable names and values are locations of the svg
* @param {*} manifestLocation Location of manifest used for resolving path
*/
async function generateIconSourceMap(icons, manifestLocation) {
const sources = {};
const fileNames = [];
const promises = [];
const fs = require("fs").promises;
for (const icon of Object.values(icons)) {
const [location] = icon.split("?");
// resolve location against manifestLocation
const resolvedLocation = path.resolve(manifestLocation, location);
const iconData = fs.readFile(resolvedLocation);
promises.push(iconData);
const fileName = path.basename(resolvedLocation);
fileNames.push(fileName);
}
const results = await Promise.all(promises);
for (let i = 0; i < results.length; ++i) {
const svgString = results[i].toString();
const result = optimize(svgString, {
plugins: [
{
name: "preset-default",
params: {
overrides: { convertColors: false, },
},
},
],
});
const optimizedSvgString = result.data;
sources[fileNames[i]] = optimizedSvgString;
}
return sources;
}
/**
* Returns a mapping from location (of manifest file) to an array containing all the chunks (of css files) generated from that location.
* To understand what chunk means in this context, see https://rollupjs.org/guide/en/#generatebundle.
* @param {*} bundle Mapping from fileName to AssetInfo | ChunkInfo
*/
function getMappingFromLocationToChunkArray(bundle) {
const chunkMap = new Map();
for (const [fileName, info] of Object.entries(bundle)) {
if (!fileName.endsWith(".css") || info.type === "asset" || info.facadeModuleId?.includes("type=runtime")) {
continue;
}
const location = info.facadeModuleId?.match(/(.+)\/.+\.css/)?.[1];
if (!location) {
throw new Error("Cannot find location of css chunk!");
}
const array = chunkMap.get(location);
if (!array) {
chunkMap.set(location, [info]);
}
else {
array.push(info);
}
}
return chunkMap;
}
/**
* Returns a mapping from unhashed file name (of css files) to AssetInfo.
* To understand what AssetInfo means in this context, see https://rollupjs.org/guide/en/#generatebundle.
* @param {*} bundle Mapping from fileName to AssetInfo | ChunkInfo
*/
function getMappingFromFileNameToAssetInfo(bundle) {
const assetMap = new Map();
for (const [fileName, info] of Object.entries(bundle)) {
if (!fileName.endsWith(".css")) {
continue;
}
if (info.type === "asset") {
/**
* So this is the css assetInfo that contains the asset hashed file name.
* We'll store it in a separate map indexed via fileName (unhashed) to avoid
* searching through the bundle array later.
*/
assetMap.set(info.name, info);
}
}
return assetMap;
}
/**
* Returns a mapping from location (of manifest file) to ChunkInfo of the runtime css asset
* To understand what ChunkInfo means in this context, see https://rollupjs.org/guide/en/#generatebundle.
* @param {*} bundle Mapping from fileName to AssetInfo | ChunkInfo
*/
function getMappingFromLocationToRuntimeChunk(bundle) {
let runtimeThemeChunkMap = new Map();
for (const [fileName, info] of Object.entries(bundle)) {
if (!fileName.endsWith(".css") || info.type === "asset") {
continue;
}
const location = info.facadeModuleId?.match(/(.+)\/.+\.css/)?.[1];
if (!location) {
throw new Error("Cannot find location of css chunk!");
}
if (info.facadeModuleId?.includes("type=runtime")) {
/**
* We have a separate field in manifest.source just for the runtime theme,
* so store this separately.
*/
runtimeThemeChunkMap.set(location, info);
}
}
return runtimeThemeChunkMap;
}
module.exports = function buildThemes(options) {
let manifest, variants, defaultDark, defaultLight, defaultThemes = {};
let isDevelopment = false;
const virtualModuleId = '@theme/'
const resolvedVirtualModuleId = '\0' + virtualModuleId;
const themeToManifestLocation = new Map();
return {
name: "build-themes",
enforce: "pre",
configResolved(config) {
if (config.command === "serve") {
isDevelopment = true;
}
},
async buildStart() {
const { themeConfig } = options;
for (const location of themeConfig.themes) {
manifest = require(`${location}/manifest.json`);
const themeCollectionId = manifest.id;
themeToManifestLocation.set(themeCollectionId, location);
variants = manifest.values.variants;
for (const [variant, details] of Object.entries(variants)) {
const fileName = `theme-${themeCollectionId}-${variant}.css`;
if (themeCollectionId === themeConfig.default && details.default) {
// This is the default theme, stash the file name for later
if (details.dark) {
defaultDark = fileName;
defaultThemes["dark"] = `${themeCollectionId}-${variant}`;
}
else {
defaultLight = fileName;
defaultThemes["light"] = `${themeCollectionId}-${variant}`;
}
}
// emit the css as built theme bundle
if (!isDevelopment) {
this.emitFile({ type: "chunk", id: `${location}/theme.css?variant=${variant}${details.dark ? "&dark=true" : ""}`, fileName, });
}
}
// emit the css as runtime theme bundle
if (!isDevelopment) {
this.emitFile({ type: "chunk", id: `${location}/theme.css?type=runtime`, fileName: `theme-${themeCollectionId}-runtime.css`, });
}
}
},
resolveId(id) {
if (id.startsWith(virtualModuleId)) {
return '\0' + id;
}
},
async load(id) {
if (isDevelopment) {
/**
* To load the theme during dev, we need to take a different approach because emitFile is not supported in dev.
* We solve this by resolving virtual file "@theme/name/variant" into the necessary css import.
* This virtual file import is removed when hydrogen is built (see transform hook).
*/
if (id.startsWith(resolvedVirtualModuleId)) {
let [theme, variant, file] = id.substr(resolvedVirtualModuleId.length).split("/");
if (theme === "default") {
theme = options.themeConfig.default;
}
const location = themeToManifestLocation.get(theme);
const manifest = require(`${location}/manifest.json`);
const variants = manifest.values.variants;
if (!variant || variant === "default") {
// choose the first default variant for now
// this will need to support light/dark variants as well
variant = Object.keys(variants).find(variantName => variants[variantName].default);
}
if (!file) {
file = "index.js";
}
switch (file) {
case "index.js": {
const isDark = variants[variant].dark;
return `import "${path.resolve(`${location}/theme.css`)}${isDark? "?dark=true": ""}";` +
`import "@theme/${theme}/${variant}/variables.css"`;
}
case "variables.css": {
const variables = variants[variant].variables;
const css = getRootSectionWithVariables(variables);
return css;
}
}
}
}
else {
const result = id.match(/(.+)\/theme.css\?variant=([^&]+)/);
if (result) {
const [, location, variant] = result;
const cssSource = await readCSSSource(location);
const config = variants[variant];
return appendVariablesToCSS(config.variables, cssSource);
}
return null;
}
},
transform(code, id) {
if (isDevelopment) {
return;
}
/**
* Removes develop-only script tag; this cannot be done in transformIndexHtml hook because
* by the time that hook runs, the import is added to the bundled js file which would
* result in a runtime error.
*/
const devScriptTag =
/<script type="module"> import "@theme\/.+"; <\/script>/;
if (id.endsWith("index.html")) {
const htmlWithoutDevScript = code.replace(devScriptTag, "");
return htmlWithoutDevScript;
}
},
transformIndexHtml(_, ctx) {
if (isDevelopment) {
// Don't add default stylesheets to index.html on dev
return;
}
let darkThemeLocation, lightThemeLocation;
for (const [, bundle] of Object.entries(ctx.bundle)) {
if (bundle.name === defaultDark) {
darkThemeLocation = bundle.fileName;
}
if (bundle.name === defaultLight) {
lightThemeLocation = bundle.fileName;
}
}
return [
{
tag: "link",
attrs: {
rel: "stylesheet",
type: "text/css",
media: "(prefers-color-scheme: dark)",
href: `./${darkThemeLocation}`,
class: "theme",
}
},
{
tag: "link",
attrs: {
rel: "stylesheet",
type: "text/css",
media: "(prefers-color-scheme: light)",
href: `./${lightThemeLocation}`,
class: "theme",
}
},
];
},
async generateBundle(_, bundle) {
const assetMap = getMappingFromFileNameToAssetInfo(bundle);
const chunkMap = getMappingFromLocationToChunkArray(bundle);
const runtimeThemeChunkMap = getMappingFromLocationToRuntimeChunk(bundle);
const manifestLocations = [];
// Location of the directory containing manifest relative to the root of the build output
const manifestLocation = "assets";
for (const [location, chunkArray] of chunkMap) {
const manifest = require(`${location}/manifest.json`);
const compiledVariables = options.compiledVariables.get(location);
const derivedVariables = compiledVariables["derived-variables"];
const icon = compiledVariables["icon"];
const builtAssets = {};
let themeKey;
for (const chunk of chunkArray) {
const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/);
themeKey = name;
const locationRelativeToBuildRoot = assetMap.get(chunk.fileName).fileName;
const locationRelativeToManifest = path.relative(manifestLocation, locationRelativeToBuildRoot);
builtAssets[`${name}-${variant}`] = locationRelativeToManifest;
}
// Emit the base svg icons as asset
const nameToAssetHashedLocation = [];
const nameToSource = await generateIconSourceMap(icon, location);
for (const [name, source] of Object.entries(nameToSource)) {
const ref = this.emitFile({ type: "asset", name, source });
const assetHashedName = this.getFileName(ref);
nameToAssetHashedLocation[name] = assetHashedName;
}
// Update icon section in output manifest with paths to the icon in build output
for (const [variable, location] of Object.entries(icon)) {
const [locationWithoutQueryParameters, queryParameters] = location.split("?");
const name = path.basename(locationWithoutQueryParameters);
const locationRelativeToBuildRoot = nameToAssetHashedLocation[name];
const locationRelativeToManifest = path.relative(manifestLocation, locationRelativeToBuildRoot);
icon[variable] = `${locationRelativeToManifest}?${queryParameters}`;
}
const runtimeThemeChunk = runtimeThemeChunkMap.get(location);
const runtimeAssetLocation = path.relative(manifestLocation, assetMap.get(runtimeThemeChunk.fileName).fileName);
manifest.source = {
"built-assets": builtAssets,
"runtime-asset": runtimeAssetLocation,
"derived-variables": derivedVariables,
"icon": icon,
};
const name = `theme-${themeKey}.json`;
manifestLocations.push(`${manifestLocation}/${name}`);
this.emitFile({
type: "asset",
name,
source: JSON.stringify(manifest),
});
}
addThemesToConfig(bundle, manifestLocations, defaultThemes);
},
}
}

View File

@ -0,0 +1,157 @@
const fs = require('fs/promises');
const path = require('path');
const xxhash = require('xxhashjs');
function contentHash(str) {
var hasher = new xxhash.h32(0);
hasher.update(str);
return hasher.digest();
}
function injectServiceWorker(swFile, findUnhashedFileNamesFromBundle, placeholdersPerChunk) {
const swName = path.basename(swFile);
let root;
let version;
let logger;
return {
name: "hydrogen:injectServiceWorker",
apply: "build",
enforce: "post",
buildStart() {
this.emitFile({
type: "chunk",
fileName: swName,
id: swFile,
});
},
configResolved: config => {
root = config.root;
version = JSON.parse(config.define.DEFINE_VERSION); // unquote
logger = config.logger;
},
generateBundle: async function(options, bundle) {
const otherUnhashedFiles = findUnhashedFileNamesFromBundle(bundle);
const unhashedFilenames = [swName].concat(otherUnhashedFiles);
const unhashedFileContentMap = unhashedFilenames.reduce((map, fileName) => {
const chunkOrAsset = bundle[fileName];
if (!chunkOrAsset) {
throw new Error("could not get content for uncached asset or chunk " + fileName);
}
map[fileName] = chunkOrAsset.source || chunkOrAsset.code;
return map;
}, {});
const assets = Object.values(bundle);
const hashedFileNames = assets.map(o => o.fileName).filter(fileName => !unhashedFileContentMap[fileName]);
const globalHash = getBuildHash(hashedFileNames, unhashedFileContentMap);
const placeholderValues = {
DEFINE_GLOBAL_HASH: `"${globalHash}"`,
...getCacheFileNamePlaceholderValues(swName, unhashedFilenames, assets, placeholdersPerChunk)
};
replacePlaceholdersInChunks(assets, placeholdersPerChunk, placeholderValues);
logger.info(`\nBuilt ${version} (${globalHash})`);
}
};
}
function getBuildHash(hashedFileNames, unhashedFileContentMap) {
const unhashedHashes = Object.entries(unhashedFileContentMap).map(([fileName, content]) => {
return `${fileName}-${contentHash(Buffer.from(content))}`;
});
const globalHashAssets = hashedFileNames.concat(unhashedHashes);
globalHashAssets.sort();
return contentHash(globalHashAssets.join(",")).toString();
}
const NON_PRECACHED_JS = [
"hydrogen-legacy",
"olm_legacy.js",
// most environments don't need the worker
"main.js"
];
function isPreCached(asset) {
const {name, fileName} = asset;
return name.endsWith(".svg") ||
name.endsWith(".png") ||
name.endsWith(".css") ||
name.endsWith(".wasm") ||
name.endsWith(".html") ||
// the index and vendor chunks don't have an extension in `name`, so check extension on `fileName`
fileName.endsWith(".js") && !NON_PRECACHED_JS.includes(path.basename(name));
}
function getCacheFileNamePlaceholderValues(swName, unhashedFilenames, assets) {
const unhashedPreCachedAssets = [];
const hashedPreCachedAssets = [];
const hashedCachedOnRequestAssets = [];
for (const asset of assets) {
const {name, fileName} = asset;
// the service worker should not be cached at all,
// it's how updates happen
if (fileName === swName) {
continue;
} else if (unhashedFilenames.includes(fileName)) {
unhashedPreCachedAssets.push(fileName);
} else if (isPreCached(asset)) {
hashedPreCachedAssets.push(fileName);
} else {
hashedCachedOnRequestAssets.push(fileName);
}
}
return {
DEFINE_UNHASHED_PRECACHED_ASSETS: JSON.stringify(unhashedPreCachedAssets),
DEFINE_HASHED_PRECACHED_ASSETS: JSON.stringify(hashedPreCachedAssets),
DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS: JSON.stringify(hashedCachedOnRequestAssets)
}
}
function replacePlaceholdersInChunks(assets, placeholdersPerChunk, placeholderValues) {
for (const [name, placeholderMap] of Object.entries(placeholdersPerChunk)) {
const chunk = assets.find(a => a.type === "chunk" && a.name === name);
if (!chunk) {
throw new Error(`could not find chunk ${name} to replace placeholders`);
}
for (const [placeholderName, placeholderLiteral] of Object.entries(placeholderMap)) {
const replacedValue = placeholderValues[placeholderName];
const oldCode = chunk.code;
chunk.code = chunk.code.replaceAll(placeholderLiteral, replacedValue);
if (chunk.code === oldCode) {
throw new Error(`Could not replace ${placeholderName} in ${name}, looking for literal ${placeholderLiteral}:\n${chunk.code}`);
}
}
}
}
/** creates a value to be include in the `define` build settings,
* but can be replace at the end of the build in certain chunks.
* We need this for injecting the global build hash and the final
* filenames in the service worker and index chunk.
* These values are only known in the generateBundle step, so we
* replace them by unique strings wrapped in a prompt call so no
* transformation will touch them (minifying, ...) and we can do a
* string replacement still at the end of the build. */
function definePlaceholderValue(mode, name, devValue) {
if (mode === "production") {
// note that `prompt(...)` will never be in the final output, it's replaced by the final value
// once we know at the end of the build what it is and just used as a temporary value during the build
// as something that will not be transformed.
// I first considered Symbol but it's not inconceivable that babel would transform this.
return `prompt(${JSON.stringify(name)})`;
} else {
return JSON.stringify(devValue);
}
}
function createPlaceholderValues(mode) {
return {
DEFINE_GLOBAL_HASH: definePlaceholderValue(mode, "DEFINE_GLOBAL_HASH", null),
DEFINE_UNHASHED_PRECACHED_ASSETS: definePlaceholderValue(mode, "UNHASHED_PRECACHED_ASSETS", []),
DEFINE_HASHED_PRECACHED_ASSETS: definePlaceholderValue(mode, "HASHED_PRECACHED_ASSETS", []),
DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS: definePlaceholderValue(mode, "HASHED_CACHED_ON_REQUEST_ASSETS", []),
};
}
module.exports = {injectServiceWorker, createPlaceholderValues};

View File

@ -1,578 +0,0 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {build as snowpackBuild, loadConfiguration} from "snowpack"
import cheerio from "cheerio";
import fsRoot from "fs";
const fs = fsRoot.promises;
import path from "path";
import xxhash from 'xxhashjs';
import { rollup } from 'rollup';
import postcss from "postcss";
import postcssImport from "postcss-import";
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import commander from "commander";
// needed for legacy bundle
import babel from '@rollup/plugin-babel';
// needed to find the polyfill modules in the main-legacy.js bundle
import { nodeResolve } from '@rollup/plugin-node-resolve';
// needed because some of the polyfills are written as commonjs modules
import commonjs from '@rollup/plugin-commonjs';
// multi-entry plugin so we can add polyfill file to main
import multi from '@rollup/plugin-multi-entry';
import removeJsComments from 'rollup-plugin-cleanup';
// replace urls of asset names with content hashed version
import postcssUrl from "postcss-url";
import cssvariables from "postcss-css-variables";
import autoprefixer from "autoprefixer";
import flexbugsFixes from "postcss-flexbugs-fixes";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectDir = path.join(__dirname, "../");
const snowpackOutPath = path.join(projectDir, "snowpack-build-output");
const cssSrcDir = path.join(projectDir, "src/platform/web/ui/css/");
const snowpackConfig = await loadConfiguration({buildOptions: {out: snowpackOutPath}}, "snowpack.config.js");
const snowpackOutDir = snowpackConfig.buildOptions.out.substring(projectDir.length);
const srcDir = path.join(projectDir, `${snowpackOutDir}/src/`);
const isPathInSrcDir = path => path.startsWith(srcDir);
const parameters = new commander.Command();
parameters
.option("--modern-only", "don't make a legacy build")
.option("--override-imports <json file>", "pass in a file to override import paths, see doc/SKINNING.md")
.option("--override-css <main css file>", "pass in an alternative main css file")
parameters.parse(process.argv);
/**
* We use Snowpack to handle the translation of TypeScript
* into JavaScript. We thus can't bundle files straight from
* the src directory, since some of them are TypeScript, and since
* they may import Node modules. We thus bundle files after they
* have been processed by Snowpack. This function returns paths
* to the files that have already been pre-processed in this manner.
*/
function srcPath(src) {
return path.join(snowpackOutDir, 'src', src);
}
async function build({modernOnly, overrideImports, overrideCss}) {
await snowpackBuild({config: snowpackConfig});
// get version number
const version = JSON.parse(await fs.readFile(path.join(projectDir, "package.json"), "utf8")).version;
let importOverridesMap;
if (overrideImports) {
importOverridesMap = await readImportOverrides(overrideImports);
}
const devHtml = await fs.readFile(path.join(snowpackOutPath, "index.html"), "utf8");
const doc = cheerio.load(devHtml);
const themes = [];
findThemes(doc, themeName => {
themes.push(themeName);
});
// clear target dir
const targetDir = path.join(projectDir, "target/");
await removeDirIfExists(targetDir);
await createDirs(targetDir, themes);
const assets = new AssetMap(targetDir);
// copy olm assets
const olmAssets = await copyFolder(path.join(projectDir, "lib/olm/"), assets.directory);
assets.addSubMap(olmAssets);
await assets.write(`hydrogen.js`, await buildJs(srcPath("main.js"), [srcPath("platform/web/Platform.js")], importOverridesMap));
if (!modernOnly) {
await assets.write(`hydrogen-legacy.js`, await buildJsLegacy(srcPath("main.js"), [
srcPath('platform/web/legacy-polyfill.js'),
srcPath('platform/web/LegacyPlatform.js')
], importOverridesMap));
await assets.write(`worker.js`, await buildJsLegacy(srcPath("platform/web/worker/main.js"), [srcPath('platform/web/worker/polyfill.js')]));
}
// copy over non-theme assets
const baseConfig = JSON.parse(await fs.readFile(path.join(projectDir, "assets/config.json"), {encoding: "utf8"}));
const downloadSandbox = "download-sandbox.html";
let downloadSandboxHtml = await fs.readFile(path.join(projectDir, `assets/${downloadSandbox}`));
await assets.write(downloadSandbox, downloadSandboxHtml);
// creates the directories where the theme css bundles are placed in,
// and writes to assets, so the build bundles can translate them, so do it first
await copyThemeAssets(themes, assets);
await buildCssBundles(buildCssLegacy, themes, assets, overrideCss);
await buildManifest(assets);
// all assets have been added, create a hash from all assets name to cache unhashed files like index.html
assets.addToHashForAll("index.html", devHtml);
let swSource = await fs.readFile(path.join(snowpackOutPath, "sw.js"), "utf8");
assets.addToHashForAll("sw.js", swSource);
const globalHash = assets.hashForAll();
await buildServiceWorker(swSource, version, globalHash, assets);
await buildHtml(doc, version, baseConfig, globalHash, modernOnly, assets);
await removeDirIfExists(snowpackOutPath);
console.log(`built hydrogen ${version} (${globalHash}) successfully with ${assets.size} files`);
}
async function findThemes(doc, callback) {
doc("link[rel~=stylesheet][title]").each((i, el) => {
const theme = doc(el);
const href = theme.attr("href");
const themesPrefix = "/themes/";
const prefixIdx = href.indexOf(themesPrefix);
if (prefixIdx !== -1) {
const themeNameStart = prefixIdx + themesPrefix.length;
const themeNameEnd = href.indexOf("/", themeNameStart);
const themeName = href.substr(themeNameStart, themeNameEnd - themeNameStart);
callback(themeName, theme);
}
});
}
async function createDirs(targetDir, themes) {
await fs.mkdir(targetDir);
const themeDir = path.join(targetDir, "themes");
await fs.mkdir(themeDir);
for (const theme of themes) {
await fs.mkdir(path.join(themeDir, theme));
}
}
async function copyThemeAssets(themes, assets) {
for (const theme of themes) {
const themeDstFolder = path.join(assets.directory, `themes/${theme}`);
const themeSrcFolder = path.join(cssSrcDir, `themes/${theme}`);
const themeAssets = await copyFolder(themeSrcFolder, themeDstFolder, file => {
return !file.endsWith(".css");
});
assets.addSubMap(themeAssets);
}
return assets;
}
async function buildHtml(doc, version, baseConfig, globalHash, modernOnly, assets) {
// transform html file
// change path to main.css to css bundle
doc("link[rel=stylesheet]:not([title])").attr("href", assets.resolve(`hydrogen.css`));
// adjust file name of icon on iOS
doc("link[rel=apple-touch-icon]").attr("href", assets.resolve(`icon-maskable.png`));
// change paths to all theme stylesheets
findThemes(doc, (themeName, theme) => {
theme.attr("href", assets.resolve(`themes/${themeName}/bundle.css`));
});
const configJSON = JSON.stringify(Object.assign({}, baseConfig, {
worker: assets.has("worker.js") ? assets.resolve(`worker.js`) : null,
downloadSandbox: assets.resolve("download-sandbox.html"),
serviceWorker: "sw.js",
olm: {
wasm: assets.resolve("olm.wasm"),
legacyBundle: assets.resolve("olm_legacy.js"),
wasmBundle: assets.resolve("olm.js"),
}
}));
const modernScript = `import {main, Platform} from "./${assets.resolve(`hydrogen.js`)}"; main(new Platform(document.body, ${configJSON}));`;
const mainScripts = [
`<script type="module">${wrapWithLicenseComments(modernScript)}</script>`
];
if (!modernOnly) {
const legacyScript = `hydrogen.main(new hydrogen.Platform(document.body, ${configJSON}));`;
mainScripts.push(
`<script type="text/javascript" nomodule src="${assets.resolve(`hydrogen-legacy.js`)}"></script>`,
`<script type="text/javascript" nomodule>${wrapWithLicenseComments(legacyScript)}</script>`
);
}
doc("script#main").replaceWith(mainScripts.join(""));
const versionScript = doc("script#version");
versionScript.attr("type", "text/javascript");
let vSource = versionScript.contents().text();
vSource = vSource.replace(`"%%VERSION%%"`, `"${version}"`);
vSource = vSource.replace(`"%%GLOBAL_HASH%%"`, `"${globalHash}"`);
versionScript.text(wrapWithLicenseComments(vSource));
doc("head").append(`<link rel="manifest" href="${assets.resolve("manifest.json")}">`);
await assets.writeUnhashed("index.html", doc.html());
}
async function buildJs(mainFile, extraFiles, importOverrides) {
// create js bundle
const plugins = [multi(), removeJsComments({comments: "none"})];
if (importOverrides) {
plugins.push(overridesAsRollupPlugin(importOverrides));
}
const bundle = await rollup({
// for fake-indexeddb, so usage for tests only doesn't put it in bundle
treeshake: {moduleSideEffects: isPathInSrcDir},
input: extraFiles.concat(mainFile),
plugins
});
const {output} = await bundle.generate({
format: 'es',
// TODO: can remove this?
name: `hydrogen`
});
const code = output[0].code;
return wrapWithLicenseComments(code);
}
async function buildJsLegacy(mainFile, extraFiles, importOverrides) {
// compile down to whatever IE 11 needs
const babelPlugin = babel.babel({
babelHelpers: 'bundled',
exclude: 'node_modules/**',
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "entry",
corejs: "3.4",
targets: "IE 11",
// we provide our own promise polyfill (es6-promise)
// with support for synchronous flushing of
// the queue for idb where needed
exclude: ["es.promise", "es.promise.all-settled", "es.promise.finally"]
}
]
]
});
const plugins = [multi(), commonjs()];
if (importOverrides) {
plugins.push(overridesAsRollupPlugin(importOverrides));
}
plugins.push(nodeResolve(), babelPlugin);
// create js bundle
const rollupConfig = {
// for fake-indexeddb, so usage for tests only doesn't put it in bundle
treeshake: {moduleSideEffects: isPathInSrcDir},
// important the extraFiles come first,
// so polyfills are available in the global scope
// if needed for the mainfile
input: extraFiles.concat(mainFile),
plugins
};
const bundle = await rollup(rollupConfig);
const {output} = await bundle.generate({
format: 'iife',
name: `hydrogen`
});
const code = output[0].code;
return wrapWithLicenseComments(code);
}
function wrapWithLicenseComments(code) {
// Add proper license comments to make GNU LibreJS accept the file
const start = '// @license magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt Apache-2.0';
const end = '// @license-end';
return `${start}\n${code}\n${end}`;
}
const NON_PRECACHED_JS = [
"hydrogen-legacy.js",
"olm_legacy.js",
"worker.js"
];
function isPreCached(asset) {
return asset.endsWith(".svg") ||
asset.endsWith(".png") ||
asset.endsWith(".css") ||
asset.endsWith(".wasm") ||
asset.endsWith(".html") ||
// most environments don't need the worker
asset.endsWith(".js") && !NON_PRECACHED_JS.includes(asset);
}
async function buildManifest(assets) {
const webManifest = JSON.parse(await fs.readFile(path.join(projectDir, "assets/manifest.json"), "utf8"));
// copy manifest icons
for (const icon of webManifest.icons) {
let iconData = await fs.readFile(path.join(projectDir, icon.src));
const iconTargetPath = path.basename(icon.src);
icon.src = await assets.write(iconTargetPath, iconData);
}
await assets.write("manifest.json", JSON.stringify(webManifest));
}
async function buildServiceWorker(swSource, version, globalHash, assets) {
const unhashedPreCachedAssets = ["index.html"];
const hashedPreCachedAssets = [];
const hashedCachedOnRequestAssets = [];
for (const [unresolved, resolved] of assets) {
if (unresolved === resolved) {
unhashedPreCachedAssets.push(resolved);
} else if (isPreCached(unresolved)) {
hashedPreCachedAssets.push(resolved);
} else {
hashedCachedOnRequestAssets.push(resolved);
}
}
const replaceArrayInSource = (name, value) => {
const newSource = swSource.replace(`${name} = []`, `${name} = ${JSON.stringify(value)}`);
if (newSource === swSource) {
throw new Error(`${name} was not found in the service worker source`);
}
return newSource;
};
const replaceStringInSource = (name, value) => {
const newSource = swSource.replace(new RegExp(`${name}\\s=\\s"[^"]*"`), `${name} = ${JSON.stringify(value)}`);
if (newSource === swSource) {
throw new Error(`${name} was not found in the service worker source`);
}
return newSource;
};
// write service worker
swSource = swSource.replace(`"%%VERSION%%"`, `"${version}"`);
swSource = swSource.replace(`"%%GLOBAL_HASH%%"`, `"${globalHash}"`);
swSource = replaceArrayInSource("UNHASHED_PRECACHED_ASSETS", unhashedPreCachedAssets);
swSource = replaceArrayInSource("HASHED_PRECACHED_ASSETS", hashedPreCachedAssets);
swSource = replaceArrayInSource("HASHED_CACHED_ON_REQUEST_ASSETS", hashedCachedOnRequestAssets);
swSource = replaceStringInSource("NOTIFICATION_BADGE_ICON", assets.resolve("icon.png"));
// service worker should not have a hashed name as it is polled by the browser for updates
await assets.writeUnhashed("sw.js", swSource);
}
async function buildCssBundles(buildFn, themes, assets, mainCssFile = null) {
if (!mainCssFile) {
mainCssFile = path.join(cssSrcDir, "main.css");
}
const bundleCss = await buildFn(mainCssFile);
await assets.write(`hydrogen.css`, bundleCss);
for (const theme of themes) {
const themeRelPath = `themes/${theme}/`;
const themeRoot = path.join(cssSrcDir, themeRelPath);
const assetUrlMapper = ({absolutePath}) => {
if (!absolutePath.startsWith(themeRoot)) {
throw new Error("resource is out of theme directory: " + absolutePath);
}
const relPath = absolutePath.substr(themeRoot.length);
const hashedDstPath = assets.resolve(path.join(themeRelPath, relPath));
if (hashedDstPath) {
return hashedDstPath.substr(themeRelPath.length);
}
};
const themeCss = await buildFn(path.join(themeRoot, `theme.css`), assetUrlMapper);
await assets.write(path.join(themeRelPath, `bundle.css`), themeCss);
}
}
// async function buildCss(entryPath, urlMapper = null) {
// const preCss = await fs.readFile(entryPath, "utf8");
// const options = [postcssImport];
// if (urlMapper) {
// options.push(postcssUrl({url: urlMapper}));
// }
// const cssBundler = postcss(options);
// const result = await cssBundler.process(preCss, {from: entryPath});
// return result.css;
// }
async function buildCssLegacy(entryPath, urlMapper = null) {
const preCss = await fs.readFile(entryPath, "utf8");
const options = [
postcssImport,
cssvariables({
preserve: (declaration) => {
return declaration.value.indexOf("var(--ios-") == 0;
}
}),
autoprefixer({overrideBrowserslist: ["IE 11"], grid: "no-autoplace"}),
flexbugsFixes()
];
if (urlMapper) {
options.push(postcssUrl({url: urlMapper}));
}
const cssBundler = postcss(options);
const result = await cssBundler.process(preCss, {from: entryPath});
return result.css;
}
async function removeDirIfExists(targetDir) {
try {
await fs.rmdir(targetDir, {recursive: true});
} catch (err) {
if (err.code !== "ENOENT") {
throw err;
}
}
}
async function copyFolder(srcRoot, dstRoot, filter, assets = null) {
assets = assets || new AssetMap(dstRoot);
const dirEnts = await fs.readdir(srcRoot, {withFileTypes: true});
for (const dirEnt of dirEnts) {
const dstPath = path.join(dstRoot, dirEnt.name);
const srcPath = path.join(srcRoot, dirEnt.name);
if (dirEnt.isDirectory()) {
await fs.mkdir(dstPath);
await copyFolder(srcPath, dstPath, filter, assets);
} else if ((dirEnt.isFile() || dirEnt.isSymbolicLink()) && (!filter || filter(srcPath))) {
const content = await fs.readFile(srcPath);
await assets.write(dstPath, content);
}
}
return assets;
}
function contentHash(str) {
var hasher = new xxhash.h32(0);
hasher.update(str);
return hasher.digest();
}
class AssetMap {
constructor(targetDir) {
// remove last / if any, so substr in create works well
this._targetDir = path.resolve(targetDir);
this._assets = new Map();
// hashes for unhashed resources so changes in these resources also contribute to the hashForAll
this._unhashedHashes = [];
}
_toRelPath(resourcePath) {
let relPath = resourcePath;
if (path.isAbsolute(resourcePath)) {
if (!resourcePath.startsWith(this._targetDir)) {
throw new Error(`absolute path ${resourcePath} that is not within target dir ${this._targetDir}`);
}
relPath = resourcePath.substr(this._targetDir.length + 1); // + 1 for the /
}
return relPath;
}
_create(resourcePath, content) {
const relPath = this._toRelPath(resourcePath);
const hash = contentHash(Buffer.from(content));
const dir = path.dirname(relPath);
const extname = path.extname(relPath);
const basename = path.basename(relPath, extname);
const dstRelPath = path.join(dir, `${basename}-${hash}${extname}`);
this._assets.set(relPath, dstRelPath);
return dstRelPath;
}
async write(resourcePath, content) {
const relPath = this._create(resourcePath, content);
const fullPath = path.join(this.directory, relPath);
if (typeof content === "string") {
await fs.writeFile(fullPath, content, "utf8");
} else {
await fs.writeFile(fullPath, content);
}
return relPath;
}
async writeUnhashed(resourcePath, content) {
const relPath = this._toRelPath(resourcePath);
this._assets.set(relPath, relPath);
const fullPath = path.join(this.directory, relPath);
if (typeof content === "string") {
await fs.writeFile(fullPath, content, "utf8");
} else {
await fs.writeFile(fullPath, content);
}
return relPath;
}
get directory() {
return this._targetDir;
}
resolve(resourcePath) {
const relPath = this._toRelPath(resourcePath);
const result = this._assets.get(relPath);
if (!result) {
throw new Error(`unknown path: ${relPath}, only know ${Array.from(this._assets.keys()).join(", ")}`);
}
return result;
}
addSubMap(assetMap) {
if (!assetMap.directory.startsWith(this.directory)) {
throw new Error(`map directory doesn't start with this directory: ${assetMap.directory} ${this.directory}`);
}
const relSubRoot = assetMap.directory.substr(this.directory.length + 1);
for (const [key, value] of assetMap._assets.entries()) {
this._assets.set(path.join(relSubRoot, key), path.join(relSubRoot, value));
}
}
[Symbol.iterator]() {
return this._assets.entries();
}
isUnhashed(relPath) {
const resolvedPath = this._assets.get(relPath);
if (!resolvedPath) {
throw new Error("Unknown asset: " + relPath);
}
return relPath === resolvedPath;
}
get size() {
return this._assets.size;
}
has(relPath) {
return this._assets.has(relPath);
}
hashForAll() {
const globalHashAssets = Array.from(this).map(([, resolved]) => resolved);
globalHashAssets.push(...this._unhashedHashes);
globalHashAssets.sort();
return contentHash(globalHashAssets.join(","));
}
addToHashForAll(resourcePath, content) {
this._unhashedHashes.push(`${resourcePath}-${contentHash(Buffer.from(content))}`);
}
}
async function readImportOverrides(filename) {
const json = await fs.readFile(filename, "utf8");
const mapping = new Map(Object.entries(JSON.parse(json)));
return {
basedir: path.dirname(path.resolve(filename))+path.sep,
mapping
};
}
function overridesAsRollupPlugin(importOverrides) {
const {mapping, basedir} = importOverrides;
return {
name: "rewrite-imports",
resolveId (source, importer) {
let file;
if (source.startsWith(path.sep)) {
file = source;
} else {
file = path.join(path.dirname(importer), source);
}
if (file.startsWith(basedir)) {
const searchPath = file.substr(basedir.length);
const replacingPath = mapping.get(searchPath);
if (replacingPath) {
console.info(`replacing ${searchPath} with ${replacingPath}`);
return path.join(basedir, replacingPath);
}
}
return null;
}
};
}
build(parameters).catch(err => console.error(err));

165
scripts/ci.sh Executable file
View File

@ -0,0 +1,165 @@
#!/bin/bash
# ci.sh: Helper script to automate deployment operations on CI/CD
# Copyright © 2022 Aravinth Manivannan <realaravinth@batsense.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set -xEeuo pipefail
#source $(pwd)/scripts/lib.sh
readonly SSH_ID_FILE=/tmp/ci-ssh-id
readonly SSH_REMOTE_NAME=origin-ssh
readonly PROJECT_ROOT=$(pwd)
match_arg() {
if [ $1 == $2 ] || [ $1 == $3 ]
then
return 0
else
return 1
fi
}
help() {
cat << EOF
USAGE: ci.sh [SUBCOMMAND]
Helper script to automate deployment operations on CI/CD
Subcommands
-c --clean cleanup secrets, SSH key and other runtime data
-i --init <SSH_PRIVATE_KEY> initialize environment, write SSH private to file
-d --deploy <PAGES-SECRET> <TARGET BRANCH> push branch to Gitea and call Pages server
-h --help print this help menu
EOF
}
# $1: SSH private key
write_ssh(){
truncate --size 0 $SSH_ID_FILE
echo "$1" > $SSH_ID_FILE
chmod 600 $SSH_ID_FILE
}
set_ssh_remote() {
http_remote_url=$(git remote get-url origin)
remote_hostname=$(echo $http_remote_url | cut -d '/' -f 3)
repository_owner=$(echo $http_remote_url | cut -d '/' -f 4)
repository_name=$(echo $http_remote_url | cut -d '/' -f 5)
ssh_remote="git@$remote_hostname:$repository_owner/$repository_name"
ssh_remote="git@git.batsense.net:mystiq/hydrogen-web.git"
git remote add $SSH_REMOTE_NAME $ssh_remote
}
clean() {
if [ -f $SSH_ID_FILE ]
then
shred $SSH_ID_FILE
rm $SSH_ID_FILE
fi
}
# $1: branch name
# $2: directory containing build assets
# $3: Author in <author-name author@example.com> format
commit_files() {
cd $PROJECT_ROOT
original_branch=$(git branch --show-current)
tmp_dir=$(mktemp -d)
cp -r $2/* $tmp_dir
if [[ -z $(git ls-remote --heads origin ${1}) ]]
then
echo "[*] Creating deployment branch $1"
git checkout --orphan $1
else
echo "[*] Deployment branch $1 exists, pulling changes from remote"
git fetch origin $1
git switch $1
fi
git rm -rf .
/bin/rm -rf *
cp -r $tmp_dir/* .
git add --all
if [ $(git status --porcelain | xargs | sed '/^$/d' | wc -l) -gt 0 ];
then
echo "[*] Repository has changed, committing changes"
git commit \
--author="$3" \
--message="new deploy: $(date --iso-8601=seconds)"
fi
git checkout $original_branch
}
# $1: Pages API secret
# $2: Deployment target branch
deploy() {
if (( "$#" < 2 ))
then
help
else
git -c core.sshCommand="/usr/bin/ssh -oStrictHostKeyChecking=no -i $SSH_ID_FILE"\
push --force $SSH_REMOTE_NAME $2
curl -vv --location --request \
POST "https://deploy.batsense.net/api/v1/update"\
--header 'Content-Type: application/json' \
--data-raw "{ \"secret\": \"$1\", \"branch\": \"$2\" }"
fi
}
if (( "$#" < 1 ))
then
help
exit -1
fi
if match_arg $1 '-i' '--init'
then
if (( "$#" < 2 ))
then
help
exit -1
fi
set_ssh_remote
write_ssh "$2"
elif match_arg $1 '-c' '--clean'
then
clean
elif match_arg $1 '-cf' '--commit-files'
then
if (( "$#" < 4 ))
then
help
exit -1
fi
commit_files $2 $3 $4
elif match_arg $1 '-d' '--deploy'
then
if (( "$#" < 3 ))
then
help
exit -1
fi
deploy $2 $3
elif match_arg $1 '-h' '--help'
then
help
else
help
fi

3
scripts/cleanup.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
# Remove icons created in .tmp
rm -rf .tmp

View File

@ -1,12 +0,0 @@
import fsRoot from "fs";
const fs = fsRoot.promises;
export async function removeDirIfExists(targetDir) {
try {
await fs.rmdir(targetDir, {recursive: true});
} catch (err) {
if (err.code !== "ENOENT") {
throw err;
}
}
}

View File

@ -1,6 +0,0 @@
git checkout gh-pages
cp -R target/* .
git add $(find . -maxdepth 1 -type f)
git add themes
git commit -m "update hydrogen"
git checkout master

View File

@ -1,6 +1,7 @@
module.exports = class Buffer {
static isBuffer(array) {return array instanceof Uint8Array;}
static from(arrayBuffer) {return arrayBuffer;}
static allocUnsafe(size) {return Buffer.alloc(size);}
static alloc(size) {return new Uint8Array(size);}
var Buffer = {
isBuffer: function(array) {return array instanceof Uint8Array;},
from: function(arrayBuffer) {return arrayBuffer;},
allocUnsafe: function(size) {return Buffer.alloc(size);},
alloc: function(size) {return new Uint8Array(size);}
};
export default Buffer;

View File

@ -1,4 +0,0 @@
// we have our own main file for this module as we need both these symbols to
// be exported, and we also don't want to auto behaviour that modifies global vars
exports.FDBFactory = require("fake-indexeddb/lib/FDBFactory.js");
exports.FDBKeyRange = require("fake-indexeddb/lib/FDBKeyRange.js");

View File

@ -1 +1,2 @@
module.exports.Buffer = require("buffer");
import Buffer from "buffer";
export {Buffer};

View File

@ -2,6 +2,9 @@ VERSION=$(jq -r ".version" package.json)
PACKAGE=hydrogen-web-$VERSION.tar.gz
yarn build
pushd target
# move config file so we don't override it
# when deploying a new version
mv config.json config.sample.json
tar -czvf ../$PACKAGE ./
popd
echo $PACKAGE

View File

@ -1,132 +0,0 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const fsRoot = require("fs");
const fs = fsRoot.promises;
const path = require("path");
const { rollup } = require('rollup');
const { fileURLToPath } = require('url');
const { dirname } = require('path');
// needed to translate commonjs modules to esm
const commonjs = require('@rollup/plugin-commonjs');
const json = require('@rollup/plugin-json');
const { nodeResolve } = require('@rollup/plugin-node-resolve');
const projectDir = path.join(__dirname, "../");
async function removeDirIfExists(targetDir) {
try {
await fs.rmdir(targetDir, {recursive: true});
} catch (err) {
if (err.code !== "ENOENT") {
throw err;
}
}
}
/** function used to resolve common-js require calls below. */
function packageIterator(request, start, defaultIterator) {
// this is just working for bs58, would need to tune it further for other dependencies
if (request === "safe-buffer") {
return [path.join(projectDir, "/scripts/package-overrides/safe-buffer")];
} else if (request === "buffer/") {
return [path.join(projectDir, "/scripts/package-overrides/buffer")];
} else {
return defaultIterator();
}
}
async function commonjsToESM(src, dst) {
// create js bundle
const bundle = await rollup({
treeshake: {moduleSideEffects: false},
input: src,
plugins: [commonjs(), json(), nodeResolve({
browser: true,
preferBuiltins: false,
customResolveOptions: {packageIterator}
})]
});
const {output} = await bundle.generate({
format: 'es'
});
const code = output[0].code;
await fs.writeFile(dst, code, "utf8");
}
async function populateLib() {
const libDir = path.join(projectDir, "lib/");
await removeDirIfExists(libDir);
await fs.mkdir(libDir);
const olmSrcDir = path.dirname(require.resolve("@matrix-org/olm"));
const olmDstDir = path.join(libDir, "olm/");
await fs.mkdir(olmDstDir);
for (const file of ["olm.js", "olm.wasm", "olm_legacy.js"]) {
await fs.copyFile(path.join(olmSrcDir, file), path.join(olmDstDir, file));
}
// transpile node-html-parser to esm
await fs.mkdir(path.join(libDir, "node-html-parser/"));
await commonjsToESM(
require.resolve('node-html-parser/dist/index.js'),
path.join(libDir, "node-html-parser/index.js")
);
// transpile another-json to esm
await fs.mkdir(path.join(libDir, "another-json/"));
await commonjsToESM(
require.resolve('another-json/another-json.js'),
path.join(libDir, "another-json/index.js")
);
// transpile bs58 to esm
await fs.mkdir(path.join(libDir, "bs58/"));
await commonjsToESM(
require.resolve('bs58/index.js'),
path.join(libDir, "bs58/index.js")
);
// transpile base64-arraybuffer to esm
await fs.mkdir(path.join(libDir, "base64-arraybuffer/"));
await commonjsToESM(
require.resolve('base64-arraybuffer/lib/base64-arraybuffer.js'),
path.join(libDir, "base64-arraybuffer/index.js")
);
// this probably should no go in here, we can just import "aes-js" from legacy-extras.js
// as that file is never loaded from a browser
// transpile aesjs to esm
await fs.mkdir(path.join(libDir, "aes-js/"));
await commonjsToESM(
require.resolve('aes-js/index.js'),
path.join(libDir, "aes-js/index.js")
);
// es6-promise is already written as an es module,
// but it does need to be babelified, and current we don't babelify
// anything in node_modules in the build script, so make a bundle that
// is conveniently not placed in node_modules rather than symlinking.
await fs.mkdir(path.join(libDir, "es6-promise/"));
await commonjsToESM(
require.resolve('es6-promise/lib/es6-promise/promise.js'),
path.join(libDir, "es6-promise/index.js")
);
// fake-indexeddb, used for tests (but unresolvable bare imports also makes the build complain)
// and might want to use it for in-memory storage too, although we probably do ts->es6 with esm
// directly rather than ts->es5->es6 as we do now. The bundle is 240K currently.
await fs.mkdir(path.join(libDir, "fake-indexeddb/"));
await commonjsToESM(
path.join(projectDir, "/scripts/package-overrides/fake-indexeddb.js"),
path.join(libDir, "fake-indexeddb/index.js")
);
}
populateLib();

View File

@ -0,0 +1,180 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const valueParser = require("postcss-value-parser");
/**
* This plugin derives new css variables from a given set of base variables.
* A derived css variable has the form --base--operation-argument; meaning that the derived
* variable has a value that is generated from the base variable "base" by applying "operation"
* with given "argument".
*
* eg: given the base variable --foo-color: #40E0D0, --foo-color--darker-20 is a css variable
* derived from foo-color by making it 20% more darker.
*
* All derived variables are added to the :root section.
*
* The actual derivation is done outside the plugin in a callback.
*/
function getValueFromAlias(alias, {aliasMap, baseVariables, resolvedMap}) {
const derivedVariable = aliasMap.get(alias);
return baseVariables.get(derivedVariable) ?? resolvedMap.get(derivedVariable);
}
function parseDeclarationValue(value) {
const parsed = valueParser(value);
const variables = [];
parsed.walk(node => {
if (node.type !== "function") {
return;
}
switch (node.value) {
case "var": {
const variable = node.nodes[0];
variables.push(variable.value);
break;
}
case "url": {
const url = node.nodes[0].value;
// resolve url with some absolute url so that we get the query params without using regex
const params = new URL(url, "file://foo/bar/").searchParams;
const primary = params.get("primary");
const secondary = params.get("secondary");
if (primary) { variables.push(primary); }
if (secondary) { variables.push(secondary); }
break;
}
}
});
return variables;
}
function resolveDerivedVariable(decl, derive, maps, isDark) {
const { baseVariables, resolvedMap } = maps;
const RE_VARIABLE_VALUE = /(?:--)?((.+)--(.+)-(.+))/;
const variableCollection = parseDeclarationValue(decl.value);
for (const variable of variableCollection) {
const matches = variable.match(RE_VARIABLE_VALUE);
if (matches) {
const [, wholeVariable, baseVariable, operation, argument] = matches;
const value = baseVariables.get(baseVariable) ?? getValueFromAlias(baseVariable, maps);
if (!value) {
throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`);
}
const derivedValue = derive(value, operation, argument, isDark);
resolvedMap.set(wholeVariable, derivedValue);
}
}
}
function extract(decl, {aliasMap, baseVariables}) {
if (decl.variable) {
// see if right side is of form "var(--foo)"
const wholeVariable = decl.value.match(/var\(--(.+)\)/)?.[1];
// remove -- from the prop
const prop = decl.prop.substring(2);
if (wholeVariable) {
aliasMap.set(prop, wholeVariable);
// Since this is an alias, we shouldn't store it in baseVariables
return;
}
baseVariables.set(prop, decl.value);
}
}
function addResolvedVariablesToRootSelector(root, {Rule, Declaration}, {resolvedMap}) {
const newRule = new Rule({ selector: ":root", source: root.source });
// Add derived css variables to :root
resolvedMap.forEach((value, key) => {
const declaration = new Declaration({prop: `--${key}`, value});
newRule.append(declaration);
});
root.append(newRule);
}
function populateMapWithDerivedVariables(map, cssFileLocation, {resolvedMap, aliasMap}) {
const location = cssFileLocation.match(/(.+)\/.+\.css/)?.[1];
const derivedVariables = [
...([...resolvedMap.keys()].filter(v => !aliasMap.has(v))),
...([...aliasMap.entries()].map(([alias, variable]) => `${alias}=${variable}`))
];
const sharedObject = map.get(location);
const output = { "derived-variables": derivedVariables };
if (sharedObject) {
Object.assign(sharedObject, output);
}
else {
map.set(location, output);
}
}
/**
* @callback derive
* @param {string} value - The base value on which an operation is applied
* @param {string} operation - The operation to be applied (eg: darker, lighter...)
* @param {string} argument - The argument for this operation
* @param {boolean} isDark - Indicates whether this theme is dark
*/
/**
*
* @param {Object} opts - Options for the plugin
* @param {derive} opts.derive - The callback which contains the logic for resolving derived variables
* @param {Map} opts.compiledVariables - A map that stores derived variables so that manifest source sections can be produced
*/
module.exports = (opts = {}) => {
const aliasMap = new Map();
const resolvedMap = new Map();
const baseVariables = new Map();
const maps = { aliasMap, resolvedMap, baseVariables };
return {
postcssPlugin: "postcss-compile-variables",
Once(root, {Rule, Declaration, result}) {
const cssFileLocation = root.source.input.from;
if (cssFileLocation.includes("type=runtime")) {
// If this is a runtime theme, don't derive variables.
return;
}
const isDark = cssFileLocation.includes("dark=true");
/*
Go through the CSS file once to extract all aliases and base variables.
We use these when resolving derived variables later.
*/
root.walkDecls(decl => extract(decl, maps));
root.walkDecls(decl => resolveDerivedVariable(decl, opts.derive, maps, isDark));
addResolvedVariablesToRootSelector(root, {Rule, Declaration}, maps);
if (opts.compiledVariables){
populateMapWithDerivedVariables(opts.compiledVariables, cssFileLocation, maps);
}
// Also produce a mapping from alias to completely resolved color
const resolvedAliasMap = new Map();
aliasMap.forEach((value, key) => {
resolvedAliasMap.set(key, resolvedMap.get(value));
});
// Publish the base-variables, derived-variables and resolved aliases to the other postcss-plugins
const combinedMap = new Map([...baseVariables, ...resolvedMap, ...resolvedAliasMap]);
result.messages.push({
type: "resolved-variable-map",
plugin: "postcss-compile-variables",
colorMap: combinedMap,
});
},
};
};
module.exports.postcss = true;

View File

@ -0,0 +1,92 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const valueParser = require("postcss-value-parser");
const resolve = require("path").resolve;
function colorsFromURL(url, colorMap) {
const params = new URL(`file://${url}`).searchParams;
const primary = params.get("primary");
if (!primary) {
return null;
}
const secondary = params.get("secondary");
const primaryColor = colorMap.get(primary);
const secondaryColor = colorMap.get(secondary);
if (!primaryColor) {
throw new Error(`Variable ${primary} not found in resolved color variables!`);
}
if (secondary && !secondaryColor) {
throw new Error(`Variable ${secondary} not found in resolved color variables!`);
}
return [primaryColor, secondaryColor];
}
function processURL(decl, replacer, colorMap, cssPath) {
const value = decl.value;
const parsed = valueParser(value);
parsed.walk(node => {
if (node.type !== "function" || node.value !== "url") {
return;
}
const urlStringNode = node.nodes[0];
const oldURL = urlStringNode.value;
const oldURLAbsolute = resolve(cssPath, oldURL);
const colors = colorsFromURL(oldURLAbsolute, colorMap);
if (!colors) {
// If no primary color is provided via url params, then this url need not be handled.
return;
}
const newURL = replacer(oldURLAbsolute.replace(/\?.+/, ""), ...colors);
if (!newURL) {
throw new Error("Replacer failed to produce a replacement URL!");
}
urlStringNode.value = newURL;
});
decl.assign({prop: decl.prop, value: parsed.toString()})
}
/* *
* @type {import('postcss').PluginCreator}
*/
module.exports = (opts = {}) => {
return {
postcssPlugin: "postcss-url-to-variable",
Once(root, {result}) {
const cssFileLocation = root.source.input.from;
if (cssFileLocation.includes("type=runtime")) {
// If this is a runtime theme, don't process urls.
return;
}
/*
postcss-compile-variables should have sent the list of resolved colours down via results
*/
const {colorMap} = result.messages.find(m => m.type === "resolved-variable-map");
if (!colorMap) {
throw new Error("Postcss results do not contain resolved colors!");
}
/*
Go through each declaration and if it contains an URL, replace the url with the result
of running replacer(url)
*/
const cssPath = root.source?.input.file.replace(/[^/]*$/, "");
root.walkDecls(decl => processURL(decl, opts.replacer, colorMap, cssPath));
},
};
};
module.exports.postcss = true;

View File

@ -0,0 +1,97 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const valueParser = require("postcss-value-parser");
/**
* This plugin extracts content inside url() into css variables and adds the variables to the root section.
* This plugin is used in conjunction with css-url-processor plugin to colorize svg icons.
*/
const idToPrepend = "icon-url";
function findAndReplaceUrl(decl, urlVariables, counter) {
const value = decl.value;
const parsed = valueParser(value);
parsed.walk(node => {
if (node.type !== "function" || node.value !== "url") {
return;
}
const url = node.nodes[0].value;
if (!url.match(/\.svg\?primary=.+/)) {
return;
}
const count = counter.next().value;
const variableName = `${idToPrepend}-${count}`;
urlVariables.set(variableName, url);
node.value = "var";
node.nodes = [{ type: "word", value: `--${variableName}` }];
});
decl.assign({prop: decl.prop, value: parsed.toString()})
}
function addResolvedVariablesToRootSelector(root, { Rule, Declaration }, urlVariables) {
const newRule = new Rule({ selector: ":root", source: root.source });
// Add derived css variables to :root
urlVariables.forEach((value, key) => {
const declaration = new Declaration({ prop: `--${key}`, value: `url("${value}")`});
newRule.append(declaration);
});
root.append(newRule);
}
function populateMapWithIcons(map, cssFileLocation, urlVariables) {
const location = cssFileLocation.match(/(.+)\/.+\.css/)?.[1];
const sharedObject = map.get(location);
const output = {"icon": Object.fromEntries(urlVariables)};
if (sharedObject) {
Object.assign(sharedObject, output);
}
else {
map.set(location, output);
}
}
function *createCounter() {
for (let i = 0; ; ++i) {
yield i;
}
}
/* *
* @type {import('postcss').PluginCreator}
*/
module.exports = (opts = {}) => {
return {
postcssPlugin: "postcss-url-to-variable",
Once(root, { Rule, Declaration }) {
const urlVariables = new Map();
const counter = createCounter();
root.walkDecls(decl => findAndReplaceUrl(decl, urlVariables, counter));
const cssFileLocation = root.source.input.from;
if (urlVariables.size && !cssFileLocation.includes("type=runtime")) {
addResolvedVariablesToRootSelector(root, { Rule, Declaration }, urlVariables);
}
if (opts.compiledVariables){
const cssFileLocation = root.source.input.from;
populateMapWithIcons(opts.compiledVariables, cssFileLocation, urlVariables);
}
},
};
};
module.exports.postcss = true;

View File

@ -0,0 +1,51 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {readFileSync, mkdirSync, writeFileSync} from "fs";
import {resolve} from "path";
import {h32} from "xxhashjs";
import {getColoredSvgString} from "../../src/platform/web/theming/shared/svg-colorizer.mjs";
function createHash(content) {
const hasher = new h32(0);
hasher.update(content);
return hasher.digest();
}
/**
* Builds a new svg with the colors replaced and returns its location.
* @param {string} svgLocation The location of the input svg file
* @param {string} primaryColor Primary color for the new svg
* @param {string} secondaryColor Secondary color for the new svg
*/
export function buildColorizedSVG(svgLocation, primaryColor, secondaryColor) {
const svgCode = readFileSync(svgLocation, { encoding: "utf8"});
const coloredSVGCode = getColoredSvgString(svgCode, primaryColor, secondaryColor);
const fileName = svgLocation.match(/.+[/\\](.+\.svg)/)[1];
const outputName = `${fileName.substring(0, fileName.length - 4)}-${createHash(coloredSVGCode)}.svg`;
const outputPath = resolve(__dirname, "./.tmp");
try {
mkdirSync(outputPath);
}
catch (e) {
if (e.code !== "EEXIST") {
throw e;
}
}
const outputFile = `${outputPath}/${outputName}`;
writeFileSync(outputFile, coloredSVGCode);
return outputFile;
}

View File

@ -14,16 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {LoginMethod} from "./LoginMethod.js";
const postcss = require("postcss");
export class PasswordLoginMethod extends LoginMethod {
constructor(options) {
super(options);
this.username = options.username;
this.password = options.password;
}
async login(hsApi, deviceName, log) {
return await hsApi.passwordLogin(this.username, this.password, deviceName, {log}).response();
}
module.exports.createTestRunner = function (plugin) {
return async function run(input, output, opts = {}, assert) {
let result = await postcss([plugin(opts)]).process(input, { from: undefined, });
assert.strictEqual(
result.css.replaceAll(/\s/g, ""),
output.replaceAll(/\s/g, "")
);
assert.strictEqual(result.warnings().length, 0);
};
}

View File

@ -0,0 +1,156 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const offColor = require("off-color").offColor;
const postcss = require("postcss");
const plugin = require("../css-compile-variables");
const derive = require("../color").derive;
const run = require("./common").createTestRunner(plugin);
module.exports.tests = function tests() {
return {
"derived variables are resolved": async (assert) => {
const inputCSS = `
:root {
--foo-color: #ff0;
}
div {
background-color: var(--foo-color--lighter-50);
}`;
const transformedColor = offColor("#ff0").lighten(0.5);
const outputCSS =
inputCSS +
`
:root {
--foo-color--lighter-50: ${transformedColor.hex()};
}
`;
await run( inputCSS, outputCSS, {derive}, assert);
},
"derived variables work with alias": async (assert) => {
const inputCSS = `
:root {
--icon-color: #fff;
}
div {
background: var(--icon-color--darker-20);
--my-alias: var(--icon-color--darker-20);
color: var(--my-alias--lighter-15);
}`;
const colorDarker = offColor("#fff").darken(0.2).hex();
const aliasLighter = offColor(colorDarker).lighten(0.15).hex();
const outputCSS = inputCSS + `:root {
--icon-color--darker-20: ${colorDarker};
--my-alias--lighter-15: ${aliasLighter};
}
`;
await run(inputCSS, outputCSS, {derive}, assert);
},
"derived variable throws if base not present in config": async (assert) => {
const css = `:root {
color: var(--icon-color--darker-20);
}`;
assert.rejects(async () => await postcss([plugin({ variables: {} })]).process(css, { from: undefined, }));
},
"multiple derived variable in single declaration is parsed correctly": async (assert) => {
const inputCSS = `
:root {
--foo-color: #ff0;
}
div {
background-color: linear-gradient(var(--foo-color--lighter-50), var(--foo-color--darker-20));
}`;
const transformedColor1 = offColor("#ff0").lighten(0.5);
const transformedColor2 = offColor("#ff0").darken(0.2);
const outputCSS =
inputCSS +
`
:root {
--foo-color--lighter-50: ${transformedColor1.hex()};
--foo-color--darker-20: ${transformedColor2.hex()};
}
`;
await run( inputCSS, outputCSS, {derive}, assert);
},
"multiple aliased-derived variable in single declaration is parsed correctly": async (assert) => {
const inputCSS = `
:root {
--foo-color: #ff0;
}
div {
--my-alias: var(--foo-color);
background-color: linear-gradient(var(--my-alias--lighter-50), var(--my-alias--darker-20));
}`;
const transformedColor1 = offColor("#ff0").lighten(0.5);
const transformedColor2 = offColor("#ff0").darken(0.2);
const outputCSS =
inputCSS +
`
:root {
--my-alias--lighter-50: ${transformedColor1.hex()};
--my-alias--darker-20: ${transformedColor2.hex()};
}
`;
await run( inputCSS, outputCSS, {derive}, assert);
},
"compiledVariables map is populated": async (assert) => {
const compiledVariables = new Map();
const inputCSS = `
:root {
--icon-color: #fff;
}
div {
background: var(--icon-color--darker-20);
--my-alias: var(--icon-color--darker-20);
color: var(--my-alias--lighter-15);
}`;
await postcss([plugin({ derive, compiledVariables })]).process(inputCSS, { from: "/foo/bar/test.css", });
const actualArray = compiledVariables.get("/foo/bar")["derived-variables"];
const expectedArray = ["icon-color--darker-20", "my-alias=icon-color--darker-20", "my-alias--lighter-15"];
assert.deepStrictEqual(actualArray.sort(), expectedArray.sort());
},
"derived variable are supported in urls": async (assert) => {
const inputCSS = `
:root {
--foo-color: #ff0;
}
div {
background-color: var(--foo-color--lighter-50);
background: url("./foo/bar/icon.svg?primary=foo-color--darker-5");
}
a {
background: url("foo/bar/icon.svg");
}`;
const transformedColorLighter = offColor("#ff0").lighten(0.5);
const transformedColorDarker = offColor("#ff0").darken(0.05);
const outputCSS =
inputCSS +
`
:root {
--foo-color--lighter-50: ${transformedColorLighter.hex()};
--foo-color--darker-5: ${transformedColorDarker.hex()};
}
`;
await run( inputCSS, outputCSS, {derive}, assert);
}
};
};

View File

@ -0,0 +1,71 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const plugin = require("../css-url-to-variables");
const run = require("./common").createTestRunner(plugin);
const postcss = require("postcss");
module.exports.tests = function tests() {
return {
"url is replaced with variable": async (assert) => {
const inputCSS = `div {
background: no-repeat center/80% url("../img/image.svg?primary=main-color--darker-20");
}
button {
background: url("/home/foo/bar/cool.svg?primary=blue&secondary=green");
}`;
const outputCSS =
`div {
background: no-repeat center/80% var(--icon-url-0);
}
button {
background: var(--icon-url-1);
}`+
`
:root {
--icon-url-0: url("../img/image.svg?primary=main-color--darker-20");
--icon-url-1: url("/home/foo/bar/cool.svg?primary=blue&secondary=green");
}
`;
await run(inputCSS, outputCSS, { }, assert);
},
"non svg urls without query params are not replaced": async (assert) => {
const inputCSS = `div {
background: no-repeat url("./img/foo/bar/image.png");
}`;
await run(inputCSS, inputCSS, {}, assert);
},
"map is populated with icons": async (assert) => {
const compiledVariables = new Map();
compiledVariables.set("/foo/bar", { "derived-variables": ["background-color--darker-20", "accent-color--lighter-15"] });
const inputCSS = `div {
background: no-repeat center/80% url("../img/image.svg?primary=main-color--darker-20");
}
button {
background: url("/home/foo/bar/cool.svg?primary=blue&secondary=green");
}`;
const expectedObject = {
"icon-url-0": "../img/image.svg?primary=main-color--darker-20",
"icon-url-1": "/home/foo/bar/cool.svg?primary=blue&secondary=green",
};
await postcss([plugin({compiledVariables})]).process(inputCSS, { from: "/foo/bar/test.css", });
const sharedVariable = compiledVariables.get("/foo/bar");
assert.deepEqual(["background-color--darker-20", "accent-color--lighter-15"], sharedVariable["derived-variables"]);
assert.deepEqual(expectedObject, sharedVariable["icon"]);
}
};
};

View File

@ -1,3 +1,4 @@
set -e
if [ -z "$1" ]; then
echo "provide a new version, current version is $(jq '.version' package.json)"
exit 1

View File

@ -0,0 +1,19 @@
{
"name": "hydrogen-view-sdk",
"description": "Embeddable matrix client library, including view components",
"version": "0.1.0",
"main": "./lib-build/hydrogen.cjs.js",
"exports": {
".": {
"import": "./lib-build/hydrogen.es.js",
"require": "./lib-build/hydrogen.cjs.js"
},
"./paths/vite": "./paths/vite.js",
"./style.css": "./asset-build/assets/theme-element-light.css",
"./theme-element-light.css": "./asset-build/assets/theme-element-light.css",
"./theme-element-dark.css": "./asset-build/assets/theme-element-dark.css",
"./main.js": "./asset-build/assets/main.js",
"./download-sandbox.html": "./asset-build/assets/download-sandbox.html",
"./assets/*": "./asset-build/assets/*"
}
}

25
scripts/sdk/build.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
# Exit whenever one of the commands fail with a non-zero exit code
set -e
set -o pipefail
# Enable extended globs so we can use the `!(filename)` glob syntax
shopt -s extglob
# Only remove the directory contents instead of the whole directory to maintain
# the `npm link`/`yarn link` symlink
rm -rf target/*
yarn run vite build -c vite.sdk-assets-config.js
yarn run vite build -c vite.sdk-lib-config.js
yarn tsc -p tsconfig-declaration.json
./scripts/sdk/create-manifest.js ./target/package.json
mkdir target/paths
# this doesn't work, the ?url imports need to be in the consuming project, so disable for now
# ./scripts/sdk/transform-paths.js ./src/platform/web/sdk/paths/vite.js ./target/paths/vite.js
cp doc/SDK.md target/README.md
pushd target/asset-build
rm index.html
popd
pushd target/asset-build/assets
# Remove all `*.wasm` and `*.js` files except for `main.js`
rm !(main).js *.wasm
popd

23
scripts/sdk/create-manifest.js Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env node
const fs = require("fs");
const appManifest = require("../../package.json");
const baseSDKManifest = require("./base-manifest.json");
/*
Need to leave typescript type definitions out until the
typescript conversion is complete and all imports in the d.ts files
exists.
```
"types": "types/lib.d.ts"
```
*/
const mergeOptions = require('merge-options');
const manifestExtension = {
devDependencies: undefined,
scripts: undefined,
};
const manifest = mergeOptions(appManifest, baseSDKManifest, manifestExtension);
const json = JSON.stringify(manifest, undefined, 2);
const outFile = process.argv[2];
fs.writeFileSync(outFile, json, {encoding: "utf8"});

3
scripts/sdk/test/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
dist
yarn.lock

2
scripts/sdk/test/deps.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
// Keep TypeScripts from complaining about hydrogen-view-sdk not having types yet
declare module "hydrogen-view-sdk";

View File

@ -0,0 +1,21 @@
import * as hydrogenViewSdk from "hydrogen-view-sdk";
import downloadSandboxPath from 'hydrogen-view-sdk/download-sandbox.html?url';
import workerPath from 'hydrogen-view-sdk/main.js?url';
import olmWasmPath from '@matrix-org/olm/olm.wasm?url';
import olmJsPath from '@matrix-org/olm/olm.js?url';
import olmLegacyJsPath from '@matrix-org/olm/olm_legacy.js?url';
const assetPaths = {
downloadSandbox: downloadSandboxPath,
worker: workerPath,
olm: {
wasm: olmWasmPath,
legacyBundle: olmLegacyJsPath,
wasmBundle: olmJsPath
}
};
import "hydrogen-view-sdk/assets/theme-element-light.css";
console.log('hydrogenViewSdk', hydrogenViewSdk);
console.log('assetPaths', assetPaths);
console.log('Entry ESM works ✅');

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app" class="hydrogen"></div>
<script type="module" src="./esm-entry.ts"></script>
</body>
</html>

View File

@ -0,0 +1,8 @@
{
"name": "test-sdk",
"version": "0.0.0",
"description": "",
"dependencies": {
"hydrogen-view-sdk": "link:../../../target"
}
}

View File

@ -0,0 +1,13 @@
// Make sure the SDK can be used in a CommonJS environment.
// Usage: node scripts/sdk/test/test-sdk-in-commonjs-env.js
const hydrogenViewSdk = require('hydrogen-view-sdk');
// Test that the "exports" are available:
// Worker
require.resolve('hydrogen-view-sdk/main.js');
// Styles
require.resolve('hydrogen-view-sdk/assets/theme-element-light.css');
// Can access files in the assets/* directory
require.resolve('hydrogen-view-sdk/assets/main.js');
console.log('SDK works in CommonJS ✅');

View File

@ -0,0 +1,19 @@
const { resolve } = require('path');
const { build } = require('vite');
async function main() {
await build({
outDir: './dist',
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html')
}
}
}
});
console.log('SDK works in Vite build ✅');
}
main();

36
scripts/sdk/transform-paths.js Executable file
View File

@ -0,0 +1,36 @@
#!/usr/bin/env node
/**
This script transforms the string literals in the sdk path files to adjust paths
from what they are at development time to what they will be in the sdk package.
It does this by looking in all string literals in the paths file and looking for file names
that we expect and need replacing (as they are bundled with the sdk).
Usage: ./transform-paths.js <input file> <output file>
*/
const acorn = require("acorn");
const walk = require("acorn-walk")
const escodegen = require("escodegen");
const fs = require("fs");
const code = fs.readFileSync(process.argv[2], {encoding: "utf8"});
const ast = acorn.parse(code, {ecmaVersion: "13", sourceType: "module"});
function changePrefix(value, file, newPrefix = "") {
const idx = value.indexOf(file);
if (idx !== -1) {
return newPrefix + value.substr(idx);
}
return value;
}
walk.simple(ast, {
Literal(node) {
node.value = changePrefix(node.value, "download-sandbox.html", "../");
node.value = changePrefix(node.value, "main.js", "../");
}
});
const transformedCode = escodegen.generate(ast);
fs.writeFileSync(process.argv[3], transformedCode, {encoding: "utf8"})

View File

@ -1,43 +0,0 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const finalhandler = require('finalhandler')
const http = require('http')
const serveStatic = require('serve-static')
const path = require('path');
// Serve up parent directory with cache disabled
const serve = serveStatic(
path.resolve(__dirname, "../"),
{
etag: false,
setHeaders: res => {
res.setHeader("Pragma", "no-cache");
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
res.setHeader("Expires", "Wed, 21 Oct 2015 07:28:00 GMT");
},
index: ['index.html', 'index.htm']
}
);
// Create server
const server = http.createServer(function onRequest (req, res) {
console.log(req.method, req.url);
serve(req, res, finalhandler(req, res))
});
// Listen
server.listen(3000);

View File

@ -0,0 +1,5 @@
#!/bin/sh
cp scripts/test-derived-theme/theme.json target/assets/theme-customer.json
cat target/config.json | jq '.themeManifests += ["assets/theme-customer.json"]' | cat > target/config.temp.json
rm target/config.json
mv target/config.temp.json target/config.json

View File

@ -0,0 +1,51 @@
{
"name": "Customer",
"extends": "element",
"id": "customer",
"values": {
"variants": {
"dark": {
"dark": true,
"default": true,
"name": "Dark",
"variables": {
"background-color-primary": "#21262b",
"background-color-secondary": "#2D3239",
"text-color": "#fff",
"accent-color": "#F03F5B",
"error-color": "#FF4B55",
"fixed-white": "#fff",
"room-badge": "#61708b",
"link-color": "#238cf5"
}
},
"light": {
"default": true,
"name": "Dark",
"variables": {
"background-color-primary": "#21262b",
"background-color-secondary": "#2D3239",
"text-color": "#fff",
"accent-color": "#F03F5B",
"error-color": "#FF4B55",
"fixed-white": "#fff",
"room-badge": "#61708b",
"link-color": "#238cf5"
}
},
"red": {
"name": "Gruvbox",
"variables": {
"background-color-primary": "#282828",
"background-color-secondary": "#3c3836",
"text-color": "#fbf1c7",
"accent-color": "#8ec07c",
"error-color": "#fb4934",
"fixed-white": "#fff",
"room-badge": "#cc241d",
"link-color": "#fe8019"
}
}
}
}
}

View File

@ -1,38 +0,0 @@
// Snowpack Configuration File
// See all supported options: https://www.snowpack.dev/reference/configuration
/** @type {import("snowpack").SnowpackUserConfig } */
module.exports = {
mount: {
// More specific paths before less specific paths (if they overlap)
"src/platform/web/docroot": "/",
"src": "/src",
"lib": {url: "/lib", static: true },
"assets": "/assets",
/* ... */
},
exclude: [
/* Avoid scanning scripts which use dev-dependencies and pull in babel, rollup, etc. */
'**/node_modules/**/*',
'**/scripts/**',
'**/target/**',
'**/prototypes/**',
'**/src/matrix/storage/memory/**',
'**/src/platform/web/legacy-polyfill.js',
'**/src/platform/web/worker/polyfill.js'
],
plugins: [
/* ... */
],
packageOptions: {
/* ... */
},
devOptions: {
open: "none",
hmr: false,
/* ... */
},
buildOptions: {
/* ... */
},
};

View File

@ -0,0 +1,136 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "./ViewModel";
import {KeyType} from "../matrix/ssss/index";
import {Status} from "./session/settings/KeyBackupViewModel.js";
export class AccountSetupViewModel extends ViewModel {
constructor(options) {
super(options);
this._accountSetup = options.accountSetup;
this._dehydratedDevice = undefined;
this._decryptDehydratedDeviceViewModel = undefined;
if (this._accountSetup.encryptedDehydratedDevice) {
this._decryptDehydratedDeviceViewModel = new DecryptDehydratedDeviceViewModel(this, dehydratedDevice => {
this._dehydratedDevice = dehydratedDevice;
this._decryptDehydratedDeviceViewModel = undefined;
this.emitChange("deviceDecrypted");
});
}
}
get decryptDehydratedDeviceViewModel() {
return this._decryptDehydratedDeviceViewModel;
}
get deviceDecrypted() {
return !!this._dehydratedDevice;
}
get dehydratedDeviceId() {
return this._accountSetup.encryptedDehydratedDevice.deviceId;
}
finish() {
this._accountSetup.finish(this._dehydratedDevice);
}
}
// this vm adopts the same shape as KeyBackupViewModel so the same view can be reused.
class DecryptDehydratedDeviceViewModel extends ViewModel {
constructor(accountSetupViewModel, decryptedCallback) {
super(accountSetupViewModel.options);
this._accountSetupViewModel = accountSetupViewModel;
this._isBusy = false;
this._status = Status.SetupKey;
this._error = undefined;
this._decryptedCallback = decryptedCallback;
}
get decryptAction() {
return this.i18n`Restore`;
}
get purpose() {
return this.i18n`claim your dehydrated device`;
}
get offerDehydratedDeviceSetup() {
return false;
}
get dehydratedDeviceId() {
return this._accountSetupViewModel._dehydratedDevice?.deviceId;
}
get isBusy() {
return this._isBusy;
}
get backupVersion() { return 0; }
get status() {
return this._status;
}
get error() {
return this._error?.message;
}
showPhraseSetup() {
if (this._status === Status.SetupKey) {
this._status = Status.SetupPhrase;
this.emitChange("status");
}
}
showKeySetup() {
if (this._status === Status.SetupPhrase) {
this._status = Status.SetupKey;
this.emitChange("status");
}
}
async _enterCredentials(keyType, credential) {
if (credential) {
try {
this._isBusy = true;
this.emitChange("isBusy");
const {encryptedDehydratedDevice} = this._accountSetupViewModel._accountSetup;
const dehydratedDevice = await encryptedDehydratedDevice.decrypt(keyType, credential);
this._decryptedCallback(dehydratedDevice);
} catch (err) {
console.error(err);
this._error = err;
this.emitChange("error");
} finally {
this._isBusy = false;
this.emitChange("");
}
}
}
enterSecurityPhrase(passphrase) {
this._enterCredentials(KeyType.Passphrase, passphrase);
}
enterSecurityKey(securityKey) {
this._enterCredentials(KeyType.RecoveryKey, securityKey);
}
disable() {}
}

View File

@ -0,0 +1,71 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {Options as BaseOptions, ViewModel} from "./ViewModel";
import {Client} from "../matrix/Client.js";
import {SegmentType} from "./navigation/index";
type Options = { sessionId: string; } & BaseOptions;
export class LogoutViewModel extends ViewModel<SegmentType, Options> {
private _sessionId: string;
private _busy: boolean;
private _showConfirm: boolean;
private _error?: Error;
constructor(options: Options) {
super(options);
this._sessionId = options.sessionId;
this._busy = false;
this._showConfirm = true;
this._error = undefined;
}
get showConfirm(): boolean {
return this._showConfirm;
}
get busy(): boolean {
return this._busy;
}
get cancelUrl(): string | undefined {
return this.urlCreator.urlForSegment("session", true);
}
async logout(): Promise<void> {
this._busy = true;
this._showConfirm = false;
this.emitChange("busy");
try {
const client = new Client(this.platform);
await client.startLogout(this._sessionId);
this.navigation.push("session", true);
} catch (err) {
this._error = err;
this._busy = false;
this.emitChange("busy");
}
}
get status(): string {
if (this._error) {
return this.i18n`Could not log out of device: ${this._error.message}`;
} else {
return this.i18n`Logging out… Please don't close the app.`;
}
}
}

View File

@ -14,22 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {Client} from "../matrix/Client.js";
import {SessionViewModel} from "./session/SessionViewModel.js";
import {SessionLoadViewModel} from "./SessionLoadViewModel.js";
import {LoginViewModel} from "./login/LoginViewModel.js";
import {LoginViewModel} from "./login/LoginViewModel";
import {LogoutViewModel} from "./LogoutViewModel";
import {SessionPickerViewModel} from "./SessionPickerViewModel.js";
import {ViewModel} from "./ViewModel.js";
import {ViewModel} from "./ViewModel";
export class RootViewModel extends ViewModel {
constructor(options) {
super(options);
this._createSessionContainer = options.createSessionContainer;
this._error = null;
this._sessionPickerViewModel = null;
this._sessionLoadViewModel = null;
this._loginViewModel = null;
this._logoutViewModel = null;
this._sessionViewModel = null;
this._pendingSessionContainer = null;
this._pendingClient = null;
}
async load() {
@ -40,29 +42,34 @@ export class RootViewModel extends ViewModel {
}
async _applyNavigation(shouldRestoreLastUrl) {
const isLogin = this.navigation.path.get("login")
const isLogin = this.navigation.path.get("login");
const logoutSessionId = this.navigation.path.get("logout")?.value;
const sessionId = this.navigation.path.get("session")?.value;
const loginToken = this.navigation.path.get("sso")?.value;
if (isLogin) {
if (this.activeSection !== "login") {
this._showLogin();
}
} else if (logoutSessionId) {
if (this.activeSection !== "logout") {
this._showLogout(logoutSessionId);
}
} else if (sessionId === true) {
if (this.activeSection !== "picker") {
this._showPicker();
}
} else if (sessionId) {
if (!this._sessionViewModel || this._sessionViewModel.id !== sessionId) {
// see _showLogin for where _pendingSessionContainer comes from
if (this._pendingSessionContainer && this._pendingSessionContainer.sessionId === sessionId) {
const sessionContainer = this._pendingSessionContainer;
this._pendingSessionContainer = null;
this._showSession(sessionContainer);
// see _showLogin for where _pendingClient comes from
if (this._pendingClient && this._pendingClient.sessionId === sessionId) {
const client = this._pendingClient;
this._pendingClient = null;
this._showSession(client);
} else {
// this should never happen, but we want to be sure not to leak it
if (this._pendingSessionContainer) {
this._pendingSessionContainer.dispose();
this._pendingSessionContainer = null;
if (this._pendingClient) {
this._pendingClient.dispose();
this._pendingClient = null;
}
this._showSessionLoader(sessionId);
}
@ -106,38 +113,43 @@ export class RootViewModel extends ViewModel {
this._setSection(() => {
this._loginViewModel = new LoginViewModel(this.childOptions({
defaultHomeserver: this.platform.config["defaultHomeServer"],
createSessionContainer: this._createSessionContainer,
ready: sessionContainer => {
ready: client => {
// we don't want to load the session container again,
// but we also want the change of screen to go through the navigation
// so we store the session container in a temporary variable that will be
// consumed by _applyNavigation, triggered by the navigation change
//
//
// Also, we should not call _setSection before the navigation is in the correct state,
// as url creation (e.g. in RoomTileViewModel)
// won't be using the correct navigation base path.
this._pendingSessionContainer = sessionContainer;
this.navigation.push("session", sessionContainer.sessionId);
this._pendingClient = client;
this.navigation.push("session", client.sessionId);
},
loginToken
}));
});
}
_showSession(sessionContainer) {
_showLogout(sessionId) {
this._setSection(() => {
this._sessionViewModel = new SessionViewModel(this.childOptions({sessionContainer}));
this._logoutViewModel = new LogoutViewModel(this.childOptions({sessionId}));
});
}
_showSession(client) {
this._setSection(() => {
this._sessionViewModel = new SessionViewModel(this.childOptions({client}));
this._sessionViewModel.start();
});
}
_showSessionLoader(sessionId) {
const sessionContainer = this._createSessionContainer();
sessionContainer.startWithExistingSession(sessionId);
const client = new Client(this.platform);
client.startWithExistingSession(sessionId);
this._setSection(() => {
this._sessionLoadViewModel = new SessionLoadViewModel(this.childOptions({
sessionContainer,
ready: sessionContainer => this._showSession(sessionContainer)
client,
ready: client => this._showSession(client)
}));
this._sessionLoadViewModel.start();
});
@ -150,6 +162,8 @@ export class RootViewModel extends ViewModel {
return "session";
} else if (this._loginViewModel) {
return "login";
} else if (this._logoutViewModel) {
return "logout";
} else if (this._sessionPickerViewModel) {
return "picker";
} else if (this._sessionLoadViewModel) {
@ -165,12 +179,14 @@ export class RootViewModel extends ViewModel {
this._sessionPickerViewModel = this.disposeTracked(this._sessionPickerViewModel);
this._sessionLoadViewModel = this.disposeTracked(this._sessionLoadViewModel);
this._loginViewModel = this.disposeTracked(this._loginViewModel);
this._logoutViewModel = this.disposeTracked(this._logoutViewModel);
this._sessionViewModel = this.disposeTracked(this._sessionViewModel);
// now set it again
setter();
this._sessionPickerViewModel && this.track(this._sessionPickerViewModel);
this._sessionLoadViewModel && this.track(this._sessionLoadViewModel);
this._loginViewModel && this.track(this._loginViewModel);
this._logoutViewModel && this.track(this._logoutViewModel);
this._sessionViewModel && this.track(this._sessionViewModel);
this.emitChange("activeSection");
}
@ -178,6 +194,7 @@ export class RootViewModel extends ViewModel {
get error() { return this._error; }
get sessionViewModel() { return this._sessionViewModel; }
get loginViewModel() { return this._loginViewModel; }
get logoutViewModel() { return this._logoutViewModel; }
get sessionPickerViewModel() { return this._sessionPickerViewModel; }
get sessionLoadViewModel() { return this._sessionLoadViewModel; }
}

View File

@ -14,21 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {LoadStatus} from "../matrix/SessionContainer.js";
import {AccountSetupViewModel} from "./AccountSetupViewModel.js";
import {LoadStatus} from "../matrix/Client.js";
import {SyncStatus} from "../matrix/Sync.js";
import {ViewModel} from "./ViewModel.js";
import {ViewModel} from "./ViewModel";
export class SessionLoadViewModel extends ViewModel {
constructor(options) {
super(options);
const {sessionContainer, ready, homeserver, deleteSessionOnCancel} = options;
this._sessionContainer = sessionContainer;
const {client, ready, homeserver, deleteSessionOnCancel} = options;
this._client = client;
this._ready = ready;
this._homeserver = homeserver;
this._deleteSessionOnCancel = deleteSessionOnCancel;
this._loading = false;
this._error = null;
this.backUrl = this.urlCreator.urlForSegment("session", true);
this._accountSetupViewModel = undefined;
}
async start() {
@ -38,11 +41,16 @@ export class SessionLoadViewModel extends ViewModel {
try {
this._loading = true;
this.emitChange("loading");
this._waitHandle = this._sessionContainer.loadStatus.waitFor(s => {
this._waitHandle = this._client.loadStatus.waitFor(s => {
if (s === LoadStatus.AccountSetup) {
this._accountSetupViewModel = new AccountSetupViewModel(this.childOptions({accountSetup: this._client.accountSetup}));
} else {
this._accountSetupViewModel = undefined;
}
this.emitChange("loadLabel");
// wait for initial sync, but not catchup sync
const isCatchupSync = s === LoadStatus.FirstSync &&
this._sessionContainer.sync.status.get() === SyncStatus.CatchupSync;
this._client.sync.status.get() === SyncStatus.CatchupSync;
return isCatchupSync ||
s === LoadStatus.LoginFailed ||
s === LoadStatus.Error ||
@ -59,15 +67,15 @@ export class SessionLoadViewModel extends ViewModel {
// much like we will once you are in the app. Probably a good idea
// did it finish or get stuck at LoginFailed or Error?
const loadStatus = this._sessionContainer.loadStatus.get();
const loadError = this._sessionContainer.loadError;
const loadStatus = this._client.loadStatus.get();
const loadError = this._client.loadError;
if (loadStatus === LoadStatus.FirstSync || loadStatus === LoadStatus.Ready) {
const sessionContainer = this._sessionContainer;
const client = this._client;
// session container is ready,
// don't dispose it anymore when
// we get disposed
this._sessionContainer = null;
this._ready(sessionContainer);
this._client = null;
this._ready(client);
}
if (loadError) {
console.error("session load error", loadError);
@ -77,16 +85,16 @@ export class SessionLoadViewModel extends ViewModel {
console.error("error thrown during session load", err.stack);
} finally {
this._loading = false;
// loadLabel in case of sc.loadError also gets updated through this
// loadLabel in case of client.loadError also gets updated through this
this.emitChange("loading");
}
}
dispose() {
if (this._sessionContainer) {
this._sessionContainer.dispose();
this._sessionContainer = null;
if (this._client) {
this._client.dispose();
this._client = null;
}
if (this._waitHandle) {
// rejects with AbortError
@ -97,20 +105,27 @@ export class SessionLoadViewModel extends ViewModel {
// to show a spinner or not
get loading() {
const client = this._client;
if (client && client.loadStatus.get() === LoadStatus.AccountSetup) {
return false;
}
return this._loading;
}
get loadLabel() {
const sc = this._sessionContainer;
const error = this._error || (sc && sc.loadError);
if (error || (sc && sc.loadStatus.get() === LoadStatus.Error)) {
const client = this._client;
const error = this._getError();
if (error || (client && client.loadStatus.get() === LoadStatus.Error)) {
return `Something went wrong: ${error && error.message}.`;
}
// Statuses related to login are handled by respective login view models
if (sc) {
switch (sc.loadStatus.get()) {
if (client) {
switch (client.loadStatus.get()) {
case LoadStatus.QueryAccount:
return `Querying account encryption setup…`;
case LoadStatus.AccountSetup:
return ""; // we'll show a header ing AccountSetupView
case LoadStatus.SessionSetup:
return `Setting up your encryption keys…`;
case LoadStatus.Loading:
@ -118,10 +133,32 @@ export class SessionLoadViewModel extends ViewModel {
case LoadStatus.FirstSync:
return `Getting your conversations from the server…`;
default:
return this._sessionContainer.loadStatus.get();
return this._client.loadStatus.get();
}
}
return `Preparing…`;
}
_getError() {
return this._error || this._client?.loadError;
}
get hasError() {
return !!this._getError();
}
async exportLogs() {
const logExport = await this.logger.export();
this.platform.saveFileAs(logExport.asBlob(), `hydrogen-logs-${this.platform.clock.now()}.json`);
}
async logout() {
await this._client.logout();
this.navigation.push("session", true);
}
get accountSetupViewModel() {
return this._accountSetupViewModel;
}
}

View File

@ -15,8 +15,8 @@ limitations under the License.
*/
import {SortedArray} from "../observable/index.js";
import {ViewModel} from "./ViewModel.js";
import {avatarInitials, getIdentifierColorNumber} from "./avatar.js";
import {ViewModel} from "./ViewModel";
import {avatarInitials, getIdentifierColorNumber} from "./avatar";
class SessionItemViewModel extends ViewModel {
constructor(options, pickerVM) {
@ -33,44 +33,6 @@ class SessionItemViewModel extends ViewModel {
return this._error && this._error.message;
}
async delete() {
this._isDeleting = true;
this.emitChange("isDeleting");
try {
await this._pickerVM.delete(this.id);
} catch(err) {
this._error = err;
console.error(err);
this.emitChange("error");
} finally {
this._isDeleting = false;
this.emitChange("isDeleting");
}
}
async clear() {
this._isClearing = true;
this.emitChange();
try {
await this._pickerVM.clear(this.id);
} catch(err) {
this._error = err;
console.error(err);
this.emitChange("error");
} finally {
this._isClearing = false;
this.emitChange("isClearing");
}
}
get isDeleting() {
return this._isDeleting;
}
get isClearing() {
return this._isClearing;
}
get id() {
return this._sessionInfo.id;
}
@ -96,27 +58,6 @@ class SessionItemViewModel extends ViewModel {
return this._exportDataUrl;
}
async export() {
try {
const data = await this._pickerVM._exportData(this._sessionInfo.id);
const json = JSON.stringify(data, undefined, 2);
const blob = new Blob([json], {type: "application/json"});
this._exportDataUrl = URL.createObjectURL(blob);
this.emitChange("exportDataUrl");
} catch (err) {
alert(err.message);
console.error(err);
}
}
clearExport() {
if (this._exportDataUrl) {
URL.revokeObjectURL(this._exportDataUrl);
this._exportDataUrl = null;
this.emitChange("exportDataUrl");
}
}
get avatarColorNumber() {
return getIdentifierColorNumber(this._sessionInfo.userId);
}
@ -148,39 +89,6 @@ export class SessionPickerViewModel extends ViewModel {
return this._loadViewModel;
}
async _exportData(id) {
const sessionInfo = await this.platform.sessionInfoStorage.get(id);
const stores = await this.platform.storageFactory.export(id);
const data = {sessionInfo, stores};
return data;
}
async import(json) {
try {
const data = JSON.parse(json);
const {sessionInfo} = data;
sessionInfo.comment = `Imported on ${new Date().toLocaleString()} from id ${sessionInfo.id}.`;
sessionInfo.id = this._createSessionContainer().createNewSessionId();
await this.platform.storageFactory.import(sessionInfo.id, data.stores);
await this.platform.sessionInfoStorage.add(sessionInfo);
this._sessions.set(new SessionItemViewModel(sessionInfo, this));
} catch (err) {
alert(err.message);
console.error(err);
}
}
async delete(id) {
const idx = this._sessions.array.findIndex(s => s.id === id);
await this.platform.sessionInfoStorage.delete(id);
await this.platform.storageFactory.delete(id);
this._sessions.remove(idx);
}
async clear(id) {
await this.platform.storageFactory.delete(id);
}
get sessions() {
return this._sessions;
}

View File

@ -1,5 +1,6 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -18,65 +19,93 @@ limitations under the License.
// as in some cases it would really be more convenient to have multiple events (like telling the timeline to scroll down)
// we do need to return a disposable from EventEmitter.on, or at least have a method here to easily track a subscription to an EventEmitter
import {EventEmitter} from "../utils/EventEmitter.js";
import {Disposables} from "../utils/Disposables.js";
import {EventEmitter} from "../utils/EventEmitter";
import {Disposables} from "../utils/Disposables";
export class ViewModel extends EventEmitter {
constructor(options = {}) {
import type {Disposable} from "../utils/Disposables";
import type {Platform} from "../platform/web/Platform";
import type {Clock} from "../platform/web/dom/Clock";
import type {ILogger} from "../logging/types";
import type {Navigation} from "./navigation/Navigation";
import type {SegmentType} from "./navigation/index";
import type {IURLRouter} from "./navigation/URLRouter";
export type Options<T extends object = SegmentType> = {
platform: Platform;
logger: ILogger;
urlCreator: IURLRouter<T>;
navigation: Navigation<T>;
emitChange?: (params: any) => void;
}
export class ViewModel<N extends object = SegmentType, O extends Options<N> = Options<N>> extends EventEmitter<{change: never}> {
private disposables?: Disposables;
private _isDisposed = false;
private _options: Readonly<O>;
constructor(options: Readonly<O>) {
super();
this.disposables = null;
this._isDisposed = false;
this._options = options;
}
childOptions(explicitOptions) {
const {navigation, urlCreator, platform} = this._options;
return Object.assign({navigation, urlCreator, platform}, explicitOptions);
childOptions<T extends Object>(explicitOptions: T): T & Options<N> {
return Object.assign({}, this._options, explicitOptions);
}
get options(): Readonly<O> { return this._options; }
// makes it easier to pass through dependencies of a sub-view model
getOption(name) {
getOption<N extends keyof O>(name: N): O[N] {
return this._options[name];
}
track(disposable) {
observeNavigation<T extends keyof N>(type: T, onChange: (value: N[T], type: T) => void): void {
const segmentObservable = this.navigation.observe(type);
const unsubscribe = segmentObservable.subscribe((value: N[T]) => {
onChange(value, type);
});
this.track(unsubscribe);
}
track<D extends Disposable>(disposable: D): D {
if (!this.disposables) {
this.disposables = new Disposables();
}
return this.disposables.track(disposable);
}
untrack(disposable) {
untrack(disposable: Disposable): undefined {
if (this.disposables) {
return this.disposables.untrack(disposable);
}
return null;
return undefined;
}
dispose() {
dispose(): void {
if (this.disposables) {
this.disposables.dispose();
}
this._isDisposed = true;
}
get isDisposed() {
get isDisposed(): boolean {
return this._isDisposed;
}
disposeTracked(disposable) {
disposeTracked(disposable: Disposable | undefined): undefined {
if (this.disposables) {
return this.disposables.disposeTracked(disposable);
}
return null;
return undefined;
}
// TODO: this will need to support binding
// if any of the expr is a function, assume the function is a binding, and return a binding function ourselves
//
//
// translated string should probably always be bindings, unless we're fine with a refresh when changing the language?
// we probably are, if we're using routing with a url, we could just refresh.
i18n(parts, ...expr) {
i18n(parts: TemplateStringsArray, ...expr: any[]): string {
// just concat for now
let result = "";
for (let i = 0; i < parts.length; ++i) {
@ -88,11 +117,7 @@ export class ViewModel extends EventEmitter {
return result;
}
updateOptions(options) {
this._options = Object.assign(this._options, options);
}
emitChange(changedProps) {
emitChange(changedProps: any): void {
if (this._options.emitChange) {
this._options.emitChange(changedProps);
} else {
@ -100,27 +125,24 @@ export class ViewModel extends EventEmitter {
}
}
get platform() {
get platform(): Platform {
return this._options.platform;
}
get clock() {
get clock(): Clock {
return this._options.platform.clock;
}
get logger() {
get logger(): ILogger {
return this.platform.logger;
}
/**
* The url router, only meant to be used to create urls with from view models.
* @return {URLRouter}
*/
get urlCreator() {
get urlCreator(): IURLRouter<N> {
return this._options.urlCreator;
}
get navigation() {
return this._options.navigation;
get navigation(): Navigation<N> {
// typescript needs a little help here
return this._options.navigation as unknown as Navigation<N>;
}
}

View File

@ -14,7 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export function avatarInitials(name) {
import { Platform } from "../platform/web/Platform";
import { MediaRepository } from "../matrix/net/MediaRepository";
export function avatarInitials(name: string): string {
let firstChar = name.charAt(0);
if (firstChar === "!" || firstChar === "@" || firstChar === "#") {
firstChar = name.charAt(1);
@ -29,10 +32,10 @@ export function avatarInitials(name) {
*
* @return {number}
*/
function hashCode(str) {
function hashCode(str: string): number {
let hash = 0;
let i;
let chr;
let i: number;
let chr: number;
if (str.length === 0) {
return hash;
}
@ -44,11 +47,11 @@ function hashCode(str) {
return Math.abs(hash);
}
export function getIdentifierColorNumber(id) {
export function getIdentifierColorNumber(id: string): number {
return (hashCode(id) % 8) + 1;
}
export function getAvatarHttpUrl(avatarUrl, cssSize, platform, mediaRepository) {
export function getAvatarHttpUrl(avatarUrl: string, cssSize: number, platform: Platform, mediaRepository: MediaRepository): string | null {
if (avatarUrl) {
const imageSize = cssSize * platform.devicePixelRatio;
return mediaRepository.mxcUrlThumbnail(avatarUrl, imageSize, imageSize, "crop");

View File

@ -14,19 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../ViewModel.js";
import {LoginFailure} from "../../matrix/SessionContainer.js";
import {ViewModel} from "../ViewModel";
import {LoginFailure} from "../../matrix/Client.js";
export class CompleteSSOLoginViewModel extends ViewModel {
constructor(options) {
super(options);
const {
loginToken,
sessionContainer,
client,
attemptLogin,
} = options;
this._loginToken = loginToken;
this._sessionContainer = sessionContainer;
this._client = client;
this._attemptLogin = attemptLogin;
this._errorMessage = "";
this.performSSOLoginCompletion();
@ -46,7 +46,7 @@ export class CompleteSSOLoginViewModel extends ViewModel {
const homeserver = await this.platform.settingsStorage.getString("sso_ongoing_login_homeserver");
let loginOptions;
try {
loginOptions = await this._sessionContainer.queryLogin(homeserver).result;
loginOptions = await this._client.queryLogin(homeserver).result;
}
catch (err) {
this._showError(err.message);

View File

@ -14,132 +14,176 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../ViewModel.js";
import {Client} from "../../matrix/Client.js";
import {Options as BaseOptions, ViewModel} from "../ViewModel";
import {PasswordLoginViewModel} from "./PasswordLoginViewModel.js";
import {StartSSOLoginViewModel} from "./StartSSOLoginViewModel.js";
import {CompleteSSOLoginViewModel} from "./CompleteSSOLoginViewModel.js";
import {LoadStatus} from "../../matrix/SessionContainer.js";
import {LoadStatus} from "../../matrix/Client.js";
import {SessionLoadViewModel} from "../SessionLoadViewModel.js";
import {SegmentType} from "../navigation/index";
export class LoginViewModel extends ViewModel {
constructor(options) {
import type {PasswordLoginMethod, SSOLoginHelper, TokenLoginMethod, ILoginMethod} from "../../matrix/login";
type Options = {
defaultHomeserver: string;
ready: ReadyFn;
loginToken?: string;
} & BaseOptions;
export class LoginViewModel extends ViewModel<SegmentType, Options> {
private _ready: ReadyFn;
private _loginToken?: string;
private _client: Client;
private _loginOptions?: LoginOptions;
private _passwordLoginViewModel?: PasswordLoginViewModel;
private _startSSOLoginViewModel?: StartSSOLoginViewModel;
private _completeSSOLoginViewModel?: CompleteSSOLoginViewModel;
private _loadViewModel?: SessionLoadViewModel;
private _loadViewModelSubscription?: () => void;
private _homeserver: string;
private _queriedHomeserver?: string;
private _abortHomeserverQueryTimeout?: () => void;
private _abortQueryOperation?: () => void;
private _hideHomeserver: boolean = false;
private _isBusy: boolean = false;
private _errorMessage: string = "";
constructor(options: Readonly<Options>) {
super(options);
const {ready, defaultHomeserver, createSessionContainer, loginToken} = options;
this._createSessionContainer = createSessionContainer;
const {ready, defaultHomeserver, loginToken} = options;
this._ready = ready;
this._loginToken = loginToken;
this._sessionContainer = this._createSessionContainer();
this._loginOptions = null;
this._passwordLoginViewModel = null;
this._startSSOLoginViewModel = null;
this._completeSSOLoginViewModel = null;
this._loadViewModel = null;
this._loadViewModelSubscription = null;
this._client = new Client(this.platform);
this._homeserver = defaultHomeserver;
this._queriedHomeserver = null;
this._errorMessage = "";
this._hideHomeserver = false;
this._isBusy = false;
this._abortHomeserverQueryTimeout = null;
this._abortQueryOperation = null;
this._initViewModels();
}
get passwordLoginViewModel() { return this._passwordLoginViewModel; }
get startSSOLoginViewModel() { return this._startSSOLoginViewModel; }
get completeSSOLoginViewModel(){ return this._completeSSOLoginViewModel; }
get homeserver() { return this._homeserver; }
get resolvedHomeserver() { return this._loginOptions?.homeserver; }
get errorMessage() { return this._errorMessage; }
get showHomeserver() { return !this._hideHomeserver; }
get loadViewModel() {return this._loadViewModel; }
get isBusy() { return this._isBusy; }
get isFetchingLoginOptions() { return !!this._abortQueryOperation; }
get passwordLoginViewModel(): PasswordLoginViewModel {
return this._passwordLoginViewModel;
}
goBack() {
get startSSOLoginViewModel(): StartSSOLoginViewModel {
return this._startSSOLoginViewModel;
}
get completeSSOLoginViewModel(): CompleteSSOLoginViewModel {
return this._completeSSOLoginViewModel;
}
get homeserver(): string {
return this._homeserver;
}
get resolvedHomeserver(): string | undefined {
return this._loginOptions?.homeserver;
}
get errorMessage(): string {
return this._errorMessage;
}
get showHomeserver(): boolean {
return !this._hideHomeserver;
}
get loadViewModel(): SessionLoadViewModel {
return this._loadViewModel;
}
get isBusy(): boolean {
return this._isBusy;
}
get isFetchingLoginOptions(): boolean {
return !!this._abortQueryOperation;
}
goBack(): void {
this.navigation.push("session");
}
async _initViewModels() {
private _initViewModels(): void {
if (this._loginToken) {
this._hideHomeserver = true;
this._completeSSOLoginViewModel = this.track(new CompleteSSOLoginViewModel(
this.childOptions(
{
sessionContainer: this._sessionContainer,
attemptLogin: loginMethod => this.attemptLogin(loginMethod),
client: this._client,
attemptLogin: (loginMethod: TokenLoginMethod) => this.attemptLogin(loginMethod),
loginToken: this._loginToken
})));
this.emitChange("completeSSOLoginViewModel");
}
else {
await this.queryHomeserver();
void this.queryHomeserver();
}
}
_showPasswordLogin() {
private _showPasswordLogin(): void {
this._passwordLoginViewModel = this.track(new PasswordLoginViewModel(
this.childOptions({
loginOptions: this._loginOptions,
attemptLogin: loginMethod => this.attemptLogin(loginMethod)
attemptLogin: (loginMethod: PasswordLoginMethod) => this.attemptLogin(loginMethod)
})));
this.emitChange("passwordLoginViewModel");
}
_showSSOLogin() {
private _showSSOLogin(): void {
this._startSSOLoginViewModel = this.track(
new StartSSOLoginViewModel(this.childOptions({loginOptions: this._loginOptions}))
);
this.emitChange("startSSOLoginViewModel");
}
_showError(message) {
private _showError(message: string): void {
this._errorMessage = message;
this.emitChange("errorMessage");
}
_setBusy(status) {
private _setBusy(status: boolean): void {
this._isBusy = status;
this._passwordLoginViewModel?.setBusy(status);
this._startSSOLoginViewModel?.setBusy(status);
this.emitChange("isBusy");
}
async attemptLogin(loginMethod) {
async attemptLogin(loginMethod: ILoginMethod): Promise<null> {
this._setBusy(true);
this._sessionContainer.startWithLogin(loginMethod);
const loadStatus = this._sessionContainer.loadStatus;
const handle = loadStatus.waitFor(status => status !== LoadStatus.Login);
void this._client.startWithLogin(loginMethod, {inspectAccountSetup: true});
const loadStatus = this._client.loadStatus;
const handle = loadStatus.waitFor((status: LoadStatus) => status !== LoadStatus.Login);
await handle.promise;
this._setBusy(false);
const status = loadStatus.get();
if (status === LoadStatus.LoginFailed) {
return this._sessionContainer.loginFailure;
return this._client.loginFailure;
}
this._hideHomeserver = true;
this.emitChange("hideHomeserver");
this._disposeViewModels();
this._createLoadViewModel();
void this._createLoadViewModel();
return null;
}
_createLoadViewModel() {
private _createLoadViewModel(): void {
this._loadViewModelSubscription = this.disposeTracked(this._loadViewModelSubscription);
this._loadViewModel = this.disposeTracked(this._loadViewModel);
this._loadViewModel = this.track(
new SessionLoadViewModel(
this.childOptions({
ready: (sessionContainer) => {
ready: (client) => {
// make sure we don't delete the session in dispose when navigating away
this._sessionContainer = null;
this._ready(sessionContainer);
this._client = null;
this._ready(client);
},
sessionContainer: this._sessionContainer,
client: this._client,
homeserver: this._homeserver
})
)
);
this._loadViewModel.start();
void this._loadViewModel.start();
this.emitChange("loadViewModel");
this._loadViewModelSubscription = this.track(
this._loadViewModel.disposableOn("change", () => {
@ -151,22 +195,22 @@ export class LoginViewModel extends ViewModel {
);
}
_disposeViewModels() {
this._startSSOLoginViewModel = this.disposeTracked(this._ssoLoginViewModel);
private _disposeViewModels(): void {
this._startSSOLoginViewModel = this.disposeTracked(this._startSSOLoginViewModel);
this._passwordLoginViewModel = this.disposeTracked(this._passwordLoginViewModel);
this._completeSSOLoginViewModel = this.disposeTracked(this._completeSSOLoginViewModel);
this.emitChange("disposeViewModels");
}
async setHomeserver(newHomeserver) {
async setHomeserver(newHomeserver: string): Promise<void> {
this._homeserver = newHomeserver;
// clear everything set by queryHomeserver
this._loginOptions = null;
this._queriedHomeserver = null;
this._loginOptions = undefined;
this._queriedHomeserver = undefined;
this._showError("");
this._disposeViewModels();
this._abortQueryOperation = this.disposeTracked(this._abortQueryOperation);
this.emitChange(); // multiple fields changing
this.emitChange("loginViewModels"); // multiple fields changing
// also clear the timeout if it is still running
this.disposeTracked(this._abortHomeserverQueryTimeout);
const timeout = this.clock.createTimeout(1000);
@ -181,10 +225,10 @@ export class LoginViewModel extends ViewModel {
}
}
this._abortHomeserverQueryTimeout = this.disposeTracked(this._abortHomeserverQueryTimeout);
this.queryHomeserver();
void this.queryHomeserver();
}
async queryHomeserver() {
async queryHomeserver(): Promise<void> {
// don't repeat a query we've just done
if (this._homeserver === this._queriedHomeserver || this._homeserver === "") {
return;
@ -200,7 +244,7 @@ export class LoginViewModel extends ViewModel {
// cancel ongoing query operation, if any
this._abortQueryOperation = this.disposeTracked(this._abortQueryOperation);
try {
const queryOperation = this._sessionContainer.queryLogin(this._homeserver);
const queryOperation = this._client.queryLogin(this._homeserver);
this._abortQueryOperation = this.track(() => queryOperation.abort());
this.emitChange("isFetchingLoginOptions");
this._loginOptions = await queryOperation.result;
@ -210,7 +254,7 @@ export class LoginViewModel extends ViewModel {
if (e.name === "AbortError") {
return; //aborted, bail out
} else {
this._loginOptions = null;
this._loginOptions = undefined;
}
} finally {
this._abortQueryOperation = this.disposeTracked(this._abortQueryOperation);
@ -221,19 +265,29 @@ export class LoginViewModel extends ViewModel {
if (this._loginOptions.password) { this._showPasswordLogin(); }
if (!this._loginOptions.sso && !this._loginOptions.password) {
this._showError("This homeserver supports neither SSO nor password based login flows");
}
}
}
else {
this._showError(`Could not query login methods supported by ${this.homeserver}`);
}
}
dispose() {
dispose(): void {
super.dispose();
if (this._sessionContainer) {
if (this._client) {
// if we move away before we're done with initial sync
// delete the session
this._sessionContainer.deleteSession();
void this._client.deleteSession();
}
}
}
type ReadyFn = (client: Client) => void;
// TODO: move to Client.js when its converted to typescript.
type LoginOptions = {
homeserver: string;
password?: (username: string, password: string) => PasswordLoginMethod;
sso?: SSOLoginHelper;
token?: (loginToken: string) => TokenLoginMethod;
};

View File

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../ViewModel.js";
import {LoginFailure} from "../../matrix/SessionContainer.js";
import {ViewModel} from "../ViewModel";
import {LoginFailure} from "../../matrix/Client.js";
export class PasswordLoginViewModel extends ViewModel {
constructor(options) {

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../ViewModel.js";
import {ViewModel} from "../ViewModel";
export class StartSSOLoginViewModel extends ViewModel{
constructor(options) {

View File

@ -14,29 +14,51 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {BaseObservableValue, ObservableValue} from "../../observable/ObservableValue.js";
import {BaseObservableValue, ObservableValue} from "../../observable/ObservableValue";
export class Navigation {
constructor(allowsChild) {
type AllowsChild<T> = (parent: Segment<T> | undefined, child: Segment<T>) => boolean;
/**
* OptionalValue is basically stating that if SegmentType[type] = true:
* - Allow this type to be optional
* - Give it a default value of undefined
* - Also allow it to be true
* This lets us do:
* const s: Segment<SegmentType> = new Segment("create-room");
* instead of
* const s: Segment<SegmentType> = new Segment("create-room", undefined);
*/
export type OptionalValue<T> = T extends true? [(undefined | true)?]: [T];
export class Navigation<T extends object> {
private readonly _allowsChild: AllowsChild<T>;
private _path: Path<T>;
private readonly _observables: Map<keyof T, SegmentObservable<T>> = new Map();
private readonly _pathObservable: ObservableValue<Path<T>>;
constructor(allowsChild: AllowsChild<T>) {
this._allowsChild = allowsChild;
this._path = new Path([], allowsChild);
this._observables = new Map();
this._pathObservable = new ObservableValue(this._path);
}
get pathObservable() {
get pathObservable(): ObservableValue<Path<T>> {
return this._pathObservable;
}
get path() {
get path(): Path<T> {
return this._path;
}
push(type, value = undefined) {
return this.applyPath(this.path.with(new Segment(type, value)));
push<K extends keyof T>(type: K, ...value: OptionalValue<T[K]>): void {
const newPath = this.path.with(new Segment(type, ...value));
if (newPath) {
this.applyPath(newPath);
}
}
applyPath(path) {
applyPath(path: Path<T>): void {
// Path is not exported, so you can only create a Path through Navigation,
// so we assume it respects the allowsChild rules
const oldPath = this._path;
@ -60,7 +82,7 @@ export class Navigation {
this._pathObservable.set(this._path);
}
observe(type) {
observe(type: keyof T): SegmentObservable<T> {
let observable = this._observables.get(type);
if (!observable) {
observable = new SegmentObservable(this, type);
@ -69,9 +91,9 @@ export class Navigation {
return observable;
}
pathFrom(segments) {
let parent;
let i;
pathFrom(segments: Segment<any>[]): Path<T> {
let parent: Segment<any> | undefined;
let i: number;
for (i = 0; i < segments.length; i += 1) {
if (!this._allowsChild(parent, segments[i])) {
return new Path(segments.slice(0, i), this._allowsChild);
@ -81,12 +103,12 @@ export class Navigation {
return new Path(segments, this._allowsChild);
}
segment(type, value) {
return new Segment(type, value);
segment<K extends keyof T>(type: K, ...value: OptionalValue<T[K]>): Segment<T> {
return new Segment(type, ...value);
}
}
function segmentValueEqual(a, b) {
function segmentValueEqual<T>(a?: T[keyof T], b?: T[keyof T]): boolean {
if (a === b) {
return true;
}
@ -103,24 +125,29 @@ function segmentValueEqual(a, b) {
return false;
}
export class Segment {
constructor(type, value) {
this.type = type;
this.value = value === undefined ? true : value;
export class Segment<T, K extends keyof T = any> {
public value: T[K];
constructor(public type: K, ...value: OptionalValue<T[K]>) {
this.value = (value[0] === undefined ? true : value[0]) as unknown as T[K];
}
}
class Path {
constructor(segments = [], allowsChild) {
class Path<T> {
private readonly _segments: Segment<T, any>[];
private readonly _allowsChild: AllowsChild<T>;
constructor(segments: Segment<T>[] = [], allowsChild: AllowsChild<T>) {
this._segments = segments;
this._allowsChild = allowsChild;
}
clone() {
clone(): Path<T> {
return new Path(this._segments.slice(), this._allowsChild);
}
with(segment) {
with(segment: Segment<T>): Path<T> | undefined {
let index = this._segments.length - 1;
do {
if (this._allowsChild(this._segments[index], segment)) {
@ -132,10 +159,10 @@ class Path {
index -= 1;
} while(index >= -1);
// allow -1 as well so we check if the segment is allowed as root
return null;
return undefined;
}
until(type) {
until(type: keyof T): Path<T> {
const index = this._segments.findIndex(s => s.type === type);
if (index !== -1) {
return new Path(this._segments.slice(0, index + 1), this._allowsChild)
@ -143,11 +170,11 @@ class Path {
return new Path([], this._allowsChild);
}
get(type) {
get(type: keyof T): Segment<T> | undefined {
return this._segments.find(s => s.type === type);
}
replace(segment) {
replace(segment: Segment<T>): Path<T> | undefined {
const index = this._segments.findIndex(s => s.type === segment.type);
if (index !== -1) {
const parent = this._segments[index - 1];
@ -160,10 +187,10 @@ class Path {
}
}
}
return null;
return undefined;
}
get segments() {
get segments(): Segment<T>[] {
return this._segments;
}
}
@ -172,43 +199,49 @@ class Path {
* custom observable so it always returns what is in navigation.path, even if we haven't emitted the change yet.
* This ensures that observers of a segment can also read the most recent value of other segments.
*/
class SegmentObservable extends BaseObservableValue {
constructor(navigation, type) {
class SegmentObservable<T extends object> extends BaseObservableValue<T[keyof T] | undefined> {
private readonly _navigation: Navigation<T>;
private _type: keyof T;
private _lastSetValue?: T[keyof T];
constructor(navigation: Navigation<T>, type: keyof T) {
super();
this._navigation = navigation;
this._type = type;
this._lastSetValue = navigation.path.get(type)?.value;
}
get() {
get(): T[keyof T] | undefined {
const path = this._navigation.path;
const segment = path.get(this._type);
const value = segment?.value;
return value;
}
emitIfChanged() {
emitIfChanged(): void {
const newValue = this.get();
if (!segmentValueEqual(newValue, this._lastSetValue)) {
if (!segmentValueEqual<T>(newValue, this._lastSetValue)) {
this._lastSetValue = newValue;
this.emit(newValue);
}
}
}
export type {Path};
export function tests() {
function createMockNavigation() {
return new Navigation((parent, {type}) => {
switch (parent?.type) {
case undefined:
return type === "1" || "2";
return type === "1" || type === "2";
case "1":
return type === "1.1";
case "1.1":
return type === "1.1.1";
case "2":
return type === "2.1" || "2.2";
return type === "2.1" || type === "2.2";
default:
return false;
}
@ -216,7 +249,7 @@ export function tests() {
}
function observeTypes(nav, types) {
const changes = [];
const changes: {type:string, value:any}[] = [];
for (const type of types) {
nav.observe(type).subscribe(value => {
changes.push({type, value});
@ -225,6 +258,12 @@ export function tests() {
return changes;
}
type SegmentType = {
"foo": number;
"bar": number;
"baz": number;
}
return {
"applying a path emits an event on the observable": assert => {
const nav = createMockNavigation();
@ -242,18 +281,18 @@ export function tests() {
assert.equal(changes[1].value, 8);
},
"path.get": assert => {
const path = new Path([new Segment("foo", 5), new Segment("bar", 6)], () => true);
assert.equal(path.get("foo").value, 5);
assert.equal(path.get("bar").value, 6);
const path = new Path<SegmentType>([new Segment("foo", 5), new Segment("bar", 6)], () => true);
assert.equal(path.get("foo")!.value, 5);
assert.equal(path.get("bar")!.value, 6);
},
"path.replace success": assert => {
const path = new Path([new Segment("foo", 5), new Segment("bar", 6)], () => true);
const path = new Path<SegmentType>([new Segment("foo", 5), new Segment("bar", 6)], () => true);
const newPath = path.replace(new Segment("foo", 1));
assert.equal(newPath.get("foo").value, 1);
assert.equal(newPath.get("bar").value, 6);
assert.equal(newPath!.get("foo")!.value, 1);
assert.equal(newPath!.get("bar")!.value, 6);
},
"path.replace not found": assert => {
const path = new Path([new Segment("foo", 5), new Segment("bar", 6)], () => true);
const path = new Path<SegmentType>([new Segment("foo", 5), new Segment("bar", 6)], () => true);
const newPath = path.replace(new Segment("baz", 1));
assert.equal(newPath, null);
}

View File

@ -14,28 +14,55 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export class URLRouter {
constructor({history, navigation, parseUrlPath, stringifyPath}) {
import type {History} from "../../platform/web/dom/History.js";
import type {Navigation, Segment, Path, OptionalValue} from "./Navigation";
import type {SubscriptionHandle} from "../../observable/BaseObservable";
type ParseURLPath<T> = (urlPath: string, currentNavPath: Path<T>, defaultSessionId?: string) => Segment<T>[];
type StringifyPath<T> = (path: Path<T>) => string;
export interface IURLRouter<T> {
attach(): void;
dispose(): void;
pushUrl(url: string): void;
tryRestoreLastUrl(): boolean;
urlForSegments(segments: Segment<T>[]): string | undefined;
urlForSegment<K extends keyof T>(type: K, ...value: OptionalValue<T[K]>): string | undefined;
urlUntilSegment(type: keyof T): string;
urlForPath(path: Path<T>): string;
openRoomActionUrl(roomId: string): string;
createSSOCallbackURL(): string;
normalizeUrl(): void;
}
export class URLRouter<T extends {session: string | boolean}> implements IURLRouter<T> {
private readonly _history: History;
private readonly _navigation: Navigation<T>;
private readonly _parseUrlPath: ParseURLPath<T>;
private readonly _stringifyPath: StringifyPath<T>;
private _subscription?: SubscriptionHandle;
private _pathSubscription?: SubscriptionHandle;
private _isApplyingUrl: boolean = false;
private _defaultSessionId?: string;
constructor(history: History, navigation: Navigation<T>, parseUrlPath: ParseURLPath<T>, stringifyPath: StringifyPath<T>) {
this._history = history;
this._navigation = navigation;
this._parseUrlPath = parseUrlPath;
this._stringifyPath = stringifyPath;
this._subscription = null;
this._pathSubscription = null;
this._isApplyingUrl = false;
this._defaultSessionId = this._getLastSessionId();
}
_getLastSessionId() {
const navPath = this._urlAsNavPath(this._history.getLastUrl() || "");
private _getLastSessionId(): string | undefined {
const navPath = this._urlAsNavPath(this._history.getLastSessionUrl() || "");
const sessionId = navPath.get("session")?.value;
if (typeof sessionId === "string") {
return sessionId;
}
return null;
return undefined;
}
attach() {
attach(): void {
this._subscription = this._history.subscribe(url => this._applyUrl(url));
// subscribe to path before applying initial url
// so redirects in _applyNavPathToHistory are reflected in url bar
@ -43,12 +70,12 @@ export class URLRouter {
this._applyUrl(this._history.get());
}
dispose() {
this._subscription = this._subscription();
this._pathSubscription = this._pathSubscription();
dispose(): void {
if (this._subscription) { this._subscription = this._subscription(); }
if (this._pathSubscription) { this._pathSubscription = this._pathSubscription(); }
}
_applyNavPathToHistory(path) {
private _applyNavPathToHistory(path: Path<T>): void {
const url = this.urlForPath(path);
if (url !== this._history.get()) {
if (this._isApplyingUrl) {
@ -60,7 +87,7 @@ export class URLRouter {
}
}
_applyNavPathToNavigation(navPath) {
private _applyNavPathToNavigation(navPath: Path<T>): void {
// this will cause _applyNavPathToHistory to be called,
// so set a flag whether this request came from ourselves
// (in which case it is a redirect if the url does not match the current one)
@ -69,22 +96,22 @@ export class URLRouter {
this._isApplyingUrl = false;
}
_urlAsNavPath(url) {
private _urlAsNavPath(url: string): Path<T> {
const urlPath = this._history.urlAsPath(url);
return this._navigation.pathFrom(this._parseUrlPath(urlPath, this._navigation.path, this._defaultSessionId));
}
_applyUrl(url) {
private _applyUrl(url: string): void {
const navPath = this._urlAsNavPath(url);
this._applyNavPathToNavigation(navPath);
}
pushUrl(url) {
pushUrl(url: string): void {
this._history.pushUrl(url);
}
tryRestoreLastUrl() {
const lastNavPath = this._urlAsNavPath(this._history.getLastUrl() || "");
tryRestoreLastUrl(): boolean {
const lastNavPath = this._urlAsNavPath(this._history.getLastSessionUrl() || "");
if (lastNavPath.segments.length !== 0) {
this._applyNavPathToNavigation(lastNavPath);
return true;
@ -92,8 +119,8 @@ export class URLRouter {
return false;
}
urlForSegments(segments) {
let path = this._navigation.path;
urlForSegments(segments: Segment<T>[]): string | undefined {
let path: Path<T> | undefined = this._navigation.path;
for (const segment of segments) {
path = path.with(segment);
if (!path) {
@ -103,29 +130,29 @@ export class URLRouter {
return this.urlForPath(path);
}
urlForSegment(type, value) {
return this.urlForSegments([this._navigation.segment(type, value)]);
urlForSegment<K extends keyof T>(type: K, ...value: OptionalValue<T[K]>): string | undefined {
return this.urlForSegments([this._navigation.segment(type, ...value)]);
}
urlUntilSegment(type) {
urlUntilSegment(type: keyof T): string {
return this.urlForPath(this._navigation.path.until(type));
}
urlForPath(path) {
urlForPath(path: Path<T>): string {
return this._history.pathAsUrl(this._stringifyPath(path));
}
openRoomActionUrl(roomId) {
openRoomActionUrl(roomId: string): string {
// not a segment to navigation knowns about, so append it manually
const urlPath = `${this._stringifyPath(this._navigation.path.until("session"))}/open-room/${roomId}`;
return this._history.pathAsUrl(urlPath);
}
createSSOCallbackURL() {
createSSOCallbackURL(): string {
return window.location.origin;
}
normalizeUrl() {
normalizeUrl(): void {
// Remove any queryParameters from the URL
// Gets rid of the loginToken after SSO
this._history.replaceUrlSilently(`${window.location.origin}/${window.location.hash}`);

View File

@ -14,25 +14,43 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {Navigation, Segment} from "./Navigation.js";
import {URLRouter} from "./URLRouter.js";
import {Navigation, Segment} from "./Navigation";
import {URLRouter} from "./URLRouter";
import type {Path, OptionalValue} from "./Navigation";
export function createNavigation() {
export type SegmentType = {
"login": true;
"session": string | boolean;
"sso": string;
"logout": true;
"room": string;
"rooms": string[];
"settings": true;
"create-room": true;
"empty-grid-tile": number;
"lightbox": string;
"right-panel": true;
"details": true;
"members": true;
"member": string;
};
export function createNavigation(): Navigation<SegmentType> {
return new Navigation(allowsChild);
}
export function createRouter({history, navigation}) {
return new URLRouter({history, navigation, stringifyPath, parseUrlPath});
export function createRouter({history, navigation}: {history: History, navigation: Navigation<SegmentType>}): URLRouter<SegmentType> {
return new URLRouter(history, navigation, parseUrlPath, stringifyPath);
}
function allowsChild(parent, child) {
function allowsChild(parent: Segment<SegmentType> | undefined, child: Segment<SegmentType>): boolean {
const {type} = child;
switch (parent?.type) {
case undefined:
// allowed root segments
return type === "login" || type === "session" || type === "sso";
return type === "login" || type === "session" || type === "sso" || type === "logout";
case "session":
return type === "room" || type === "rooms" || type === "settings";
return type === "room" || type === "rooms" || type === "settings" || type === "create-room";
case "rooms":
// downside of the approach: both of these will control which tile is selected
return type === "room" || type === "empty-grid-tile";
@ -45,8 +63,9 @@ function allowsChild(parent, child) {
}
}
export function removeRoomFromPath(path, roomId) {
const rooms = path.get("rooms");
export function removeRoomFromPath(path: Path<SegmentType>, roomId: string): Path<SegmentType> | undefined {
let newPath: Path<SegmentType> | undefined = path;
const rooms = newPath.get("rooms");
let roomIdGridIndex = -1;
// first delete from rooms segment
if (rooms) {
@ -54,22 +73,22 @@ export function removeRoomFromPath(path, roomId) {
if (roomIdGridIndex !== -1) {
const idsWithoutRoom = rooms.value.slice();
idsWithoutRoom[roomIdGridIndex] = "";
path = path.replace(new Segment("rooms", idsWithoutRoom));
newPath = newPath.replace(new Segment("rooms", idsWithoutRoom));
}
}
const room = path.get("room");
const room = newPath!.get("room");
// then from room (which occurs with or without rooms)
if (room && room.value === roomId) {
if (roomIdGridIndex !== -1) {
path = path.with(new Segment("empty-grid-tile", roomIdGridIndex));
newPath = newPath!.with(new Segment("empty-grid-tile", roomIdGridIndex));
} else {
path = path.until("session");
newPath = newPath!.until("session");
}
}
return path;
return newPath;
}
function roomsSegmentWithRoom(rooms, roomId, path) {
function roomsSegmentWithRoom(rooms: Segment<SegmentType, "rooms">, roomId: string, path: Path<SegmentType>): Segment<SegmentType, "rooms"> {
if(!rooms.value.includes(roomId)) {
const emptyGridTile = path.get("empty-grid-tile");
const oldRoom = path.get("room");
@ -87,28 +106,28 @@ function roomsSegmentWithRoom(rooms, roomId, path) {
}
}
function pushRightPanelSegment(array, segment, value = true) {
function pushRightPanelSegment<T extends keyof SegmentType>(array: Segment<SegmentType>[], segment: T, ...value: OptionalValue<SegmentType[T]>): void {
array.push(new Segment("right-panel"));
array.push(new Segment(segment, value));
array.push(new Segment(segment, ...value));
}
export function addPanelIfNeeded(navigation, path) {
export function addPanelIfNeeded<T extends SegmentType>(navigation: Navigation<T>, path: Path<T>): Path<T> {
const segments = navigation.path.segments;
const i = segments.findIndex(segment => segment.type === "right-panel");
let _path = path;
if (i !== -1) {
_path = path.until("room");
_path = _path.with(segments[i]);
_path = _path.with(segments[i + 1]);
_path = _path.with(segments[i])!;
_path = _path.with(segments[i + 1])!;
}
return _path;
}
export function parseUrlPath(urlPath, currentNavPath, defaultSessionId) {
// substr(1) to take of initial /
const parts = urlPath.substr(1).split("/");
export function parseUrlPath(urlPath: string, currentNavPath: Path<SegmentType>, defaultSessionId?: string): Segment<SegmentType>[] {
// substring(1) to take of initial /
const parts = urlPath.substring(1).split("/");
const iterator = parts[Symbol.iterator]();
const segments = [];
const segments: Segment<SegmentType>[] = [];
let next;
while (!(next = iterator.next()).done) {
const type = next.value;
@ -132,11 +151,16 @@ export function parseUrlPath(urlPath, currentNavPath, defaultSessionId) {
segments.push(roomsSegmentWithRoom(rooms, roomId, currentNavPath));
}
segments.push(new Segment("room", roomId));
// Add right-panel segments from previous path
const previousSegments = currentNavPath.segments;
const i = previousSegments.findIndex(s => s.type === "right-panel");
if (i !== -1) {
segments.push(...previousSegments.slice(i));
const openRoomPartIndex = parts.findIndex(part => part === "open-room");
const hasOnlyRoomIdAfterPart = openRoomPartIndex >= parts.length - 2;
if (hasOnlyRoomIdAfterPart) {
// Copy right-panel segments from previous path only if there are no other parts after open-room
// fixes memberlist -> member details closing/opening grid view
const previousSegments = currentNavPath.segments;
const i = previousSegments.findIndex(s => s.type === "right-panel");
if (i !== -1) {
segments.push(...previousSegments.slice(i));
}
}
} else if (type === "last-session") {
let sessionSegment = currentNavPath.get("session");
@ -165,9 +189,9 @@ export function parseUrlPath(urlPath, currentNavPath, defaultSessionId) {
return segments;
}
export function stringifyPath(path) {
export function stringifyPath(path: Path<SegmentType>): string {
let urlPath = "";
let prevSegment;
let prevSegment: Segment<SegmentType> | undefined;
for (const segment of path.segments) {
switch (segment.type) {
case "rooms":
@ -200,9 +224,15 @@ export function stringifyPath(path) {
}
export function tests() {
function createEmptyPath() {
const nav: Navigation<SegmentType> = new Navigation(allowsChild);
const path = nav.pathFrom([]);
return path;
}
return {
"stringify grid url with focused empty tile": assert => {
const nav = new Navigation(allowsChild);
const nav: Navigation<SegmentType> = new Navigation(allowsChild);
const path = nav.pathFrom([
new Segment("session", 1),
new Segment("rooms", ["a", "b", "c"]),
@ -212,7 +242,7 @@ export function tests() {
assert.equal(urlPath, "/session/1/rooms/a,b,c/3");
},
"stringify grid url with focused room": assert => {
const nav = new Navigation(allowsChild);
const nav: Navigation<SegmentType> = new Navigation(allowsChild);
const path = nav.pathFrom([
new Segment("session", 1),
new Segment("rooms", ["a", "b", "c"]),
@ -222,7 +252,7 @@ export function tests() {
assert.equal(urlPath, "/session/1/rooms/a,b,c/1");
},
"stringify url with right-panel and details segment": assert => {
const nav = new Navigation(allowsChild);
const nav: Navigation<SegmentType> = new Navigation(allowsChild);
const path = nav.pathFrom([
new Segment("session", 1),
new Segment("rooms", ["a", "b", "c"]),
@ -234,13 +264,15 @@ export function tests() {
assert.equal(urlPath, "/session/1/rooms/a,b,c/1/details");
},
"Parse loginToken query parameter into SSO segment": assert => {
const segments = parseUrlPath("?loginToken=a1232aSD123");
const path = createEmptyPath();
const segments = parseUrlPath("?loginToken=a1232aSD123", path);
assert.equal(segments.length, 1);
assert.equal(segments[0].type, "sso");
assert.equal(segments[0].value, "a1232aSD123");
},
"parse grid url path with focused empty tile": assert => {
const segments = parseUrlPath("/session/1/rooms/a,b,c/3");
const path = createEmptyPath();
const segments = parseUrlPath("/session/1/rooms/a,b,c/3", path);
assert.equal(segments.length, 3);
assert.equal(segments[0].type, "session");
assert.equal(segments[0].value, "1");
@ -250,7 +282,8 @@ export function tests() {
assert.equal(segments[2].value, 3);
},
"parse grid url path with focused room": assert => {
const segments = parseUrlPath("/session/1/rooms/a,b,c/1");
const path = createEmptyPath();
const segments = parseUrlPath("/session/1/rooms/a,b,c/1", path);
assert.equal(segments.length, 3);
assert.equal(segments[0].type, "session");
assert.equal(segments[0].value, "1");
@ -260,7 +293,8 @@ export function tests() {
assert.equal(segments[2].value, "b");
},
"parse empty grid url": assert => {
const segments = parseUrlPath("/session/1/rooms/");
const path = createEmptyPath();
const segments = parseUrlPath("/session/1/rooms/", path);
assert.equal(segments.length, 3);
assert.equal(segments[0].type, "session");
assert.equal(segments[0].value, "1");
@ -270,7 +304,8 @@ export function tests() {
assert.equal(segments[2].value, 0);
},
"parse empty grid url with focus": assert => {
const segments = parseUrlPath("/session/1/rooms//1");
const path = createEmptyPath();
const segments = parseUrlPath("/session/1/rooms//1", path);
assert.equal(segments.length, 3);
assert.equal(segments[0].type, "session");
assert.equal(segments[0].value, "1");
@ -280,7 +315,7 @@ export function tests() {
assert.equal(segments[2].value, 1);
},
"parse open-room action replacing the current focused room": assert => {
const nav = new Navigation(allowsChild);
const nav: Navigation<SegmentType> = new Navigation(allowsChild);
const path = nav.pathFrom([
new Segment("session", 1),
new Segment("rooms", ["a", "b", "c"]),
@ -296,7 +331,7 @@ export function tests() {
assert.equal(segments[2].value, "d");
},
"parse open-room action changing focus to an existing room": assert => {
const nav = new Navigation(allowsChild);
const nav: Navigation<SegmentType> = new Navigation(allowsChild);
const path = nav.pathFrom([
new Segment("session", 1),
new Segment("rooms", ["a", "b", "c"]),
@ -312,7 +347,7 @@ export function tests() {
assert.equal(segments[2].value, "a");
},
"parse open-room action changing focus to an existing room with details open": assert => {
const nav = new Navigation(allowsChild);
const nav: Navigation<SegmentType> = new Navigation(allowsChild);
const path = nav.pathFrom([
new Segment("session", 1),
new Segment("rooms", ["a", "b", "c"]),
@ -333,8 +368,30 @@ export function tests() {
assert.equal(segments[4].type, "details");
assert.equal(segments[4].value, true);
},
"open-room action should only copy over previous segments if there are no parts after open-room": assert => {
const nav: Navigation<SegmentType> = new Navigation(allowsChild);
const path = nav.pathFrom([
new Segment("session", 1),
new Segment("rooms", ["a", "b", "c"]),
new Segment("room", "b"),
new Segment("right-panel", true),
new Segment("members", true)
]);
const segments = parseUrlPath("/session/1/open-room/a/member/foo", path);
assert.equal(segments.length, 5);
assert.equal(segments[0].type, "session");
assert.equal(segments[0].value, "1");
assert.equal(segments[1].type, "rooms");
assert.deepEqual(segments[1].value, ["a", "b", "c"]);
assert.equal(segments[2].type, "room");
assert.equal(segments[2].value, "a");
assert.equal(segments[3].type, "right-panel");
assert.equal(segments[3].value, true);
assert.equal(segments[4].type, "member");
assert.equal(segments[4].value, "foo");
},
"parse open-room action setting a room in an empty tile": assert => {
const nav = new Navigation(allowsChild);
const nav: Navigation<SegmentType> = new Navigation(allowsChild);
const path = nav.pathFrom([
new Segment("session", 1),
new Segment("rooms", ["a", "b", "c"]),
@ -350,82 +407,83 @@ export function tests() {
assert.equal(segments[2].value, "d");
},
"parse session url path without id": assert => {
const segments = parseUrlPath("/session");
const path = createEmptyPath();
const segments = parseUrlPath("/session", path);
assert.equal(segments.length, 1);
assert.equal(segments[0].type, "session");
assert.strictEqual(segments[0].value, true);
},
"remove active room from grid path turns it into empty tile": assert => {
const nav = new Navigation(allowsChild);
const nav: Navigation<SegmentType> = new Navigation(allowsChild);
const path = nav.pathFrom([
new Segment("session", 1),
new Segment("rooms", ["a", "b", "c"]),
new Segment("room", "b")
]);
const newPath = removeRoomFromPath(path, "b");
assert.equal(newPath.segments.length, 3);
assert.equal(newPath.segments[0].type, "session");
assert.equal(newPath.segments[0].value, 1);
assert.equal(newPath.segments[1].type, "rooms");
assert.deepEqual(newPath.segments[1].value, ["a", "", "c"]);
assert.equal(newPath.segments[2].type, "empty-grid-tile");
assert.equal(newPath.segments[2].value, 1);
assert.equal(newPath?.segments.length, 3);
assert.equal(newPath?.segments[0].type, "session");
assert.equal(newPath?.segments[0].value, 1);
assert.equal(newPath?.segments[1].type, "rooms");
assert.deepEqual(newPath?.segments[1].value, ["a", "", "c"]);
assert.equal(newPath?.segments[2].type, "empty-grid-tile");
assert.equal(newPath?.segments[2].value, 1);
},
"remove inactive room from grid path": assert => {
const nav = new Navigation(allowsChild);
const nav: Navigation<SegmentType> = new Navigation(allowsChild);
const path = nav.pathFrom([
new Segment("session", 1),
new Segment("rooms", ["a", "b", "c"]),
new Segment("room", "b")
]);
const newPath = removeRoomFromPath(path, "a");
assert.equal(newPath.segments.length, 3);
assert.equal(newPath.segments[0].type, "session");
assert.equal(newPath.segments[0].value, 1);
assert.equal(newPath.segments[1].type, "rooms");
assert.deepEqual(newPath.segments[1].value, ["", "b", "c"]);
assert.equal(newPath.segments[2].type, "room");
assert.equal(newPath.segments[2].value, "b");
assert.equal(newPath?.segments.length, 3);
assert.equal(newPath?.segments[0].type, "session");
assert.equal(newPath?.segments[0].value, 1);
assert.equal(newPath?.segments[1].type, "rooms");
assert.deepEqual(newPath?.segments[1].value, ["", "b", "c"]);
assert.equal(newPath?.segments[2].type, "room");
assert.equal(newPath?.segments[2].value, "b");
},
"remove inactive room from grid path with empty tile": assert => {
const nav = new Navigation(allowsChild);
const nav: Navigation<SegmentType> = new Navigation(allowsChild);
const path = nav.pathFrom([
new Segment("session", 1),
new Segment("rooms", ["a", "b", ""]),
new Segment("empty-grid-tile", 3)
]);
const newPath = removeRoomFromPath(path, "b");
assert.equal(newPath.segments.length, 3);
assert.equal(newPath.segments[0].type, "session");
assert.equal(newPath.segments[0].value, 1);
assert.equal(newPath.segments[1].type, "rooms");
assert.deepEqual(newPath.segments[1].value, ["a", "", ""]);
assert.equal(newPath.segments[2].type, "empty-grid-tile");
assert.equal(newPath.segments[2].value, 3);
assert.equal(newPath?.segments.length, 3);
assert.equal(newPath?.segments[0].type, "session");
assert.equal(newPath?.segments[0].value, 1);
assert.equal(newPath?.segments[1].type, "rooms");
assert.deepEqual(newPath?.segments[1].value, ["a", "", ""]);
assert.equal(newPath?.segments[2].type, "empty-grid-tile");
assert.equal(newPath?.segments[2].value, 3);
},
"remove active room": assert => {
const nav = new Navigation(allowsChild);
const nav: Navigation<SegmentType> = new Navigation(allowsChild);
const path = nav.pathFrom([
new Segment("session", 1),
new Segment("room", "b")
]);
const newPath = removeRoomFromPath(path, "b");
assert.equal(newPath.segments.length, 1);
assert.equal(newPath.segments[0].type, "session");
assert.equal(newPath.segments[0].value, 1);
assert.equal(newPath?.segments.length, 1);
assert.equal(newPath?.segments[0].type, "session");
assert.equal(newPath?.segments[0].value, 1);
},
"remove inactive room doesn't do anything": assert => {
const nav = new Navigation(allowsChild);
const nav: Navigation<SegmentType> = new Navigation(allowsChild);
const path = nav.pathFrom([
new Segment("session", 1),
new Segment("room", "b")
]);
const newPath = removeRoomFromPath(path, "a");
assert.equal(newPath.segments.length, 2);
assert.equal(newPath.segments[0].type, "session");
assert.equal(newPath.segments[0].value, 1);
assert.equal(newPath.segments[1].type, "room");
assert.equal(newPath.segments[1].value, "b");
assert.equal(newPath?.segments.length, 2);
assert.equal(newPath?.segments[0].type, "session");
assert.equal(newPath?.segments[0].value, 1);
assert.equal(newPath?.segments[1].type, "room");
assert.equal(newPath?.segments[1].value, "b");
},
}

65
src/domain/rageshake.ts Normal file
View File

@ -0,0 +1,65 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import type {BlobHandle} from "../platform/web/dom/BlobHandle";
import type {RequestFunction} from "../platform/types/types";
// see https://github.com/matrix-org/rageshake#readme
type RageshakeData = {
// A textual description of the problem. Included in the details.log.gz file.
text: string | undefined;
// Application user-agent. Included in the details.log.gz file.
userAgent: string;
// Identifier for the application (eg 'riot-web'). Should correspond to a mapping configured in the configuration file for github issue reporting to work.
app: string;
// Application version. Included in the details.log.gz file.
version: string;
// Label to attach to the github issue, and include in the details file.
label: string | undefined;
};
export async function submitLogsToRageshakeServer(data: RageshakeData, logsBlob: BlobHandle, submitUrl: string, request: RequestFunction): Promise<void> {
const formData = new Map<string, string | {name: string, blob: BlobHandle}>();
if (data.text) {
formData.set("text", data.text);
}
formData.set("user_agent", data.userAgent);
formData.set("app", data.app);
formData.set("version", data.version);
if (data.label) {
formData.set("label", data.label);
}
formData.set("file", {name: "logs.json", blob: logsBlob});
const headers: Map<string, string> = new Map();
headers.set("Accept", "application/json");
const result = request(submitUrl, {
method: "POST",
body: formData,
headers
});
let response;
try {
response = await result.response();
} catch (err) {
throw new Error(`Could not submit logs to ${submitUrl}, got error ${err.message}`);
}
const {status, body} = response;
if (status < 200 || status >= 300) {
throw new Error(`Could not submit logs to ${submitUrl}, got status code ${status} with body ${body}`);
}
// we don't bother with reading report_url from the body as the rageshake server doesn't always return it
// and would have to have CORS setup properly for us to be able to read it.
}

View File

@ -0,0 +1,144 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../ViewModel";
import {imageToInfo} from "./common.js";
import {RoomType} from "../../matrix/room/common";
export class CreateRoomViewModel extends ViewModel {
constructor(options) {
super(options);
const {session} = options;
this._session = session;
this._name = undefined;
this._topic = undefined;
this._roomAlias = undefined;
this._isPublic = false;
this._isEncrypted = true;
this._isAdvancedShown = false;
this._isFederationDisabled = false;
this._avatarScaledBlob = undefined;
this._avatarFileName = undefined;
this._avatarInfo = undefined;
}
get isPublic() { return this._isPublic; }
get isEncrypted() { return this._isEncrypted; }
get canCreate() { return !!this._name; }
avatarUrl() { return this._avatarScaledBlob.url; }
get avatarTitle() { return this._name; }
get avatarLetter() { return ""; }
get avatarColorNumber() { return 0; }
get hasAvatar() { return !!this._avatarScaledBlob; }
get isFederationDisabled() { return this._isFederationDisabled; }
get isAdvancedShown() { return this._isAdvancedShown; }
setName(name) {
this._name = name;
this.emitChange("canCreate");
}
setRoomAlias(roomAlias) {
this._roomAlias = roomAlias;
}
setTopic(topic) {
this._topic = topic;
}
setPublic(isPublic) {
this._isPublic = isPublic;
this.emitChange("isPublic");
}
setEncrypted(isEncrypted) {
this._isEncrypted = isEncrypted;
this.emitChange("isEncrypted");
}
setFederationDisabled(disable) {
this._isFederationDisabled = disable;
this.emitChange("isFederationDisabled");
}
toggleAdvancedShown() {
this._isAdvancedShown = !this._isAdvancedShown;
this.emitChange("isAdvancedShown");
}
create() {
let avatar;
if (this._avatarScaledBlob) {
avatar = {
info: this._avatarInfo,
name: this._avatarFileName,
blob: this._avatarScaledBlob
}
}
const roomBeingCreated = this._session.createRoom({
type: this.isPublic ? RoomType.Public : RoomType.Private,
name: this._name ?? undefined,
topic: this._topic ?? undefined,
isEncrypted: !this.isPublic && this._isEncrypted,
isFederationDisabled: this._isFederationDisabled,
alias: this.isPublic ? ensureAliasIsLocalPart(this._roomAlias) : undefined,
avatar,
});
this.navigation.push("room", roomBeingCreated.id);
}
async selectAvatar() {
if (!this.platform.hasReadPixelPermission()) {
alert("Please allow canvas image data access, so we can scale your images down.");
return;
}
if (this._avatarScaledBlob) {
this._avatarScaledBlob.dispose();
}
this._avatarScaledBlob = undefined;
this._avatarFileName = undefined;
this._avatarInfo = undefined;
const file = await this.platform.openFile("image/*");
if (!file || !file.blob.mimeType.startsWith("image/")) {
// allow to clear the avatar by not selecting an image
this.emitChange("hasAvatar");
return;
}
let image = await this.platform.loadImage(file.blob);
const limit = 800;
if (image.maxDimension > limit) {
const scaledImage = await image.scale(limit);
image.dispose();
image = scaledImage;
}
this._avatarScaledBlob = image.blob;
this._avatarInfo = imageToInfo(image);
this._avatarFileName = file.name;
this.emitChange("hasAvatar");
}
}
function ensureAliasIsLocalPart(roomAliasLocalPart) {
if (roomAliasLocalPart.startsWith("#")) {
roomAliasLocalPart = roomAliasLocalPart.substr(1);
}
const colonIdx = roomAliasLocalPart.indexOf(":");
if (colonIdx !== -1) {
roomAliasLocalPart = roomAliasLocalPart.substr(0, colonIdx);
}
return roomAliasLocalPart;
}

View File

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../ViewModel.js";
import {addPanelIfNeeded} from "../navigation/index.js";
import {ViewModel} from "../ViewModel";
import {addPanelIfNeeded} from "../navigation/index";
function dedupeSparse(roomIds) {
return roomIds.map((id, idx) => {
@ -185,8 +185,8 @@ export class RoomGridViewModel extends ViewModel {
}
}
import {createNavigation} from "../navigation/index.js";
import {ObservableValue} from "../../observable/ObservableValue.js";
import {createNavigation} from "../navigation/index";
import {ObservableValue} from "../../observable/ObservableValue";
export function tests() {
class RoomVMMock {

View File

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ObservableValue} from "../../observable/ObservableValue.js";
import {ObservableValue} from "../../observable/ObservableValue";
import {RoomStatus} from "../../matrix/room/common";
/**
Depending on the status of a room (invited, joined, archived, or none),
@ -34,11 +35,11 @@ the now transferred child view model.
This is also why there is an explicit initialize method, see comment there.
*/
export class RoomViewModelObservable extends ObservableValue {
constructor(sessionViewModel, roomId) {
constructor(sessionViewModel, roomIdOrLocalId) {
super(null);
this._statusSubscription = null;
this._sessionViewModel = sessionViewModel;
this.id = roomId;
this.id = roomIdOrLocalId;
}
/**
@ -48,7 +49,7 @@ export class RoomViewModelObservable extends ObservableValue {
are called in that case.
*/
async initialize() {
const {session} = this._sessionViewModel._sessionContainer;
const {session} = this._sessionViewModel._client;
const statusObservable = await session.observeRoomStatus(this.id);
this.set(await this._statusToViewModel(statusObservable.get()));
this._statusSubscription = statusObservable.subscribe(async status => {
@ -59,11 +60,21 @@ export class RoomViewModelObservable extends ObservableValue {
}
async _statusToViewModel(status) {
if (status.invited) {
if (status & RoomStatus.Replaced) {
if (status & RoomStatus.BeingCreated) {
const {session} = this._sessionViewModel._client;
const roomBeingCreated = session.roomsBeingCreated.get(this.id);
this._sessionViewModel.notifyRoomReplaced(roomBeingCreated.id, roomBeingCreated.roomId);
} else {
throw new Error("Don't know how to replace a room with this status: " + (status ^ RoomStatus.Replaced));
}
} else if (status & RoomStatus.BeingCreated) {
return this._sessionViewModel._createRoomBeingCreatedViewModel(this.id);
} else if (status & RoomStatus.Invited) {
return this._sessionViewModel._createInviteViewModel(this.id);
} else if (status.joined) {
return this._sessionViewModel._createRoomViewModel(this.id);
} else if (status.archived) {
} else if (status & RoomStatus.Joined) {
return this._sessionViewModel._createRoomViewModelInstance(this.id);
} else if (status & RoomStatus.Archived) {
return await this._sessionViewModel._createArchivedRoomViewModel(this.id);
} else {
return this._sessionViewModel._createUnknownRoomViewModel(this.id);
@ -77,4 +88,4 @@ export class RoomViewModelObservable extends ObservableValue {
this.unsubscribeAll();
this.get()?.dispose();
}
}
}

View File

@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../ViewModel.js";
import {createEnum} from "../../utils/enum.js";
import {ConnectionStatus} from "../../matrix/net/Reconnector.js";
import {ViewModel} from "../ViewModel";
import {createEnum} from "../../utils/enum";
import {ConnectionStatus} from "../../matrix/net/Reconnector";
import {SyncStatus} from "../../matrix/Sync.js";
const SessionStatus = createEnum(
@ -36,7 +36,7 @@ export class SessionStatusViewModel extends ViewModel {
this._reconnector = reconnector;
this._status = this._calculateState(reconnector.connectionStatus.get(), sync.status.get());
this._session = session;
this._setupSessionBackupUrl = this.urlCreator.urlForSegment("settings");
this._setupKeyBackupUrl = this.urlCreator.urlForSegment("settings");
this._dismissSecretStorage = false;
}
@ -44,17 +44,17 @@ export class SessionStatusViewModel extends ViewModel {
const update = () => this._updateStatus();
this.track(this._sync.status.subscribe(update));
this.track(this._reconnector.connectionStatus.subscribe(update));
this.track(this._session.needsSessionBackup.subscribe(() => {
this.track(this._session.needsKeyBackup.subscribe(() => {
this.emitChange();
}));
}
get setupSessionBackupUrl () {
return this._setupSessionBackupUrl;
get setupKeyBackupUrl () {
return this._setupKeyBackupUrl;
}
get isShown() {
return (this._session.needsSessionBackup.get() && !this._dismissSecretStorage) || this._status !== SessionStatus.Syncing;
return (this._session.needsKeyBackup.get() && !this._dismissSecretStorage) || this._status !== SessionStatus.Syncing;
}
get statusLabel() {
@ -70,7 +70,7 @@ export class SessionStatusViewModel extends ViewModel {
case SessionStatus.SyncError:
return this.i18n`Sync failed because of ${this._sync.error}`;
}
if (this._session.needsSessionBackup.get()) {
if (this._session.needsKeyBackup.get()) {
return this.i18n`Set up session backup to decrypt older messages.`;
}
return "";
@ -135,7 +135,7 @@ export class SessionStatusViewModel extends ViewModel {
get isSecretStorageShown() {
// TODO: we need a model here where we can have multiple messages queued up and their buttons don't bleed into each other.
return this._status === SessionStatus.Syncing && this._session.needsSessionBackup.get() && !this._dismissSecretStorage;
return this._status === SessionStatus.Syncing && this._session.needsKeyBackup.get() && !this._dismissSecretStorage;
}
get canDismiss() {

View File

@ -19,31 +19,31 @@ import {LeftPanelViewModel} from "./leftpanel/LeftPanelViewModel.js";
import {RoomViewModel} from "./room/RoomViewModel.js";
import {UnknownRoomViewModel} from "./room/UnknownRoomViewModel.js";
import {InviteViewModel} from "./room/InviteViewModel.js";
import {RoomBeingCreatedViewModel} from "./room/RoomBeingCreatedViewModel.js";
import {LightboxViewModel} from "./room/LightboxViewModel.js";
import {SessionStatusViewModel} from "./SessionStatusViewModel.js";
import {RoomGridViewModel} from "./RoomGridViewModel.js";
import {SettingsViewModel} from "./settings/SettingsViewModel.js";
import {ViewModel} from "../ViewModel.js";
import {CreateRoomViewModel} from "./CreateRoomViewModel.js";
import {ViewModel} from "../ViewModel";
import {RoomViewModelObservable} from "./RoomViewModelObservable.js";
import {RightPanelViewModel} from "./rightpanel/RightPanelViewModel.js";
export class SessionViewModel extends ViewModel {
constructor(options) {
super(options);
const {sessionContainer} = options;
this._sessionContainer = this.track(sessionContainer);
const {client} = options;
this._client = this.track(client);
this._sessionStatusViewModel = this.track(new SessionStatusViewModel(this.childOptions({
sync: sessionContainer.sync,
reconnector: sessionContainer.reconnector,
session: sessionContainer.session,
})));
this._leftPanelViewModel = this.track(new LeftPanelViewModel(this.childOptions({
invites: this._sessionContainer.session.invites,
rooms: this._sessionContainer.session.rooms
sync: client.sync,
reconnector: client.reconnector,
session: client.session,
})));
this._leftPanelViewModel = this.track(new LeftPanelViewModel(this.childOptions({session: this._client.session})));
this._settingsViewModel = null;
this._roomViewModelObservable = null;
this._gridViewModel = null;
this._createRoomViewModel = null;
this._setupNavigation();
}
@ -75,6 +75,12 @@ export class SessionViewModel extends ViewModel {
}));
this._updateSettings(settings.get());
const createRoom = this.navigation.observe("create-room");
this.track(createRoom.subscribe(createRoomOpen => {
this._updateCreateRoom(createRoomOpen);
}));
this._updateCreateRoom(createRoom.get());
const lightbox = this.navigation.observe("lightbox");
this.track(lightbox.subscribe(eventId => {
this._updateLightbox(eventId);
@ -88,7 +94,7 @@ export class SessionViewModel extends ViewModel {
}
get id() {
return this._sessionContainer.sessionId;
return this._client.sessionId;
}
start() {
@ -96,7 +102,7 @@ export class SessionViewModel extends ViewModel {
}
get activeMiddleViewModel() {
return this._roomViewModelObservable?.get() || this._gridViewModel || this._settingsViewModel;
return this._roomViewModelObservable?.get() || this._gridViewModel || this._settingsViewModel || this._createRoomViewModel;
}
get roomGridViewModel() {
@ -119,11 +125,14 @@ export class SessionViewModel extends ViewModel {
return this._roomViewModelObservable?.get();
}
get rightPanelViewModel() {
return this._rightPanelViewModel;
}
get createRoomViewModel() {
return this._createRoomViewModel;
}
_updateGrid(roomIds) {
const changed = !(this._gridViewModel && roomIds);
const currentRoomId = this.navigation.path.get("room");
@ -162,8 +171,8 @@ export class SessionViewModel extends ViewModel {
}
}
_createRoomViewModel(roomId) {
const room = this._sessionContainer.session.rooms.get(roomId);
_createRoomViewModelInstance(roomId) {
const room = this._client.session.rooms.get(roomId);
if (room) {
const roomVM = new RoomViewModel(this.childOptions({room}));
roomVM.load();
@ -175,12 +184,12 @@ export class SessionViewModel extends ViewModel {
_createUnknownRoomViewModel(roomIdOrAlias) {
return new UnknownRoomViewModel(this.childOptions({
roomIdOrAlias,
session: this._sessionContainer.session,
session: this._client.session,
}));
}
async _createArchivedRoomViewModel(roomId) {
const room = await this._sessionContainer.session.loadArchivedRoom(roomId);
const room = await this._client.session.loadArchivedRoom(roomId);
if (room) {
const roomVM = new RoomViewModel(this.childOptions({room}));
roomVM.load();
@ -190,11 +199,22 @@ export class SessionViewModel extends ViewModel {
}
_createInviteViewModel(roomId) {
const invite = this._sessionContainer.session.invites.get(roomId);
const invite = this._client.session.invites.get(roomId);
if (invite) {
return new InviteViewModel(this.childOptions({
invite,
mediaRepository: this._sessionContainer.session.mediaRepository,
mediaRepository: this._client.session.mediaRepository,
}));
}
return null;
}
_createRoomBeingCreatedViewModel(localId) {
const roomBeingCreated = this._client.session.roomsBeingCreated.get(localId);
if (roomBeingCreated) {
return new RoomBeingCreatedViewModel(this.childOptions({
roomBeingCreated,
mediaRepository: this._client.session.mediaRepository,
}));
}
return null;
@ -230,13 +250,23 @@ export class SessionViewModel extends ViewModel {
}
if (settingsOpen) {
this._settingsViewModel = this.track(new SettingsViewModel(this.childOptions({
session: this._sessionContainer.session,
client: this._client,
})));
this._settingsViewModel.load();
}
this.emitChange("activeMiddleViewModel");
}
_updateCreateRoom(createRoomOpen) {
if (this._createRoomViewModel) {
this._createRoomViewModel = this.disposeTracked(this._createRoomViewModel);
}
if (createRoomOpen) {
this._createRoomViewModel = this.track(new CreateRoomViewModel(this.childOptions({session: this._client.session})));
}
this.emitChange("activeMiddleViewModel");
}
_updateLightbox(eventId) {
if (this._lightboxViewModel) {
this._lightboxViewModel = this.disposeTracked(this._lightboxViewModel);
@ -254,7 +284,7 @@ export class SessionViewModel extends ViewModel {
_roomFromNavigation() {
const roomId = this.navigation.path.get("room")?.value;
const room = this._sessionContainer.session.rooms.get(roomId);
const room = this._client.session.rooms.get(roomId);
return room;
}
@ -263,9 +293,12 @@ export class SessionViewModel extends ViewModel {
const enable = !!this.navigation.path.get("right-panel")?.value;
if (enable) {
const room = this._roomFromNavigation();
this._rightPanelViewModel = this.track(new RightPanelViewModel(this.childOptions({room})));
this._rightPanelViewModel = this.track(new RightPanelViewModel(this.childOptions({room, session: this._client.session})));
}
this.emitChange("rightPanelViewModel");
}
notifyRoomReplaced(oldId, newId) {
this.navigation.push("room", newId);
}
}

View File

@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export class ReplayDetectionEntry {
constructor(sessionId, messageIndex, event) {
this.sessionId = sessionId;
this.messageIndex = messageIndex;
this.eventId = event.event_id;
this.timestamp = event.origin_server_ts;
}
export function imageToInfo(image) {
return {
w: image.width,
h: image.height,
mimetype: image.blob.mimeType,
size: image.blob.size
};
}

View File

@ -15,10 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js";
import {ViewModel} from "../../ViewModel.js";
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar";
import {ViewModel} from "../../ViewModel";
const KIND_ORDER = ["invite", "room"];
const KIND_ORDER = ["roomBeingCreated", "invite", "room"];
export class BaseTileViewModel extends ViewModel {
constructor(options) {

View File

@ -1,5 +1,4 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
@ -16,6 +15,7 @@ limitations under the License.
*/
import {BaseTileViewModel} from "./BaseTileViewModel.js";
import {comparePrimitive} from "./common";
export class InviteTileViewModel extends BaseTileViewModel {
constructor(options) {
@ -25,31 +25,40 @@ export class InviteTileViewModel extends BaseTileViewModel {
this._url = this.urlCreator.openRoomActionUrl(this._invite.id);
}
get busy() {
return this._invite.accepting || this._invite.rejecting;
}
get kind() {
return "invite";
}
get url() {
return this._url;
}
get busy() { return this._invite.accepting || this._invite.rejecting; }
get kind() { return "invite"; }
get url() { return this._url; }
get name() { return this._invite.name; }
get isHighlighted() { return true; }
get isUnread() { return true; }
get badgeCount() { return this.i18n`!`; }
get _avatarSource() { return this._invite; }
/** very important that sorting order is stable and that comparing
* to itself always returns 0, otherwise SortedMapList will
* remove the wrong children, etc ... */
compare(other) {
const parentComparison = super.compare(other);
if (parentComparison !== 0) {
return parentComparison;
}
return other._invite.timestamp - this._invite.timestamp;
}
get name() {
return this._invite.name;
}
get _avatarSource() {
return this._invite;
const timeDiff = other._invite.timestamp - this._invite.timestamp;
if (timeDiff !== 0) {
return timeDiff;
}
return comparePrimitive(this._invite.id, other._invite.id);
}
}
export function tests() {
return {
"test compare with timestamp": assert => {
const urlCreator = {openRoomActionUrl() { return "";}}
const vm1 = new InviteTileViewModel({invite: {timestamp: 500, id: "1"}, urlCreator});
const vm2 = new InviteTileViewModel({invite: {timestamp: 250, id: "2"}, urlCreator});
assert(vm1.compare(vm2) < 0);
assert(vm2.compare(vm1) > 0);
assert.equal(vm1.compare(vm1), 0);
},
}
}

View File

@ -15,42 +15,47 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../../ViewModel.js";
import {ViewModel} from "../../ViewModel";
import {RoomTileViewModel} from "./RoomTileViewModel.js";
import {InviteTileViewModel} from "./InviteTileViewModel.js";
import {RoomBeingCreatedTileViewModel} from "./RoomBeingCreatedTileViewModel.js";
import {RoomFilter} from "./RoomFilter.js";
import {ApplyMap} from "../../../observable/map/ApplyMap.js";
import {addPanelIfNeeded} from "../../navigation/index.js";
import {addPanelIfNeeded} from "../../navigation/index";
export class LeftPanelViewModel extends ViewModel {
constructor(options) {
super(options);
const {rooms, invites} = options;
this._tileViewModelsMap = this._mapTileViewModels(rooms, invites);
const {session} = options;
this._tileViewModelsMap = this._mapTileViewModels(session.roomsBeingCreated, session.invites, session.rooms);
this._tileViewModelsFilterMap = new ApplyMap(this._tileViewModelsMap);
this._tileViewModels = this._tileViewModelsFilterMap.sortValues((a, b) => a.compare(b));
this._currentTileVM = null;
this._setupNavigation();
this._closeUrl = this.urlCreator.urlForSegment("session");
this._settingsUrl = this.urlCreator.urlForSegment("settings");
this._createRoomUrl = this.urlCreator.urlForSegment("create-room");
}
_mapTileViewModels(rooms, invites) {
_mapTileViewModels(roomsBeingCreated, invites, rooms) {
// join is not commutative, invites will take precedence over rooms
return invites.join(rooms).mapValues((roomOrInvite, emitChange) => {
const allTiles = invites.join(roomsBeingCreated, rooms).mapValues((item, emitChange) => {
let vm;
if (roomOrInvite.isInvite) {
vm = new InviteTileViewModel(this.childOptions({invite: roomOrInvite, emitChange}));
if (item.isBeingCreated) {
vm = new RoomBeingCreatedTileViewModel(this.childOptions({roomBeingCreated: item, emitChange}));
} else if (item.isInvite) {
vm = new InviteTileViewModel(this.childOptions({invite: item, emitChange}));
} else {
vm = new RoomTileViewModel(this.childOptions({room: roomOrInvite, emitChange}));
vm = new RoomTileViewModel(this.childOptions({room: item, emitChange}));
}
const isOpen = this.navigation.path.get("room")?.value === roomOrInvite.id;
const isOpen = this.navigation.path.get("room")?.value === item.id;
if (isOpen) {
vm.open();
this._updateCurrentVM(vm);
}
return vm;
});
return allTiles;
}
_updateCurrentVM(vm) {
@ -69,6 +74,8 @@ export class LeftPanelViewModel extends ViewModel {
return this._settingsUrl;
}
get createRoomUrl() { return this._createRoomUrl; }
_setupNavigation() {
const roomObservable = this.navigation.observe("room");
this.track(roomObservable.subscribe(roomId => this._open(roomId)));
@ -127,11 +134,14 @@ export class LeftPanelViewModel extends ViewModel {
query = query.trim();
if (query.length === 0) {
this.clearFilter();
return false;
} else {
const startFiltering = !this._tileViewModelsFilterMap.hasApply();
const filter = new RoomFilter(query);
this._tileViewModelsFilterMap.setApply((roomId, vm) => {
vm.hidden = !filter.matches(vm);
});
return startFiltering;
}
}
}

View File

@ -0,0 +1,70 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {BaseTileViewModel} from "./BaseTileViewModel.js";
import {comparePrimitive} from "./common";
export class RoomBeingCreatedTileViewModel extends BaseTileViewModel {
constructor(options) {
super(options);
const {roomBeingCreated} = options;
this._roomBeingCreated = roomBeingCreated;
this._url = this.urlCreator.openRoomActionUrl(this._roomBeingCreated.id);
}
get busy() { return !this._roomBeingCreated.error; }
get kind() { return "roomBeingCreated"; }
get isHighlighted() { return !this.busy; }
get badgeCount() { return !this.busy && this.i18n`Failed`; }
get url() { return this._url; }
get name() { return this._roomBeingCreated.name; }
get _avatarSource() { return this._roomBeingCreated; }
/** very important that sorting order is stable and that comparing
* to itself always returns 0, otherwise SortedMapList will
* remove the wrong children, etc ... */
compare(other) {
const parentCmp = super.compare(other);
if (parentCmp !== 0) {
return parentCmp;
}
const nameCmp = comparePrimitive(this.name, other.name);
if (nameCmp === 0) {
return comparePrimitive(this._roomBeingCreated.id, other._roomBeingCreated.id);
} else {
return nameCmp;
}
}
avatarUrl(size) {
// allow blob url which doesn't need mxc => http resolution
return this._roomBeingCreated.avatarBlobUrl ?? super.avatarUrl(size);
}
}
export function tests() {
return {
"test compare with names": assert => {
const urlCreator = {openRoomActionUrl() { return "";}}
const vm1 = new RoomBeingCreatedTileViewModel({roomBeingCreated: {name: "A", id: "1"}, urlCreator});
const vm2 = new RoomBeingCreatedTileViewModel({roomBeingCreated: {name: "B", id: "2"}, urlCreator});
assert(vm1.compare(vm2) < 0);
assert(vm2.compare(vm1) > 0);
assert.equal(vm1.compare(vm1), 0);
},
}
}

View File

@ -33,6 +33,9 @@ export class RoomTileViewModel extends BaseTileViewModel {
return this._url;
}
/** very important that sorting order is stable and that comparing
* to itself always returns 0, otherwise SortedMapList will
* remove the wrong children, etc ... */
compare(other) {
const parentComparison = super.compare(other);
if (parentComparison !== 0) {

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,16 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export class AccountDataStore {
constructor(store) {
this._store = store;
}
async get(type) {
return await this._store.get(type);
}
set(event) {
return this._store.put(event);
}
export function comparePrimitive(a, b) {
if (a === b) {
return 0;
} else {
return a < b ? -1 : 1;
}
}

View File

@ -14,8 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../../ViewModel.js";
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js";
import {ViewModel} from "../../ViewModel";
import {RoomType} from "../../../matrix/room/common";
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar";
export class MemberDetailsViewModel extends ViewModel {
constructor(options) {
@ -25,6 +26,7 @@ export class MemberDetailsViewModel extends ViewModel {
this._member = this._observableMember.get();
this._isEncrypted = options.isEncrypted;
this._powerLevelsObservable = options.powerLevelsObservable;
this._session = options.session;
this.track(this._powerLevelsObservable.subscribe(() => this._onPowerLevelsChange()));
this.track(this._observableMember.subscribe( () => this._onMemberChange()));
}
@ -77,6 +79,19 @@ export class MemberDetailsViewModel extends ViewModel {
}
get linkToUser() {
return `https://matrix.to/#/${this._member.userId}`;
return `https://matrix.to/#/${encodeURIComponent(this._member.userId)}`;
}
async openDirectMessage() {
const room = this._session.findDirectMessageForUserId(this.userId);
let roomId = room?.id;
if (!roomId) {
const roomBeingCreated = await this._session.createRoom({
type: RoomType.DirectMessage,
invites: [this.userId]
});
roomId = roomBeingCreated.id;
}
this.navigation.push("room", roomId);
}
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../../ViewModel.js";
import {ViewModel} from "../../ViewModel";
import {MemberTileViewModel} from "./MemberTileViewModel.js";
import {createMemberComparator} from "./members/comparator.js";
import {Disambiguator} from "./members/disambiguator.js";

View File

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../../ViewModel.js";
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js";
import {ViewModel} from "../../ViewModel";
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar";
export class MemberTileViewModel extends ViewModel {
constructor(options) {
@ -47,7 +47,8 @@ export class MemberTileViewModel extends ViewModel {
}
get detailsUrl() {
return `${this.urlCreator.urlUntilSegment("room")}/member/${this._member.userId}`;
const roomId = this.navigation.path.get("room").value;
return `${this.urlCreator.openRoomActionUrl(roomId)}/member/${this._member.userId}`;
}
_updatePreviousName(newName) {

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../../ViewModel.js";
import {ViewModel} from "../../ViewModel";
import {RoomDetailsViewModel} from "./RoomDetailsViewModel.js";
import {MemberListViewModel} from "./MemberListViewModel.js";
import {MemberDetailsViewModel} from "./MemberDetailsViewModel.js";
@ -23,6 +23,7 @@ export class RightPanelViewModel extends ViewModel {
constructor(options) {
super(options);
this._room = options.room;
this._session = options.session;
this._members = null;
this._setupNavigation();
}
@ -48,7 +49,13 @@ export class RightPanelViewModel extends ViewModel {
}
const isEncrypted = this._room.isEncrypted;
const powerLevelsObservable = await this._room.observePowerLevels();
return {observableMember, isEncrypted, powerLevelsObservable, mediaRepository: this._room.mediaRepository};
return {
observableMember,
isEncrypted,
powerLevelsObservable,
mediaRepository: this._room.mediaRepository,
session: this._session
};
}
_setupNavigation() {

View File

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../../ViewModel.js";
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js";
import {ViewModel} from "../../ViewModel";
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar";
export class RoomDetailsViewModel extends ViewModel {
constructor(options) {

View File

@ -14,22 +14,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../../ViewModel.js";
import {ViewModel} from "../../ViewModel";
export class ComposerViewModel extends ViewModel {
constructor(roomVM) {
super();
super(roomVM.options);
this._roomVM = roomVM;
this._isEmpty = true;
this._replyVM = null;
}
setReplyingTo(entry) {
const changed = this._replyVM?.internalId !== entry?.asEventKey().toString();
const changed = new Boolean(entry) !== new Boolean(this._replyVM) || !this._replyVM?.id.equals(entry.asEventKey());
if (changed) {
this._replyVM = this.disposeTracked(this._replyVM);
if (entry) {
this._replyVM = this.track(this._roomVM._createTile(entry));
this._replyVM.notifyVisible();
}
this.emitChange("replyViewModel");
this.emit("focus");

View File

@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js";
import {ViewModel} from "../../ViewModel.js";
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar";
import {ViewModel} from "../../ViewModel";
export class InviteViewModel extends ViewModel {
constructor(options) {

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ViewModel} from "../../ViewModel.js";
import {ViewModel} from "../../ViewModel";
export class LightboxViewModel extends ViewModel {
constructor(options) {

View File

@ -1,9 +1,17 @@
# "Room" view models
InviteViewModel and RoomViewModel are interchangebly used as "room view model":
- SessionViewModel.roomViewModel can be an instance of either
- RoomGridViewModel.roomViewModelAt(i) can return an instance of either
InviteViewModel, RoomViewModel and RoomBeingCreatedViewModel are interchangebly used as "room view model":
- SessionViewModel.roomViewModel can be an instance of any
- RoomGridViewModel.roomViewModelAt(i) can return an instance of any
This is because they are accessed by the same url and need to transition into each other, in these two locations. Having two methods, especially in RoomGridViewModel would have been more cumbersome, even though this is not in line with how different view models are exposed in SessionViewModel.
They share an `id` and `kind` property, the latter can be used to differentiate them from the view, and a `focus` method.
Once we convert this folder to typescript, we should use this interface for all the view models:
```ts
interface IGridItemViewModel {
id: string;
kind: string;
focus();
}
```

View File

@ -0,0 +1,75 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar";
import {ViewModel} from "../../ViewModel";
export class RoomBeingCreatedViewModel extends ViewModel {
constructor(options) {
super(options);
const {roomBeingCreated, mediaRepository} = options;
this._roomBeingCreated = roomBeingCreated;
this._mediaRepository = mediaRepository;
this._onRoomChange = this._onRoomChange.bind(this);
this._closeUrl = this.urlCreator.urlUntilSegment("session");
this._roomBeingCreated.on("change", this._onRoomChange);
}
get kind() { return "roomBeingCreated"; }
get closeUrl() { return this._closeUrl; }
get name() { return this._roomBeingCreated.name; }
get id() { return this._roomBeingCreated.id; }
get isEncrypted() { return this._roomBeingCreated.isEncrypted; }
get error() {
const {error} = this._roomBeingCreated;
if (error) {
if (error.name === "ConnectionError") {
return this.i18n`You seem to be offline`;
} else {
return error.message;
}
}
return "";
}
get avatarLetter() { return avatarInitials(this.name); }
get avatarColorNumber() { return getIdentifierColorNumber(this._roomBeingCreated.avatarColorId); }
get avatarTitle() { return this.name; }
avatarUrl(size) {
// allow blob url which doesn't need mxc => http resolution
return this._roomBeingCreated.avatarBlobUrl ??
getAvatarHttpUrl(this._roomBeingCreated.avatarUrl, size, this.platform, this._mediaRepository);
}
focus() {}
_onRoomChange() {
this.emitChange();
}
cancel() {
this._roomBeingCreated.cancel();
// navigate away from the room
this.navigation.applyPath(this.navigation.path.until("session"));
}
dispose() {
super.dispose();
this._roomBeingCreated.off("change", this._onRoomChange);
}
}

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