I was emotional and wrong. After revisiting multiple other chats, all of
them do it this way. Adding reactions to existing ones was what I was
missing. the first reaction can be in the top right bar.
For the config.json (server-level default for new users):
```json
{
"defaultSettings": {
"externalImages": "homeserver"
}
}
```
User's own choice in the UI always overrides the config.json default.
* chore: add matrixrooms.info to directory list
matrixrooms.info is a directory of all public Matrix rooms it can find,
regardless of homeserver. It is much larger than the morg directory,
so is more useful as a general search
* chore: install deps related to semantic release
* chore: add husky config
* ci: add a script to update version number on new release
* ci: update ci/cd to include semantic release changes
* chore: merge dev to semantic-release
Allow using filenames in codeblocks
- If there is a dot in the language name, we instead treat the first line after ``` as the filename and everything after the last dot as the language
- we use a custom "data-label" attribute on the code block to specify the name of the file (so only compatible with cinny from this point onwards)
* Show large image overlay when clicking url preview thumbnail
* Move image overlay into its own component
* Move ImageOverlay props into extended type
* Remove export for internal type
* fix member button tooltip in call room header
* hide sticker button in room input based on chat window width
* render camera on off data instead of duplicate join messages
* hide duplicate call member changes instead of rendering as video status
* fix prescreen message spacing
* allow user to end call if error when loading
* show call support missing error if livekit server is not provided
* prevent joining from nav item double click if no livekit support
Fix recent emoji are not getting saved
Refactor recent emoji retrieval to ensure structured cloning and proper type checking. The sdk was not updating account data because we are mutating the original and it compare and early return if found same.
* add mutation observer hok
* add hook to read speaking member by observing iframe content
* display speaking member name in call status bar and improve layout
* fix shrining
* add joined call control bar
* remove chat toggle from room header
* change member speaking icon to mic
* fix joined call control appear in other
* show spinner on end call button
* hide call statusbar for mobile view when room is selected
* make call statusbar more mobile friendly
* fix call status bar item align
* add mutation observer hok
* add hook to read speaking member by observing iframe content
* display speaking member name in call status bar and improve layout
* fix shrining
* Add users on the nav to showcase call activity and who is in the call
* add check to prevent DCing from the call you're currently in...
* Add avatar and username for the space (needs to be moved into RoomNavItem proper)
* Add background variant to buttons
* Update hook to keep method signature (accepting an array of Rooms instead) to support multiple room event tracking of the same event
* Add state listener so the call activity is real time updated on joins/leaves within the space
* Add RoomNavUser for displaying the user avatar + name in the nav for a visual of call activity and participants
* rename CallNavBottom to CallNavStatus
* Rename callnavbottom and fix linking implementation to actually be correct
* temp fix to allow the status to be cleared in some way
* re-add background to active call link button
* prepare to feed this to child elements for visibility handling
* loosely provide nav handling for testing refactoring
* Add CallView
* Update to funnel Outlet context through for Call handling (might not be the best approach, but removes code replication in PersistentCallContainer where we were remaking the roomview entirely)
* update client layout to funnel outlet the iframes for the call container
* funnel through just iframe for now for testing sake
* Update room to use CallView
* Pass forward the backupIframeRef now
* remove unused params
* Add backupIframeRef so we can re-add the lobby screen for non-joined calls (for viewing their text channels)
* Remove unused imports and restructure to support being parent to clientlayout
* Re-add layout as we're no longer oddly passing outlet context
* swap to using ref provider context from to connect to persistentcallcontainer more directly
* Revert to original code as we've moved calling to be more inline with design
* Revert to original code as we've moved the outlet context passing out and made more direct use of the ref
* Fix unexpected visibility in non-room areas
* correctly provide visibility
* re-add mobile chat handling
* Improve call room view stability
* split into two refs
* add ViewedRoom usage
* Disable
* add roomViewId and related
* (broken) juggle the iframe states proper... still needs fixing
* Conditionals to manage the active iframe state better
* add navigateRoom to be in both conditions for the nav button
* Fix the view to correctly display the active iframe based on which is currently hosting the active call (juggling views)
* Testing the iframe juggling. Seems to work for the first and second joins... so likely on the right path with this
* add url as a param for widget url
* fix backup iframe visibility
* Much closer to the call state handling we want w/ hangups and joins
* Fix the position of the member drawer to its correct location
* Ensure drawer doesn't appear in call room
* Better handling of the isCallActive in the join handler
* Add ideal call room join behavior where text rooms to call room simply joins, but doesn't swap current view
* Fix mobile call room default behavior from auto-join to displaying lobby
* swap call status to be bound to call state and not active call id
* Remove clean room ID and add default handler for if no active call has existed yet, but user clicks on show chat
* Applies the correct changes to the call state and removes listeners of old active widget so we don't trigger hang ups on the new one (the element-call widget likes to spam the hang up response back several times for some reason long after you tell it to hang up)
* Remove superfluous comments and Date.now() that was causing loading... bug when widgetId desynced
* Remove Date.now() that was causing widgetId desync
* add listener clearing, camel case es lint rule exception, remove unneeded else statements
* Remove unused
* Add widgetId as a getWidgetUrl param
* Remove no longer needed files
* revert ternary expression change and add to dependency array
* add widgetId to correct pos in getWidgetUrl usage
* Remove CallActivation
* Move and rename RoomCallNavStatus
* update imports and dependency array
* Rename and clean up
* Moved CallProvider
* Fix spelling mistake
* Fix to use shorthand prop
* Remove unneeded logger.errors
* Fixes element-call embedded support (but it seems to run poorly)
* null the default url so that we fallback to the embedded version (would recommend hosting it until performance issue is determined)
* Fix vite build to place element-call correctly for embedded npm package support
* add vite preview as an npm script
* Move files to more correct location
* Add package-lock changes
* Set dep version to exact
* Fix path issue from moving file locations
* Sets initial states so the iframes don't cause the other to fail with the npm embedded package
* Revert navitem change
* Just check for state on both which should only occur at initial
* Fixes call initializing by default on mobile
* Provides correct behavior when call isn't active and no activeClientWidgetApi exists yet
* Corrects the state for the situations where both iframes are "active" (not necessarily visible)
* Reduce code reuse in handleJoin
* Seems to sort out the hangup status button bug the occurred after joining a call via lobby
* Re-add the default view current active room behavior
* Remove repetitive check
* Add storing widget for comparing with (since we already store room id and the clientWidgetApi anyway)
* Update rendering logic to clear up remaining rendering bug (straight to call -> lobby of another room and joining call from that interface -> lobby of that previous room and joining was leading to duplication of the user in lobbies. This was actually from listening to and acknowledging hangups from the viewed widget in CallProvider)
* Prevent null rooms from ever rendering
* This seems to manage the hangup state with the status bar button well enough that black screens should never be encountered
* Remove viewed room setting here and pass the room to hang up (seems state doesn't update fast enough otherwise)
* Remove unused
* Properly declare new hangup method sig
* Seems to avoid almost all invalid states (hang up while viewing another lobby and hitting join seems to black screen, sets the active call as the previous active room id, but does join the viewed room correctly)
* Fix for cases where you're viewing a lobby and hang up your existing call and try to join lobby (was not rendering for the correct room id, but was joining the correct call prior)
* Re-add intended switching behavior
* More correct filter (viewedRoom can return false on that compare in some cases)
* Seems to shore up the remaining state issues with the status bar hangup
* Fix formatting
* In widget hang up button should be handled correct now
* Solves the CHCH sequence issue, CLJH remains
* Fixes CLJH, found CCH
* Solves CCH. Looks like CLCH left
* A bit of an abomination, but adds a state counter to iteratively handle the diverse potential states (where a user can join from the nav bar or the join button, hang up from either as well, and account for the juggling iframes)
Black screens shouldn't be occurring now.
* Fix dependency array
* Technically corrects the hangup button in the widget, should be more precise though
* Bind the on messaging iframe for easier access in hangup/join handling
* Far cleaner and more sensible handling of the call window... I just really don't like the idea of sending a click event, but right now the element-call code treats preload/skipLobby hangups (sent from our end) as if they had no lobby at all and thus black screens. Other implementation was working around that without just sending a click event on the iframe's hangup button.
* Fixes a bug where if you left a call then went to a lobby and joined it didn't update the actual activeCallRoomId
* Fixes complaints of null contentDocument in iframe
* Update to use new icons (thank you)
* Remove unneeded prop
* Re-arrange more options and add checks for each option to see if it is a call room (probably should manage a state to see if a header is already on screen and provide a slightly modified visual based on that for call rooms)
* Invert icons to show the state instead of the action they will perform (more visual clarity)
* Update src/app/features/room-nav/RoomCallNavStatus.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomCallNavStatus.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomCallNavStatus.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomCallNavStatus.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomCallNavStatus.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomCallNavStatus.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomNavItem.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room/RoomViewHeader.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomNavItem.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomNavItem.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomNavItem.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomNavItem.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomNavItem.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomNavItem.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomNavUser.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomNavUser.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room-nav/RoomNavUser.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/pages/client/space/Space.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/call/CallView.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/call/CallView.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/call/CallView.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room/RoomView.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room/RoomView.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* adjust room header for calling
* Remove No Active Call text when not in a call
* update element-call version
* Update src/app/features/room/RoomViewHeader.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room/RoomViewHeader.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room/RoomViewHeader.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room/RoomViewHeader.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Update src/app/features/room/RoomViewHeader.tsx
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
* Revert most changes to Space.tsx
* Show call room even if category is collapsed
* changes to RoomNavItem, RoomNavUser and add useCallMembers
* Rename file, sprinkle in the magic one line for matrixRTCSession. and remove comment block
* swap userId to callMembership as a prop and add a nullchecked userId that uses the membership sender
* update references to use callMembership instead
* Simplify RoomNavUser
Discard future functionality since it probably won't exist in time for merging this PR
* Simplify RoomViewHeader.tsx
Remove unused UI elements that don't have implemented functionality. Replace custom function for checking if a room is direct with a standard hook.
* Update Room.tsx to accomodate restructuring of Room, RoomView and CallView
* Update RoomView.tsx to accomodate restructuring of Room, RoomView and CallView
* Update CallView.tsx to accomodate restructuring of Room, RoomView and CallView + suggested changes
* add call related permissions to room permissions
* bump element call to 0.16.3, apply cinny theme to element call ui, replace element call lobby (backup iframe) with custom ui and only use element call for the in-call ui
* update text spacing
* redo roomcallnavstatus ui, force user preferred mute/video states when first joining calls, update variable names and remove unnecessary logic
* set default mic state to enabled
* clean up ts/eslint errors
* remove debug logs
* format using prettier rules from project prettierrc
* fix: show call nav status while active call is ongoing
* fix: clean up call nav/call view console warnings
* fix: keep call media controls visible before joining
* fix: restore header icon button fill behavior
Fixes regression from b074d421b66eb4d8b600dfa55b967e6c4f783044.
* style: blend header and room input button styles in call nav
* fix page header background color on room view header
* fix: permissions and room icon resolution (#2)
* Initialize call state upon room creation for call rooms, remove subsequent useless permission
* handle case of missing call permissions
* use call icon for room item summary when room is call room
* replace previous icon src resolution function with a more robust approach
* replace usages of previous icon resolution function with new implementation
* fix room name not updating for a while when changed
* set up framework for room power level overrides upon room creation
* override join call permission to all members upon room creation
* fix broken usages of RoomIcon
* remove unneeded import
* remove unnecessary logic
* format with prettier
* feat: show connected/connecting call status
* fix: preserve navigation context when opening non-call rooms
* fix: reset room name state when room instance changes
* feat: Disable webcam by default using callIntent='audio'
* Add channel type selecor
* Add option for voice rooms, which for now sets the default selected
option in the creation modal
* Add proper support for room selection from the enu
* Move enums to `types.ts` and change icons selection to use
`getRoomIconSrc`
* fix: group duplicate conditions into one
* fix: typo
* refactor: rename kind/voice to access/type and simplify room creation
- rename CreateRoomVoice to CreateRoomType and modal voice state to type
- rename CreateRoomKind to CreateRoomAccess and KindSelector to AccessSelector
- propagate access/defaultAccess through create room and create space forms
- set voice room power levels via createRoom power_level_content_override
* refactor: unify join rule icon mapping and update call/space icons
- bump folds from 2.5.0 to 2.6.0
- replace separate room/space join-rule icon hooks with useJoinRuleIcons(roomType)
- route join-rule icons through getRoomIconSrc for consistent room type handling
- simplify getRoomIconSrc by removing the locked override path
- use VolumeHighGlobe for public call rooms and VolumeHighLock for private call rooms
* chore(deps): bump matrix-widget-api to 1.17 and remove react-sdk-module-api
* fix: adapt SmallWidget to matrix-widget-api 1.17.0 API
* fix: render call room chat only when chat panel is open
* fix(permissions): show call settings permissions only for call rooms
* refactor: remove redundant room-nav props/guards and minor naming cleanup
* fix: use PhoneDown icon for hang up action
* chore(hooks): remove unused useStateEvents hook
* fix(room): enable members drawer toggle in desktop call rooms
- show filled User icon when the drawer is open
* Revert "fix: adapt SmallWidget to matrix-widget-api 1.17.0 API"
This reverts commit a4c34eff8a.
* fix: semi-revert matrix-widget-api 1.17 bump and migrate to 1.13 API
* fix(call): wait for Element Call contentLoaded before widget handshake
- fixes not working on firefox
* fix missing imports
* improve create room type design and add beta badge for voice room
* add beta badge for voice room in space lobby
* fix create room modal title
* pass missing roomType param to roomicon component
* add roomtype
* Add deafen functionality (#2695)
* feat:(deafen functionality)
* feat:(reworked voice controls for deafen)
* ref:(use muted instead of volume for deafen)
* fix:(backpedal audio_enabled rename)
* ref:(renaming of deafened vars)
* add stack avatar component
* add call status bar - WIP
* remove call status from navigation drawer
* fix deprecated method use in use call members hook
* render new call status bar
* move call widget driver to plugins
* remove old status bar usage from navigation drawer
* add call session and joined hook
* remove unknown changes
* upgrade widget api
* add element call embed plugin
* remove unknown change
* add call embed atom
* add call embed hooks and context
* add call embed provider
* replace old call implementation
* stop joining other call on second click if already in a call
* refactor embed placement hook
* add merge border prop to sequence card
* add call preferences
* add prescreen to call view - WIP
* prevent joining new call if already in call
* make call layout adaptive
* render call chat as right panel
* show call members in prescreen
* render call join leave event in timeline
* remove unknown rewrite in docker-nginx file
* render call event without hidden event enable
---------
Co-authored-by: Gigiaj <gigiaboone@yahoo.com>
Co-authored-by: Jaggar <18173108+GigiaJ@users.noreply.github.com>
Co-authored-by: Gimle Larpes <97182804+GimleLarpes@users.noreply.github.com>
Co-authored-by: Gimle Larpes <gimlelarpes@gmail.com>
Co-authored-by: YoJames2019 <jamesclark1700@gmail.com>
Co-authored-by: YoJames2019 <yobiscuit0@gmail.com>
Co-authored-by: hazre <mail@haz.re>
Co-authored-by: haz <37149950+hazre@users.noreply.github.com>
Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Co-authored-by: James <49845975+YoJames2019@users.noreply.github.com>
Co-authored-by: James Reilly <jreilly1821@gmail.com>
Co-authored-by: Tymek <vonautymek@gmail.com>
Co-authored-by: Thedustbuster <92692948+Thedustbustr@users.noreply.github.com>
* Pin all the action deps to SHA
* Add more docker related action checks
* Limit Docker build platforms to linux/amd64
Updated Docker build action to target only linux/amd64 platform.
* request session info from sw if missing
* fix async session request in fetch
* respond fetch synchronously and add early check for non media requests (#2670)
* make sure we call respondWith synchronously
* simplify isMediaRequest in sw
* improve naming in sw
* get back baseUrl check into validMediaRequest
* pass original request into fetch in sw
* extract mediaPath util and performs checks properly
---------
Co-authored-by: mmmykhailo <35040944+mmmykhailo@users.noreply.github.com>
Previously markAsRead() only sent m.read receipts via sendReadReceipt().
This meant the read position was not persisted across page refreshes,
especially noticeable in bridged rooms.
Now uses setRoomReadMarkers() which sets both:
- m.fully_read marker (persistent read position)
- m.read receipt
Fixes issue where rooms would still show as unread after refresh.
fix: detect muted rooms with empty actions array
The mute detection was checking for `actions[0] === "dont_notify"` but
Cinny sets `actions: []` (empty array) when muting a room, which is
the correct behavior per Matrix spec where empty actions means no
notification.
This caused muted rooms to still show unread badges and contribute to
space badge counts.
Fixes the isMutedRule check to handle both:
- Empty actions array (current Matrix spec)
- "dont_notify" string (deprecated but may exist in older rules)
* Replace 'envs.net' with 'unredacted.org' in config
https://envs.net/ is shutting down their Matrix server
* Update defaultHomeserver and reorder servers list
* Remove 'monero.social' from homeserver list
* Add support for MSC4193: Spoilers on Media
* Clarify variable names and wording
* Restore list atom
* Improve spoilered image UX with autoload off
* Use `aria-pressed` to indicate attachment spoiler state
* Improve spoiler button tooltip wording, keep reveal button from conflicting with load errors
* Make it possible to mark videos as spoilers
* Allow videos to be marked as spoilers when uploaded
* Apply requested changes
* Show a loading spinner on spoiled media when unblurred
---------
Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
On most browsers, pressing Enter to end IME composition produces this
sequence of events:
* keydown (keycode 229, key Processing/Unidentified, isComposing true)
* compositionend
* keyup (keycode 13, key Enter, isComposing false)
On Safari, the sequence is different:
* compositionend
* keydown (keycode 229, key Enter, isComposing false)
* keyup (keycode 13, key Enter, isComposing false)
This causes Safari users to mistakenly send their messages when they
press Enter to confirm their choice in an IME.
The workaround is to treat the next keydown with keycode 229 as if it
were part of the IME composition period if it occurs within a short time
of the compositionend event.
Fixes#2103, but needs confirmation from a Safari user.
* Add arrow to message bubbles and improve spacing
* make bubble message avatar smaller
* add bubble layout for event content
* adjust bubble arrow
* fix missing return statement for event content
* hide bubble for event content
* add new arrow to bubble message
* fix avatar username relative alignment
* fix types
* fix code block header background
* revert avatar size and make arrow less sharp
* show event messages timestamp to right when bubble is hidden
* fix avatar base css
* move message header outside bubble
* fix event time appears on left in hidden bubles
* extract emoji search component
* extract emoji board tabs component
* extract sidebar component
* extract no stickers component
* create emoji/sticker preview atom
* extract component from emoji/sticker item and sidebar buttons
* fix image group icon not loading
* separate emojis and sticker groups logic
* extract layout and emoji group components
* add virtualization in emoji board groups
* fix scroll to alignment
* add new search modal
* remove search modal from searchTab
* fix member avatar load for space with 2 member
* use media authentication when rendering avatar
* fix hotkey for macos
* add @ in username
* replace subspace minus separator with em dash
* fix 0 displayed in invite with no timestamp
* support displaying invite reason for receiver
* show invite reason as compact message
* remove unused import
* revert: show invite reason as compact message
* remove unused import
* add new invite prompt
* WIP - support room version 12
* add room creators hook
* revert changes from powerlevels
* improve use room creators hook
* add hook to get dm users
* add options to add creators in create room/space
* add member item component in member drawer
* remove unused import
* extract member drawer header component
* get room creators as set only if room version support them
* add room permissions hook
* support room v12 creators power
* make predecessor event id optional
* add info about founders in permissions
* allow to create infinite powers to room creators
* allow everyone with permission to create infinite power
* handle additional creators in room upgrade
* add option to follow space tombstone
* WIP - new profile view
* render common rooms in user profile
* add presence component
* WIP - room user profile
* temp hide profile button
* show mutual rooms in spaces, rooms and direct messages categories
* add message button
* add option to change user powers in profile
* improve ban info and option to unban
* add share user button in user profile
* add option to block user in user profile
* improve blocked user alert body
* add moderation tool in user profile
* open profile view on left side in member drawer
* open new user profile in all places
* add new create room
* rename create room modal file
* default restrict access for space children in room create modal
* move create room kind selector to components
* add radii variant to sequence card component
* more more reusable create room logic to components
* add create space
* update address input description
* add new space modal
* fix add room button visible on left room in space lobby
* Add setting to enable 24-hour time format
* added hour24Clock to TimeProps
* Add incomplete dateFormatString setting
* Move 24-hour toggle to Appearance
* Add "Date & Time" subheading, cleanup after merge
* Add setting for date formatting
* Fix minor formatting and naming issues
* Document functions
* adress most comments
* add hint for date formatting
* add support for 24hr time to TimePicker
* prevent overflow on small displays
* add simple button to start a thread on reply
* force build
* remove useless actions
* add actions back
* change icon to ThreadPlus
* add button to context menu
* fix capital T
---------
Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
* Improve focus behaviour on search boxes and chats
* Implemented MR #2317
* Fix crash if canMessage is false
* Prepare for PR #2335
* disable autofocus on message field
* fix inaccessible space on alias change
* fix new room in space open in home
* allow opening space timeline
* hide event timeline feature behind dev tool
* add navToActivePath to clear cache function
* kick-ban all members by servername
* Add command for deleting multiple messages
* remove console logs and improve ban command description
* improve commands description
* add server acl command
* fix code highlight not working after editing in dev tools
* fix room setting crash in knock_restricted join rule
* only show knock & space member join rule for space children
* fix knock restricted icon and label
* add active theme context
* add chroma js library
* add hook for accessible tag color
* disable reply user color - temporary
* render user color based on tag in room timeline
* remove default tag icons
* move accessible color function to plugins
* render user power color in reply
* increase username weight in timeline
* add default color for member power level tag
* show red slash in power color badge with no color
* show power level color in room input reply
* show power level username color in notifications
* show power level color in notification reply
* show power level color in message search
* render power level color in room pin menu
* add toggle for legacy username colors
* drop over saturation from member default color
* change border color of power color badge
* show legacy username color in direct rooms
* WIP - add room settings dialog
* join rule setting - WIP
* show emojis & stickers in room settings - WIP
* restyle join rule switcher
* Merge branch 'dev' into new-room-settings
* add join rule hook
* open room settings from global state
* open new room settings from all places
* rearrange settings menu item
* add option for creating new image pack
* room devtools - WIP
* render room state events as list
* add option to open state event
* add option to edit state event
* refactor text area code editor into hook
* add option to send message and state event
* add cutout card component
* add hook for room account data
* display room account data - WIP
* refactor global account data editor component
* add account data editor in room
* fix font style in devtool
* show state events in compact form
* add option to delete room image pack
* add server badge component
* add member tile component
* render members in room settings
* add search in room settings member
* add option to reset member search
* add filter in room members
* fix member virtual item key
* remove color from serve badge in room members
* show room in settings
* fix loading indicator position
* power level tags in room setting - WIP
* generate fallback tag in backward compatible way
* add color picker
* add powers editor - WIP
* add props to stop adding emoji to recent usage
* add beta feature notice badge
* add types for power level tag icon
* refactor image pack rooms code to hook
* option for adding new power levels tags
* remove console log
* refactor power icon
* add option to edit power level tags
* remove power level from powers pill
* fix power level labels
* add option to delete power levels
* fix long power level name shrinks power integer
* room permissions - WIP
* add power level selector component
* add room permissions
* move user default permission setting to other group
* add power permission peek menu
* fix weigh of power switch text
* hide above for max power in permission switcher
* improve beta badge description
* render room profile in room settings
* add option to edit room profile
* make room topic input text area
* add option to enable room encryption in room settings
* add option to change message history visibility
* add option to change join rule
* add option for addresses in room settings
* close encryption dialog after enabling
* add hide activity toggle
* stop sending/receiving typing status
* send private read receipt when setting toggle is activated
* prevent showing read-receipt when feature toggle in on
* Remove reply fallbacks & add m.mentions
(WIP) the typing on line 301 and 303 needs fixing but apart from that this is mint
* Less jank typing
* Mention the reply author in m.mentions
* Improve typing
* Fix typing in m.mentions finder
* Correctly iterate through editor children, properly handle @room, ...
..., don't mention the reply author when the reply author is ourself, don't add own user IDs when mentioning intentionally
* Formatting
* Add intentional mentions to edited messages
* refactor reusable code and fix todo
* parse mentions from all nodes
---------
Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
* Add support for MSC4193: Spoilers on Media
* Clarify variable names and wording
* Restore list atom
* Improve spoilered image UX with autoload off
* Use `aria-pressed` to indicate attachment spoiler state
* Improve spoiler button tooltip wording, keep reveal button from conflicting with load errors
* add hook to fetch one level of space hierarchy
* add enable param to level hierarchy hook
* improve HierarchyItem types
* fix type errors in lobby
* load space hierarachy per level
* fix menu item visibility
* fix unknown spaces over federation
* show inaccessible rooms only to admins
* fix unknown room renders loading content twice
* fix unknown room visible to normal user if space all room are unknown
* show no rooms card if space does not have any room
* escape inline markdown character
* fix typo
* improve document around custom markdown plugin and add escape sequence utils
* recover inline escape sequences on edit
* remove escape sequences from plain text body
* use `s` for strike-through instead of del
* escape block markdown sequences
* fix remove escape sequence was not removing all slashes from plain text
* recover block sequences on edit
* remove limit from emoji autocomplete
* remove search limit from user mention
* remove limit from room mention autocomplete
* increase user search limit to 1000
* better search string selection for emoticons
* Corrected button title
Media would load automatically if the option is checked not the other way around.
* Update src/app/features/settings/general/General.tsx
* Update General.tsx
* Update General.tsx
---------
Co-authored-by: Krishan <33421343+kfiven@users.noreply.github.com>
* Add rendering image captions
* Handle sending captions for images
* Fix caption rendering on m.video and m.audio too
* Remove unused renderBody() parameter
* Fix m.file rendering body instead of filename where possible
* Add caption rendering for generic files
+ Fix video and audio not properly sending captions
* Use m.text for captions & render on demand
* Allow custom HTML in sending captions
* Don't *send* captions
* mvoe content const into renderCaption()
---------
Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Appeareantly Firefox (and maybe Chrome) won't let service workers take over requests from <video> and <audio> tags, so we just fetch the URL ourselves.
* fix set power level broken after sdk update
* add media authentication hook
* fix service worker types
* fix service worker not working in dev mode
* fix env mode check when registering sw
* chore: Bump matrix-js-sdk to 34.4.0
* feat: Authenticated media support
* chore: Use Vite PWA for service worker support
* fix: Fix Vite PWA SW entry point
Forget this. :P
* fix: Also add Nginx rewrite for sw.js
* fix: Correct Nginx rewrite
* fix: Add Netlify redirect for sw.js
Otherwise the generic SPA rewrite to index.html would take effect, breaking Service Worker.
* fix: Account for subpath when regisering service worker
* chore: Correct types
> Please read through [the Discussion rules](https://github.com/cinnyapp/cinny/discussions/2653) and check for both existing [Discussions](https://github.com/cinnyapp/cinny/discussions?discussions_q=) and [Issues](https://github.com/cinnyapp/cinny/issues?q=sort%3Areactions-desc) prior to opening a new Discussion.
- type:markdown
attributes:
value:"# Issue Details"
- type:textarea
attributes:
label:Issue Description
description:|
Provide a detailed description of the issue. Include relevant information, such as:
- The feature or configuration option you encounter the issue with.
- Screenshots, screen recordings, or other supporting media (as needed).
- If this is a regression of an existing issue that was closed or resolved, please include the previous item reference (Discussion, Issue, PR, commit) in your description.
placeholder:|
When I try to send a message in a room, the message doesn't appear in the timeline.
OR
The application crashes when I click on the settings button.
validations:
required:true
- type:textarea
attributes:
label:Expected Behavior
description:|
Describe how you expect Cinny to behave in this situation.
placeholder:|
I expected the message to appear in the room timeline immediately after sending.
OR
The settings panel should open smoothly without any crashes.
validations:
required:true
- type:textarea
attributes:
label:Actual Behavior
description:|
Describe how Cinny actually behaves in this situation. If it is not immediately obvious how the actual behavior differs from the expected behavior described above, please be sure to mention the deviation specifically.
placeholder:|
The application freezes for 3 seconds and then shows a white screen.
validations:
required:true
- type:textarea
attributes:
label:Reproduction Steps
description:|
Provide a detailed set of step-by-step instructions for reproducing this issue.
placeholder:|
1. Open Cinny and log in to my account
2. Navigate to the #general room
3. Type a message in the message box
4. Press Enter to send
5. Notice that the message doesn't appear in the timeline
validations:
required:true
- type:textarea
attributes:
label:Environement
description:|
Please provide information about your environment. Include the following:
- OS:
- Browser:
- Cinny Web Version: (app.cinny.in or self hosted)
- Cinny desktop Version: (appimage or deb or flatpak)
- Matrix Homeserver:
placeholder:|
- OS: Windows 11
- Browser: Chrome 120.0.6099.109
- Cinny Web Version: 3.2.0 (app.cinny.in or self hosted)
- Cinny desktop Version: 3.2.0 (appimage or deb or flatpak)
- Matrix Homeserver: matrix.org (Synapse 1.97.0)
render:text
validations:
required:true
- type:textarea
id:logs
attributes:
label:Relevant Logs
description:|
If applicable, add browser console logs to help explain your problem.
**To get browser console logs:**
- Chrome/Edge: Press F12 → Console tab
- Firefox: Press F12 → Console tab
- Safari: Develop → Show Web Inspector → Console
Please wrap large log outputs in code blocks with triple backticks (```).
placeholder:|
```
Error: Failed to send message
at MessageComposer.sendMessage (composer.js:245)
at HTMLButtonElement.onClick (composer.js:189)
TypeError: Cannot read property 'content' of undefined
at RoomTimeline.render (timeline.js:567)
```
render:shell
validations:
required:false
- type:textarea
attributes:
label:Additional context
description:|
Add any other context about the problem here (e.g., when did this start happening, does it happen on different homeservers, etc.)
placeholder:|
- This started happening after I updated to version 3.2.0
- It only happens in encrypted rooms, not in public rooms
- I've tried on both Firefox and Chrome with the same result
- It works fine on my phone using the same account
- This happens on all homeservers I've tested (matrix.org, mozilla.org)
validations:
required:false
- type:markdown
attributes:
value:|
# User Acknowledgements
> [!TIP]
> Use these links to review the existing Cinny [Discussions](https://github.com/cinnyapp/cinny/discussions?discussions_q=) and [Issues](https://github.com/cinnyapp/cinny/issues?q=sort%3Areactions-desc).
- type:checkboxes#add faqs in future
attributes:
label:"I acknowledge that:"
options:
- label:I have searched the Cinny repository (both open and closed Discussions and Issues) and confirm this is not a duplicate of an existing issue or discussion.
required:true
- label:I have checked the "Preview" tab on all text fields to ensure that everything looks right, and have wrapped all configuration and code in code blocks with a group of three backticks (` ``` `) on separate lines.
<!-- Please read https://github.com/ajbura/cinny/blob/dev/CONTRIBUTING.md before submitting your pull request -->
### Description
<!-- Please include a summary of the change. Please also include relevant motivation and context. List any dependencies that are required for this change. -->
Fixes #
#### Type of change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
### Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
if:(github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
@@ -18,7 +18,7 @@ Bug reports and feature suggestions must use descriptive and concise titles and
## Pull requests
> ### Legal Notice
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license. You will also be asked to [sign the CLA](https://github.com/cinnyapp/cla) upon submiting your pull request.
**NOTE: If you want to add new features, please discuss with maintainers before coding or opening a pull request.** This is to ensure that we are on same track and following our roadmap.
We are currently in the [process of replacing the matrix-js-sdk](https://github.com/cinnyapp/cinny/issues/257#issuecomment-3714406704) with our own SDK. As a result, we will not be accepting any pull requests until further notice.
* Web app is available at https://app.cinny.in and gets updated on each new release. The `dev` branch is continuously deployed at https://dev.cinny.in but keep in mind that it could have things broken.
The web app is available at [app.cinny.in](https://app.cinny.in/) and gets updated on each new release. The `dev` branch is continuously deployed at [dev.cinny.in](https://dev.cinny.in) but keep in mind that it could have things broken.
*You can also download our desktop app from [cinny-desktop repository](https://github.com/cinnyapp/cinny-desktop).
You can also download our desktop app from the [cinny-desktop repository](https://github.com/cinnyapp/cinny-desktop).
* To host Cinny on your own, download tarball of the app from [GitHub release](https://github.com/cinnyapp/cinny/releases/latest).
You can serve the application with a webserver of your choice by simply copying `dist/` directory to the webroot.
To set default Homeserver on login, register and Explore Community page, place a customized [`config.json`](config.json) in webroot of your choice.
You will also need to setup redirects to serve the assests. An example setting of redirects for netlify is done in [`netlify.toml`](netlify.toml). You can also set `hashRouter.enabled = true` in [`config.json`](config.json) if you have trouble setting redirects.
To deploy on subdirectory, you need to rebuild the app youself after updating the `base` path in [`build.config.ts`](build.config.ts). For example, if you want to deploy on `https://cinny.in/app`, then change `base: '/app'`.
## Self-hosting
To host Cinny on your own, simply download the tarball from [GitHub releases](https://github.com/cinnyapp/cinny/releases/latest), and serve the files from `dist/` using your preferred webserver. Alternatively, you can just pull the docker image from [DockerHub](https://hub.docker.com/r/ajbura/cinny) or [GitHub Container Registry](https://github.com/cinnyapp/cinny/pkgs/container/cinny).
*Alternatively you can just pull the [DockerHub image](https://hub.docker.com/r/ajbura/cinny) by:
```
docker pull ajbura/cinny
```
or [ghcr image](https://github.com/cinnyapp/cinny/pkgs/container/cinny) by:
```
docker pull ghcr.io/cinnyapp/cinny:latest
```
*The default homeservers and explore pages are defined in [`config.json`](config.json).
<details>
<summary>PGP Public Key to verify tarball</summary>
* You need to set up redirects to serve the assests. Example configurations; [netlify](netlify.toml), [nginx](contrib/nginx/cinny.domain.tld.conf), [caddy](contrib/caddy/caddyfile).
* If you have trouble configuring redirects you can [enable hash routing](config.json#L35) — the url in the browser will have a `/#/` between the domain and open channel (ie. `app.cinny.in/#/home/` instead of `app.cinny.in/home/`) but you won't have to configure your webserver.
* To deploy on subdirectory, you need to rebuild the app youself after updating the `base` path in [`build.config.ts`](build.config.ts).
* For example, if you want to deploy on `https://cinny.in/app`, then set `base: '/app'`.
<details><summary><b>PGP Public Key to verify tarball</b></summary>
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
@@ -87,8 +86,8 @@ mxFo+ioe/ABCufSmyqFye0psX3Sp
</details>
## Local development
> We recommend using a version manager as versions change very quickly. You will likely need to switch
between multiple Node.js versions based on the needs of different projects you're working on. [NVM on windows](https://github.com/coreybutler/nvm-windows#installation--upgrades) on Windows and [nvm](https://github.com/nvm-sh/nvm) on Linux/macOS are pretty good choices. Recommended nodejs version is Iron LTS (v20).
> [!TIP]
> We recommend using a version manager as versions change very quickly. You will likely need to switch between multiple Node.js versions based on the needs of different projects you're working on. [NVM on windows](https://github.com/coreybutler/nvm-windows#installation--upgrades) on Windows and [nvm](https://github.com/nvm-sh/nvm) on Linux/macOS are pretty good choices. Recommended nodejs version is Krypton LTS (v24.13.1).
Execute the following commands to start a development server:
this one should slowly introduce changes and features that are useful for a homeserver, and go even further into the direction of replacing teamspeak or discord.
it does not try to keep up the typical matrix approach to have overengineered decisions in the chat, or avoid all kinds of exploits that are dangerous for federated servers, but less impactful for a single-server-instance.
It still tries to _respect_ matrix as a tool, that can do all these things.
In this owl folder should go all documents we produce for this Endeavour.
## Goals
- [x] Find a way to build extensions that survive upstream merges best. This means, reduce contact point, create own files, even be in own folder if possible. This needs a thorough scan of the codebase.
- [ ] We will try to integrate `comms` project at some point. but this is a separate task, not needed yet. It will bring voice server to the rooms. comms uses a wasm system.
- [x] Try to find out how much we can change behaviour of the page with the _themes_ (skins) only. Some small silly things, like that reacting with a new emoji on a post should not be on the left side of a message, where currently it is embedded in a popup.
- [x] Find out how we can set that Twitter Emojis are standard - system emojis on windows are terrible.
- [x] Find out how we can set a theme by default for new users.
- [x] img tags in messages should be rendered, without requiring them to be on a local cache. We do not need to secure against identification attacks.
- [x] youtube links should create embedded players (if enabled).
- [x] 9gag links with videos should allow them to be played if possible on demand.
## Commit Messages
- All Commits for OWL changes start with the Owl Emoji 🦉
- I do most commits as user
## Code Guidelines
- This Repository follows the official cinny repo, but represents a custom modification.
- This means we have to keep Upstream Codebase mergable easy
- This means: /src/owl/ is the local override folder, longer implementations and definitions go
- Only required wirings, imports or unavoidable modifications should be in the main source code.
Cinny has **no formal plugin/extension architecture**. All imports are static. The `plugins/` directory is just a naming convention for utility modules, not a registration system.
### Recommended Isolation Strategy
All custom owl code lives in a single `src/owl/` directory tree. React and Vite don't care where files live — imports are just paths. This keeps everything in one place that upstream will never touch, making it easy to see the full scope of our fork and trivial to manage across merges.
Upstream files import our code via relative paths like `import { YouTubeEmbed } from '../../../owl/components/YouTubeEmbed'`. These one-liner injection points are the only upstream modifications needed and are easy to re-apply after a merge.
### Injection Points (unavoidable upstream edits)
These are the upstream files we'll need to add small imports/calls into. Each edit should be minimal — ideally a single import + one-liner call to owl code, keeping the actual logic in `src/owl/`.
| File | Why | Change Size |
|------|-----|-------------|
| `src/app/state/settings.ts` | Change defaults (twitterEmoji, theme) and add new settings (youtubeEmbed) | ~5 lines |
- **Success / Warning / Critical**: Same as Primary
- **Other**: FocusRing, Shadow, Overlay
### What Themes CANNOT Change
- **Layout/positioning** - Component layout is hardcoded in vanilla-extract style objects, not CSS variables
- **Icon choices** - Icons are imported React components, not themeable
- **Behavior** - Popup positioning, menu structure, click handlers are all JS
- **Spacing/sizing** - Uses `folds` design tokens (`config.space`, `config.radii`, etc.) which are not part of the theme contract
### Verdict
**Themes alone cannot fix the reaction popup UX or change icons/menus.** They can only change colors. Layout, positioning, and behavioral changes require component modifications.
---
## 3. Reaction Popup UX
### Current Mechanism
The reaction picker is controlled by `src/app/features/room/message/Message.tsx`:
- A toolbar (`MessageOptionsBase`) appears on message hover, positioned `top: -30px; right: 0` (absolute) via `src/app/features/room/message/styles.css.ts`
- Clicking the emoji button captures `getBoundingClientRect()` and opens a `PopOut` component with `position="Bottom"` and `align="End"`
- The emoji board renders inside this PopOut popup
- There's also a context menu with `MessageQuickReactions` showing 4 recent emojis
2.**Message.tsx** - Change `PopOut` props (`position`, `align`) or replace PopOut with inline rendering
3. Optionally: render the emoji board inline below the message instead of as a floating popup
This is a **component-level change**, not achievable through themes.
---
## 4. Twitter Emoji (Twemoji) as Default
### Current State
- Setting exists: `twitterEmoji` in `src/app/state/settings.ts` (line 26)
- **Default is `false`** (line 60)
- Font files exist: `public/font/Twemoji.Mozilla.v15.1.0.ttf` and `.woff2`
- Applied via CSS custom property `--font-emoji` in `src/app/pages/client/ClientNonUIFeatures.tsx` (lines 30-40)
### How to Make It Default
**One-line change** in `src/app/state/settings.ts`:
```typescript
// Line 60: change from
twitterEmoji: false,
// to
twitterEmoji: true,
```
New users get `defaultSettings` merged with their (empty) localStorage. Existing users who never touched the toggle will also get the new default via the merge logic in `getSettings()` (lines 86-93).
**Merge risk: LOW** - Single line change in a defaults object.
---
## 5. Default Theme for New Users
### Current Defaults
In `src/app/state/settings.ts` (lines 52-56):
```typescript
themeId: undefined,// no manual override
useSystemTheme: true,// follow OS preference
lightThemeId: undefined,// fallback: LightTheme
darkThemeId: undefined,// fallback: DarkTheme
```
Theme resolution in `src/app/hooks/useTheme.ts`:
- If `useSystemTheme` is true (default): detects OS preference, picks light/dark fallback
- If disabled: uses `themeId`, falls back to LightTheme
### Available Themes
| ID | Name |
|----|------|
| `light-theme` | Light (default light) |
| `silver-theme` | Silver |
| `dark-theme` | Dark (default dark) |
| `butter-theme` | Butter |
### How to Set a Default Theme
**Option A - Force a specific theme:**
```typescript
useSystemTheme: false,
themeId:'butter-theme',// or any theme ID
```
**Option B - Change the dark/light fallbacks (keeps system detection):**
```typescript
darkThemeId:'butter-theme',// dark mode users get Butter
lightThemeId:'silver-theme',// light mode users get Silver
```
**Merge risk: LOW** - Changes to defaults object only.
---
## 6. External IMG Tags in Messages
### Current Behavior
External image URLs are **blocked** at two levels:
1.**Sanitization** (`src/app/utils/sanitize.ts`, lines 108-127): `transformImgTag` converts any `<img>` with non-`mxc://` src into an `<a>` link
2.**React parser** (`src/app/plugins/react-custom-html-parser.tsx`, lines 476-494): Non-mxc images are rendered as links, not `<img>` elements
This is a deliberate privacy measure for federated Matrix — loading external images reveals user IPs to image hosts.
### What Needs to Change
Since owl.cx is a single-server instance where this threat model doesn't apply:
1.**sanitize.ts**: Modify `transformImgTag` to allow `https://` and `http://` src URLs through as `<img>` tags instead of converting to `<a>`
2.**react-custom-html-parser.tsx**: Modify the `img` handler to render external URLs directly with `<img src={originalUrl}>` instead of converting to links
### Suggested Approach
Add an owl setting (e.g., `allowExternalImages: true`) and conditionally bypass the mxc-only restriction. This keeps the change isolated and opt-in.
| External images | ~30 lines | Medium | Do soon, add owl setting |
| YouTube embeds | New component + ~20 lines changes | Low-Medium | Build as isolated component |
| Reaction UX | Component restructure | Medium | Plan carefully, touches upstream |
The first two are trivial defaults changes. External images and YouTube embeds can be built mostly in isolation. The reaction UX rework is the most invasive change and should be planned carefully to minimize merge conflict surface.
Add `allowExternalImages: boolean` to settings, default `false` (or `true` via config.json for owl).
**Pros:** Simple, one toggle, works everywhere.
**Cons:** All-or-nothing. A privacy-conscious user can't allow it for trusted rooms only.
### Option B: Per-Room Setting (Room State Event)
Store a custom state event like `cx.owl.room.settings` with `{ allowExternalImages: true }` per room. Room admins control it.
**Pros:** Fine-grained, admin-controlled.
**Cons:** Requires room admin action. UX overhead. Need UI for room settings. Only works in rooms we control.
### Option C: Automatic by Federation Status
If `m.federate === false` (local-only room) → allow external images. If federated → block.
**Pros:** Zero configuration, matches the threat model (federated = untrusted external servers can leak IPs via images).
**Cons:** Most rooms default to `m.federate: true` even on a single homeserver. Users would need to explicitly create non-federated rooms. Doesn't match the real intent — on owl.cx, all rooms are effectively local even if technically federated.
### Option D: Homeserver-Level Default via config.json
Add to config.json:
```json
{
"defaultSettings":{
"allowExternalImages":"homeserver"
}
}
```
Where the value is one of: `"always"`, `"never"`, `"homeserver"` (only in rooms on this homeserver), `"local"` (only non-federated rooms).
**Pros:** Server operator controls the policy. Matches the owl use case perfectly — "we trust our own server."
**Cons:** More logic to implement. Need to define what "homeserver room" means (all members local? room alias local? room ID local?).
// 'homeserver': check if room belongs to our homeserver
constroomDomain=room.roomId.split(':')[1];
returnroomDomain===mx.getDomain();
},[externalImages,room.roomId,mx]);
```
**`src/app/utils/sanitize.ts`:**
`sanitizeCustomHtml` needs the flag too, since it runs before the React parser. Either:
- Pass it as a parameter: `sanitizeCustomHtml(html, { allowExternalImages })`
- Or move all image logic to the React parser (simpler — sanitizer just passes img tags through, parser decides what to render)
The second approach is cleaner: the sanitizer allows img tags with any src (already done), and the React parser decides whether to render or convert to link based on the flag.
### Settings UI
Add a dropdown in `src/app/features/settings/general/General.tsx` under the "Media" or "Privacy" section:
```
External Images: [Always / Homeserver Only / Never]
```
### Files to Touch
| File | Change |
|------|--------|
| `src/app/state/settings.ts` | Add `externalImages` to Settings + defaults |
Current state: we already removed the img→link conversion in `sanitize.ts`. For the settings-based approach, we have two options:
**Option 1 (simpler):** Leave sanitize.ts as-is (allows all img src through). The React parser handles the allow/block decision. This means the sanitized HTML contains `<img src="https://...">` but the React parser may convert it to a link at render time.
**Option 2 (stricter):** Restore the original sanitize.ts behavior and only bypass it when `allowExternalImages` is true. This requires passing options to `sanitizeCustomHtml`, which changes its signature and every call site.
**Recommendation:** Option 1. The sanitizer's job is preventing XSS, not policy decisions. Let the React parser handle the rendering policy — it already has all the context it needs.
# OWL Report #003 - External Video Embeds (YouTube + Direct Video URLs)
Date: 2026-04-17
## The Goal
Two README items merge naturally into one feature:
- **YouTube links** should create embedded players (click-to-play).
- **9gag-style direct video URLs** (e.g. `https://img-9gag-fun.9cache.com/photo/aYQnPXN_460svav1.mp4`) should be playable inline.
Both are "external video in the timeline". They share the same wiring points (URL detection → card renderer in `RenderMessageContent.tsx`) and the same policy model (`'always' | 'homeserver' | 'never'`, mirroring the external-images setting). Different rendering tech: YouTube needs an iframe, direct videos use the `<video>` tag — but that's an internal detail of the renderer.
---
## Existing Plumbing We Will Reuse
The external-images work (Report #002, now shipped) gave us the exact pattern to follow.
| Piece | File | Role |
|-------|------|------|
| URL extraction | `src/app/utils/regex.ts` → `URL_REG` / `HTTP_URL_PATTERN` | Already pulls every http(s) URL out of message text |
| URL classifier | `src/owl/utils/imageUrl.ts` → `isImageUrl()` | Pattern we will mirror for `isVideoUrl`/`isYouTubeUrl` |
| Render dispatch | `src/app/components/RenderMessageContent.tsx:63-84` → `renderUrlsPreview` | The single place we branch per URL kind |
| Policy hook | `src/owl/hooks/useAllowExternalImages.ts` | Copy for `useAllowExternalVideos` |
| Settings UI | `src/app/features/settings/general/General.tsx:882-954` (`SelectExternalImages`) + mount at line 1043-1048 | Copy for `SelectExternalVideos` |
| Matrix `<video>` element | `src/app/components/media/Video.tsx` | Already exists — reusable for direct video rendering |
The key insight: `renderUrlsPreview` in `RenderMessageContent.tsx` is already where all URL-derived embeds dispatch. Today it routes images to `ExternalImageCard` and everything else to `UrlPreviewCard`. Adding video is one more branch in the same `filter()` chain. **No changes to `sanitize.ts` or the HTML parser are needed** — we are rendering from the URL list, not from user HTML.
---
## Part A — YouTube Embeds
### Detection
YouTube URL variants we want to catch:
```
https://www.youtube.com/watch?v=VIDEOID
https://youtube.com/watch?v=VIDEOID&t=42s
https://youtu.be/VIDEOID
https://youtu.be/VIDEOID?t=42
https://www.youtube.com/shorts/VIDEOID
https://www.youtube.com/embed/VIDEOID
https://m.youtube.com/watch?v=VIDEOID
```
Parse via `URL` rather than a mega-regex. That way a typo in the query string won't break detection:
1.**Privacy**: YouTube's iframe (even on `youtube-nocookie.com`) phones home as soon as it mounts. Loading a full iframe for every YouTube link in a room scrollback is a tracking and performance problem.
2.**Performance**: A YouTube iframe is ~500KB of JS per instance. Ten YouTube links in a channel would pin the tab.
Instead: show a clickable thumbnail from `https://i.ytimg.com/vi/<ID>/hqdefault.jpg` with a play-button overlay. On click, swap to the iframe. This is the standard approach (used by Reddit, Discord, Mastodon front-ends).
// no sandbox attr — the full YouTube embed API needs scripts+same-origin, and
// youtube-nocookie is already an isolated origin. Sandbox breaks fullscreen.
/>
```
Notes:
- **`youtube-nocookie.com`** is the reduced-tracking variant and should be the default.
- **Don't sandbox**. The YouTube player relies on `postMessage` and same-origin window access for its controls; sandboxing breaks fullscreen and picture-in-picture. The iframe is already origin-isolated.
- **CSP**: Cinny's `vite.config.ts` / `public/config.json` don't set CSP headers — the deployment host does. owl.cx must allow `frame-src https://www.youtube-nocookie.com https://www.youtube.com` (and the `i.ytimg.com` image host). Flag this for deployment.
**About 9gag specifically**: their `.webp` links served under `/photo/` are actually animated images (not video). The `.mp4` variant (`aYQnPXN_460svav1.mp4`) is a true MP4 and needs `<video>`. The path-segment `photo` is misleading — extension is what matters. Our image-extension list already handles `.webp`, so animated WebP 9gag links will flow through `ExternalImageCard` today (the `<img>` tag will animate them natively). MP4 variants will flow through the new video card. No special 9gag detection needed.
### Rendering
No facade needed — `<video>` with `preload="metadata"` only downloads the first few KB (metadata + poster frame). Users click play to stream.
We can reuse the existing `<Video>` wrapper from `src/app/components/media/Video.tsx` if we want shared styling, but a new card mirroring `ExternalImageCard` is simpler and keeps all owl code in `src/owl/`.
### Gotchas
- **CORS / Hotlinking**: Some hosts (including Cloudflare sites) block hotlinked video with `Referer` checks. 9cache.com currently allows it — easy to verify empirically. If a host blocks us, `<video onError>` falls back (card hides itself). No leaking user IP because the threat model for owl is single-server — same as external images.
- **Autoplay**: Do **not** autoplay. Browsers require `muted` for autoplay and it's user-hostile. `preload="metadata"` gives a poster frame without streaming the whole file.
- **Mobile data**: `preload="metadata"` is the right default; don't change it to `auto`.
owl.cx `config.json` overrides to `'homeserver'` (or `'always'`) via the same `defaultSettings` mechanism that external-images already uses.
Open question: **single setting or two?** My recommendation is **one setting `externalVideos` covering both YouTube and direct video**, because:
- They have the same privacy/trust model from the user's point of view ("do I trust external video hosts?").
- Two toggles create decision fatigue for a feature that's largely "on or off".
- If we later want finer control, splitting later is easier than merging later.
If we ever need YouTube-specific controls (e.g. "block YouTube but allow direct video" for a super-privacy-conscious user), we can add it then. Don't design for the hypothetical.
### Hook
```ts
// src/owl/hooks/useAllowExternalVideos.ts (one-to-one copy of useAllowExternalImages)
And render each list with its card. **Gating**: `RenderMessageContent` doesn't currently take `allowExternalVideos` as a prop — we'd either add one (mirroring the eventual external-images gating if/when it lands here), or gate inside the new card component by reading the hook. Reading inside the card is simpler but requires the card to know its room context. Passing a prop down from `RoomTimeline` is cleaner and matches how `urlPreview` already flows. **Recommendation: prop.**
-`RoomTimeline.tsx` — pass `allowExternalVideos` into `RenderMessageContent` (need to find where `RenderMessageContent` is rendered in the timeline and thread it through; currently `allowExternalImages` goes into `getReactCustomHtmlParser` at line 533, not into `RenderMessageContent`, because images are gated inside HTML parsing. Video gating is different — it gates URL-derived embeds, not parsed HTML — so we pass directly to `RenderMessageContent`).
### Settings UI
In `General.tsx`, copy `SelectExternalImages` (lines 882-954) to `SelectExternalVideos`, and add a `SettingTile` row mirroring lines 1043-1048:
Upstream surface area: the three `src/app/...` edits. All feature logic (detection, rendering, policy hook, both cards, both CSS files) lives in `src/owl/`.
**Merge risk: LOW.** Pure additive change. No sanitizer or HTML-parser changes. If upstream touches `renderUrlsPreview` in `RenderMessageContent.tsx`, the branch addition is mechanical to re-apply.
---
## Security Summary
| Concern | Mitigation |
|---------|------------|
| YouTube tracking | `youtube-nocookie.com`, facade (no iframe until user click), `referrerPolicy="strict-origin-when-cross-origin"` |
| iframe XSS | YouTube iframe is on a separate origin — no DOM access to our app. No sandbox needed (and it breaks fullscreen). |
| Video host IP leak | Same as external images: single-server threat model, already accepted by owl. Setting defaults to `'never'` for upstream. |
| Video MIME spoofing | Browser validates MIME before playback; `<video>` rejects non-video payloads. `onError` hides the card. |
| CSP | Deployment-side: owl.cx host config must permit `frame-src youtube-nocookie.com`, `img-src i.ytimg.com`, `media-src *` (or allowlist). Flag for deploy. |
| Malicious autoplay | We never autoplay until user clicks. |
---
## Recommendation & Priority
**Build this as one PR**, in this order:
1.`src/owl/utils/videoUrl.ts` + unit-style smoke test (throwaway URLs in dev console is fine — this is a client app, no test infra being set up for this).
5. Manual test on owl.cx dev with: YouTube watch link, `youtu.be` short link, link with `?t=` timestamp, `youtube.com/shorts/`, a 9gag mp4, an arbitrary `.webm`, and a broken URL.
Overall risk: LOW. The pattern is copy-paste from external images, the only genuinely new code is the YouTube facade and the setting row.
One thing to explicitly decide with the user before implementing: **single `externalVideos` setting vs. split `youtubeEmbed` + `directVideoEmbed`**. My recommendation is single; flagging it so we don't bikeshed after the PR is up.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.