Page MenuHomePhorge

No OneTemporary

Size
90 KB
Referenced Files
None
Subscribers
None
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 444a863c15..9844319e32 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,400 +1,468 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+## 2.7.1
+Bugfix release. Added small optimizations to emoji picker that should make it a bit more responsive, however it needs rather large change to make it more performant which might come in a major release.
+
+### Fixed
+- Instance default theme not respected
+- Nested panel header having wrong sticky position if navbar height != panel header height
+- Toggled buttons having bad contrast (when using v2 theme)
+
+### Changed
+- Simplify the OAuth client_name to 'PleromaFE'
+- Small optimizations to emoji picker
+
+
+## 2.7.0
+
+### Known issues
+We got some reports related to emoji picker performance, this hopefully will be fixed in 2.7.1.
+
+### Notes
+This release overhauls how themes work, themes now need to be "compiled", which can cause some delay when loading for the first time and temporarily look "wrong" in some places (popups, menus, dialogs). Please do report any issues, especially if your theme looks wrong or breaks interface when loading. Also report issues if you're experiencing constant performance issues.
+
+To admins: remember that you can update PleromaFE to recent `master` or `develop` in admin dashboard in "Front-ends" tab, scroll down to find PleromaFE box and click "Reinstall `master`" or dropdown and then "Reinstall `develop`". Currently there is no mechanism to check if there is an update or not.
+
+### Changed
+- Overhauled the way themes work, migrating to new Pleroma Interface Style Sheets system aka "Themes 3".
+- Notifications are no longer sorted by "seen" status since interacting with them can change their read status and makes UI jumpy. Old behavior can be restored in settings.
+- Notifications are now shown through a ServiceWorker (since mobile chrome does not allow them otherwise), it's always enabled, even if previously we only enabled it for WebPush notifications only. If you don't like websites "running" while closed, check how to disable them in your browser. Old way to show notifications will be used as a fallback but might not have all the new features.
+- Reorganized Settings modal to move out visual stuff into Appearance tab
+
+### Added
+- Emoji pack management to the admin panel
+- Support `status` notification type (subscriptions/bell, fixes PleromaFE on newer PleromaBE versions)
+- Poll end notifications.
+- Added option to not mark all notifications when closing notifications drawer on mobile, this creates a new button to mark all as seen.
+- Option to always "show" notifications when using web push for better compatibility with some browsers (chrome, edge, safari)
+- Option to toggle what notification types appear in native notifications, by default less important ones (likes, repeats, etc) will no longer show up in native notifications.
+- Option to treat non-interactive notifications (likes, repeats et all) as seen for visual purposes (no read mark, ignored in counters, still can show in native notifications)
+- Ability to resize UI (and certain components) scale independent of browser/text scale
+- Ability to override certain aspects of UI style independent of theme used (UI roundness, fonts, underlay)
+- Theme selector with visual previews of the theme
+- Display loading and error indicator for conversation page
+- Option to only show scrobbles that are recent enough
+- Interacting (opening reply box etc) or simply clicking on non-interactive notifications now marks them as read. Clicking on native notifications for non-interactive ones also marks them as seen.
+- Support group actors
+- Focusing into a tab clears all current desktop notifications
+- Ability to change size of emoji
+- Ability to view APNG (Animated PNG) attachments.
+- Support showing extra notifications in the notifications column
+- Create a link to the URL of the scrobble when it's present
+- Allow hiding custom emojis in picker.
+- Ability to mute sensitive posts (ported from eintei).
+- Native notifications now also have "badge" property that matches instance's favicon (visible in Android Chromium at least)
+- Display public favorites on user profiles
+- Display quotes count on posts and add quotes list page
+- Show a dedicated registration notice page when further action is required after registering
+
+### Fixed
+- Synchronized requested notification types with backend, hopefully should fix missing notifications for polls and follow requests
+- Error that appeared on mobile Chromium (and derivatives) when native notifications are allowed
+- Being unable to set notification visibility for reports and follow requests
+- Native notifications appearing as many times as there are open tabs. Clicking on notification will focus last focused tab.
+- The expiry date indication won't be shown if the poll never expires
+- Profile mentions causing a 422 error on newer PleromaBE versions.
+- Color inputs are less ugly now
+- Unread notifications should now properly catch up between sessions (eventually) in polling mode
+- Video posters on Safari
+
+
## 2.6.1
### Fixed
- fix admin dashboard not having any feedback on frontend installation
- Fix frontend admin tab crashing when no primary frontend is set
- Add aria attributes to react and extra buttons
## 2.6.0
### Added
- add the initial i18n translation file for Taiwanese (Hokkien), and modify some related files.
- Implemented a very basic instance administration screen
- Implement quoting
### Fixed
- Keep aspect ratio of custom emoji reaction in notification
- Fix openSettingsModalTab so that it correctly opens Settings modal instead of Admin modal
- Add alt text to emoji picker buttons
- Use export-subst gitattribute to allow tarball builds
- fix reports now showing reason/content
- Fix HTML attribute parsing, discard attributes not strating with a letter
- Make MentionsLine aware of line breaking by non-br elements
- Fix a bug where mentioning a user twice will not fill the mention into the textarea
- Fix parsing non-ascii tags
- Fix OAuth2 token lingering after revocation
- fix regex issue in HTML parser/renderer
- don't display quoted status twice
- fix typo in code that prevented cards from showing at all
- Fix react button not working if reaction accounts are not loaded
- Fix react button misalignment on safari ios
- Fix pinned statuses gone when reloading user timeline
- Fix scrolling emoji selector in modal in safari ios
## 2.5.1
### Fixed
- Checkboxes in settings can now work with screenreaders
- Autocomplete in edit boxes can now work with screenreaders
- Status interact buttons now have focus indicator for anonymous users
- Top bar buttons now correctly have text labels
- It is now possible to register if the site admin requires birthday to register
- User cards from search results will correctly popup
- Fix notification attachment icon overflow
- Editing mute words is less laggy
- Repeater's name will no longer mess up with the directionality of the text sitting on the same line
- Unauthenticated access will give better error messages
- It is now easier to close the media viewer with a mouse when there is only one image
- Deleting profile fields can work properly
- Clicking the react button will correctly focus the search box
- Clicking buttons on the top-bar will no longer bring you to the top of the page
- Emoji picker is much faster to load
- `blockquote`s have a better display style
- Announcements posting and editing are now available to everyone with such a privilege, not just admins
- Adding or removing list members will actually work
- Emojis without a pack are now correctly displayed in emoji picker
- Changing notification settings will actually work
### Added
- You can now set and see birthdays
- Optional confirmation dialogs when performing various actions
- You can now set fallback languages
## 2.5.0 - 23.12.2022
### Fixed
- UI no longer lags when switching between mobile and desktop mode
- Popovers no longer constrained by DOM hierarchy, shouldn't be cut off by anything
- Emoji autocomplete popover and picker popover stick to the text cursor.
- Attachments are ALWAYS in same order as user uploaded, no more "videos first"
- Pinned statuses no longer appear at bottom of user timeline (still appear as part of the timeline when fetched deep enough)
- Fixed many many bugs related to new mentions, including spacing and alignment issues
- Links in profile bios now properly open in new tabs
- "Always show mobile button" is working now
- Inline images now respect their intended width/height attributes
- Links with `&` in them work properly now
- Attachment description is prefilled with backend-provided default when uploading
- Proper visual feedback that next image is loading when browsing
- Additional HTML sanitization on frontend side in case backend sanitization fails
- Interaction list popovers now properly emojify names
- AdminFE button no longer scrolls page to top when clicked
- User handles with non-ascii domains now have less intrusive indicator for the domain name
- Completely hidden posts still no longer have 1px border
- A lot of accessibility improvements
### Changed
- Using Vue 3 now
- A lot of internal dependencies updated
- "(You)s" are optional (opt-in) now, bolding your nickname is also optional (opt-out)
- User highlight background now also covers the `@`
- Reverted back to textual `@`, svg version is opt-in.
- Settings window has been thoroughly rearranged to make more sense and make navigation settings easier.
- Uploaded attachments are uniform with displayed attachments
- Flash is watchable in media-modal (takes up nearly full screen though due to sizing issues)
- Notifications about likes/repeats/emoji reacts are now minimized so they always take up same amount of space irrelevant to size of post. (You can expand them to full if need be)
- Slight width/spacing adjustments
- More sizing stuff is font-size dependent now
- Scrollbars are styled/colorized now
- Scrollbars are toggleable (for stuff that didn't have visible scrollbars before) (opt-in)
- Updated localization files
- Top bar is more useful in mobile mode now.
- "Show new" button is way more compact in mobile mode
- Slightly adjusted placement and spacing of the topbar buttons so it's less easy to accidentally log yourself out
### Added
- 3 column mode: only enables when there's space for it (opt-out, customizable)
- Apologetic pleroma-tan
- New button on timeline header to change some of the new and often-used settings
- Support for lists
- Added ability to edit posts and view post edit history etc.
- Added ability to add personal note to users
- Added initial support for admin announcements
- Added ui for account migration
- Added ui for backups
- Added ability to force-unfollow a user from you
- Emoji are now grouped by pack
- Ability to pin navigation items and collapse the navigation menu
- Ability to rearrange order of attachments when uploading
- Ability to scroll column (or page) to top via panel header button
- Options to show domains in mentions
- Option to show user avatars in mention links (opt-in)
- Option to disable the tooltip for mentions
- Option to completely hide muted threads
- Option to customize what clicking user avatar does in user popover
- Notifications for poll results
- "Favorites" link in navigation
- Very early and somewhat experimental system for automatic settings sync (used only for pinned navigation and apologetic pleroma-tan)
- Implemented remote interaction with statuses for anon visitors
- Ability to open videos in modal even if you disabled that feature, via an icon button
- New button on attachment that indicates that attachment has a description and shows a bar filled with description
- Attachments are truncated just like post contents
- Media modal now also displays description and counter position in gallery (i.e. 1/5)
- Enabled users to zoom and pan images in media viewer with mouse and touch
- Timelines/panels and conversations have sticky headers now (a bit glitchy on some browsers like safari) (opt-out)
## [2.4.2] - 2022-01-09
### Added
- Added Apply and Reset buttons to the bottom of theme tab to minimize UI travel
- Implemented user option to always show floating New Post button (normally mobile-only)
- Display reasons for instance specific policies
- Added functionality to cancel follow request
### Fixed
- Fixed link to external profile not working on user profiles
- Fixed mobile shoutbox display
- Fixed favicon badge not working in Chrome
- Escape html more properly in subject/display name
## [2.4.0] - 2021-08-08
### Added
- Added a quick settings to timeline header for easier access
- Added option to mark posts as sensitive by default
- Added quick filters for notifications
- Implemented user option to change sidebar position to the right side
- Implemented user option to hide floating shout panel
- Implemented "edit profile" button if viewing own profile which opens profile settings
### Fixed
- Fixed follow request count showing in the wrong location in mobile view
## [2.3.0] - 2021-03-01
### Fixed
- Button to remove uploaded media in post status form is now properly placed and sized.
- Fixed shoutbox not working in mobile layout
- Fixed missing highlighted border in expanded conversations again
- Fixed some UI jumpiness when opening images particularly in chat view
- Fixed chat unread badge looking weird
- Fixed punycode names not working properly
- Fixed notifications crashing on an invalid notification
### Changed
- Display 'people voted' instead of 'votes' for multi-choice polls
- Changed the "Timelines" link in side panel to toggle show all timeline options inside the panel
- Renamed "Timeline" to "Home Timeline" to be more clear
- Optimized chat to not get horrible performance after keeping the same chat open for a long time
- When opening emoji picker or react picker, it automatically focuses the search field
- Language picker now uses native language names
### Added
- Added reason field for registration when approval is required
- Group staff members by role in the About page
## [2.2.3] - 2021-01-18
### Added
- Added Report button to status ellipsis menu for easier reporting
### Fixed
- Follows/Followers tabs on user profiles now display the content properly.
- Handle punycode in screen names
- Fixed local dev mode having non-functional websockets in some cases
- Show notices for websocket events (errors, abnormal closures, reconnections)
- Fix not being able to re-enable websocket until page refresh
- Fix annoying issue where timeline might have few posts when streaming is enabled
### Changed
- Don't filter own posts when they hit your wordfilter
## [2.2.2] - 2020-12-22
### Added
- Mouseover titles for emojis in reaction picker
- Support to input emoji into the search box in reaction picker
- Added some missing unicode emoji
- Added the upload limit to the Features panel in the About page
- Support for solid color wallpaper, instance doesn't have to define a wallpaper anymore
### Fixed
- Fixed the occasional bug where screen would scroll 1px when typing into a reply form
- Fixed timeline errors locking timelines
- Fixed missing highlighted border in expanded conversations
- Fixed custom emoji not working in profile field names
- Fixed pinned statuses not appearing in user profiles
- Fixed some elements not being keyboard navigation friendly
- Fixed error handling when updating various profile images
- Fixed your latest chat messages disappearing when closing chat view and opening it again during the same session
- Fixed custom emoji not showing in poll options before voting
- Fixed link color not applied to instance name in topbar
### Changed
- Errors when fetching are now shown with popup errors instead of "Error fetching updates" in panel headers
- Made reply/fav/repeat etc buttons easier to hit
- Adjusted timeline menu clickable area to match the visible button
- Moved external source link from status heading to the ellipsis menu
- Disabled horizontal textarea resize
- Wallpaper is now top-aligned, horizontally centered.
## [2.2.1] - 2020-11-11
### Fixed
- Fixed regression in react popup alignment and overflowing
## [2.2.0] - 2020-11-06
### Added
- New option to optimize timeline rendering to make the site more responsive (enabled by default)
- New instance option `logoLeft` to move logo to the left side in desktop nav bar
- Import/export a muted users
- Proper handling of deletes when using websocket streaming
- Added optimistic chat message sending, so you can start writing next message before the previous one has been sent
- Added a small red badge to the favicon when there's unread notifications
- Added the NSFW alert to link previews
### Fixed
- Fixed clicking NSFW hider through status popover
- Fixed chat-view back button being hard to click
- Fixed fresh chat notifications being cleared immediately while leaving the chat view and not having time to actually see the messages
- Fixed multiple regressions in CSS styles
- Fixed multiple issues with input fields when using CJK font as default
- Fixed search field in navbar infringing into logo in some cases
- Fixed not being able to load the chat history in vertical screens when the message list doesn't take the full height of the scrollable container on the first fetch.
### Changed
- Clicking immediately when timeline shifts is now blocked to prevent misclicks
- Icons changed from fontello (FontAwesome 4 + others) to FontAwesome 5 due to problems with fontello.
- Some icons changed for better accessibility (lock, globe)
- Logo is now clickable
- Changed default logo to SVG version
## [2.1.2] - 2020-09-17
### Fixed
- Fixed chats list not updating its order when new messages come in
- Fixed chat messages sometimes getting lost when you receive a message at the same time
## [2.1.1] - 2020-09-08
### Changed
- Polls will be hidden with status content if "Collapse posts with subjects" is enabled and the post is collapsed.
### Fixed
- Network fetches don't pile up anymore but wait for previous ones to finish to reduce throttling.
- Autocomplete won't stop at the second @, so it'll still work with "@lain@l" and not start over.
- Fixed weird autocomplete behavior when you write ":custom_emoji: ?"
## [2.1.0] - 2020-08-28
### Added
- Autocomplete domains from list of known instances
- 'Bot' settings option and badge
- Added profile meta data fields that can be set in profile settings
- Added option to reset avatar/banner in profile settings
- Descriptions can be set on uploaded files before posting
- Added status preview option to preview your statuses before posting
- When a post is a reply to an unavailable post, the 'Reply to'-text has a strike-through style
- Added ability to see all favoriting or repeating users when hovering the number on highlighted statuses
- Bookmarks
### Changed
- Change heart to thumbs up in reaction picker
- Close the media modal on navigation events
- Add colons to the emoji alt text, to make them copyable
- Add better visual indication for drag-and-drop for files
- When disabling attachments, the placeholder links now show an icon and the description instead of just IMAGE or VIDEO etc
- Remove unnecessary options for 'automatic loading when loading older' and 'reply previews'
- Greentext now has separate color slot for it
- Removed the use of with_move parameters when fetching notifications
- Push notifications now are the same as normal notfication, and are localized.
- Updated Notification Settings to match new BE API
### Fixed
- Custom Emoji will display in poll options now.
- Status ellipsis menu closes properly when selecting certain options
- Cropped images look correct in Chrome
- Newlines in the muted words settings work again
- Clicking on non-latin hashtags won't open a new window
- Uploading and drag-dropping multiple files works correctly now.
- Subject field now appears disabled when posting
- Fix status ellipsis menu being cut off in notifications column
- Fixed autocomplete sometimes not returning the right user when there's already some results
- Videos and audio and misc files show description as alt/title properly now
- Clicking on non-image/video files no longer opens an empty modal
- Audio files can now be played back in the frontend with hidden attachments
- Videos are not cropped awkwardly in the uploads section anymore
- Reply filtering options in Settings -> Filtering now work again using filtering on server
- Don't show just blank-screen when cookies are disabled
- Add status idempotency to prevent accidental double posting when posting returns an error
- Weird bug related to post being sent seemingly after pasting with keyboard (hopefully)
- Multiple issues with muted statuses/notifications
## [2.0.5] - 2020-05-12
### Added
- Added private notifications option for push notifications
- 'Copy link' button for statuses (in the ellipsis menu)
### Changed
- Registration page no longer requires email if the server is configured not to require it
### Fixed
- Status ellipsis menu closes properly when selecting certain options
## [2.0.3] - 2020-05-02
### Fixed
- Show more/less works correctly with auto-collapsed subjects and long posts
- RTL characters won't look messed up in notifications
### Changed
- Emoji autocomplete will match any part of the word and not just start, for example :drool will now helpfully suggest :blobcatdrool: and :blobcatdroolreach:
### Added
- Follow request notification support
## [2.0.2] - 2020-04-08
### Fixed
- Favorite/Repeat avatars not showing up on private instances/non-public posts
- Autocorrect getting triggered in the captcha field
- Overflow on long domains in follow/move notifications
### Changed
- Polish translation updated
## [2.0.0] - 2020-02-28
### Added
- Tons of color slots including ones for hover/pressed/toggled buttons
- Experimental `--variable[,mod]` syntax support for color slots in themes. the `mod` makes color brighter/darker depending on background color (makes darker color brighter/darker depending on background color)
- Paper theme by Shpuld
- Icons in nav panel
- Private mode support
- Support for 'Move' type notifications
- Pleroma AMOLED dark theme
- User level domain mutes, under User Settings -> Mutes
- Emoji reactions for statuses
- MRF keyword policy disclosure
### Changed
- Updated Pleroma default themes
- theme engine update to 3 (themes v2.1 introduction)
- massive internal changes in theme engine - slowly away from "generate things separately with spaghetti code" towards "feed all data into single 'generateTheme' function and declare slot inheritance and all in a separate file"
- Breezy theme updates to make it closer to actual Breeze in some aspects
- when using `--variable` in shadows it no longer uses the actual CSS3 variable, instead it generates color from other slots
- theme doesn't get saved to local storage when opening FE anonymously
- Captcha now resets on failed registrations
- Notifications column now cleans itself up to optimize performance when tab is left open for a long time
- 403 messaging
### Fixed
- Fixed loader-spinner not disappearing when a status preview fails to load
- anon viewers won't get theme data saved to local storage, so admin changing default theme will have an effect for users coming back to instance.
- Single notifications left unread when hitting read on another device/tab
- Registration fixed
- Deactivation of remote accounts from frontend
- Fixed NSFW unhiding not working with videos when using one-click unhiding/displaying
- Improved performance of anything that uses popovers (most notably statuses)
## [1.1.7 and earlier] - 2019-12-14
### Added
- Ability to hide/show repeats from user
- User profile button clutter organized into a menu
- Emoji picker
- Started changelog anew
- Ability to change user's email
- About page
- Added remote user redirect
### Changed
- changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes
### Fixed
- improved hotkey behavior on autocomplete popup
diff --git a/changelog.d/add-apng.add b/changelog.d/add-apng.add
deleted file mode 100644
index cdec58afca..0000000000
--- a/changelog.d/add-apng.add
+++ /dev/null
@@ -1 +0,0 @@
-Make Pleroma FE to also view apng (Animated PNG) attachment.
diff --git a/changelog.d/admin-emoji-packs.add b/changelog.d/admin-emoji-packs.add
deleted file mode 100644
index 243163e8a3..0000000000
--- a/changelog.d/admin-emoji-packs.add
+++ /dev/null
@@ -1 +0,0 @@
-Added emoji pack management to the admin panel
diff --git a/changelog.d/appearance-tab.change b/changelog.d/appearance-tab.change
deleted file mode 100644
index 7fe1b45e65..0000000000
--- a/changelog.d/appearance-tab.change
+++ /dev/null
@@ -1 +0,0 @@
-Reorganized Settings modal to move out visual stuff into Appearance tab
diff --git a/changelog.d/ci-runner.skip b/changelog.d/ci-runner.skip
deleted file mode 100644
index ad4b79d520..0000000000
--- a/changelog.d/ci-runner.skip
+++ /dev/null
@@ -1 +0,0 @@
-stop using that one runner for intensive tasks
\ No newline at end of file
diff --git a/changelog.d/create-link-when-url-present.add b/changelog.d/create-link-when-url-present.add
deleted file mode 100644
index 11aa375838..0000000000
--- a/changelog.d/create-link-when-url-present.add
+++ /dev/null
@@ -1 +0,0 @@
-Create a link to the URL of the scrobble when it's present
diff --git a/changelog.d/double-notifications.fix b/changelog.d/double-notifications.fix
deleted file mode 100644
index 24e08c0ffd..0000000000
--- a/changelog.d/double-notifications.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fix native notifications appearing as many times as there are open tabs. Clicking on notification will focus last focused tab.
diff --git a/changelog.d/emoji-scale.add b/changelog.d/emoji-scale.add
deleted file mode 100644
index 791d80d96f..0000000000
--- a/changelog.d/emoji-scale.add
+++ /dev/null
@@ -1 +0,0 @@
-Ability to change size of emoji
diff --git a/changelog.d/extra-notifications.add b/changelog.d/extra-notifications.add
deleted file mode 100644
index 90f21f5401..0000000000
--- a/changelog.d/extra-notifications.add
+++ /dev/null
@@ -1 +0,0 @@
-Support showing extra notifications in the notifications column
diff --git a/changelog.d/firefox-redmon.fix b/changelog.d/firefox-redmon.fix
deleted file mode 100644
index 64ab9b1418..0000000000
--- a/changelog.d/firefox-redmon.fix
+++ /dev/null
@@ -1 +0,0 @@
-Bug with firefox and redmond themes
diff --git a/changelog.d/fixes-themes.skip b/changelog.d/fixes-themes.skip
deleted file mode 100644
index af691507d8..0000000000
--- a/changelog.d/fixes-themes.skip
+++ /dev/null
@@ -1 +0,0 @@
-fixed themes for spw and kazvmoew
diff --git a/changelog.d/fixes.skip b/changelog.d/fixes.skip
deleted file mode 100644
index cc40793ecf..0000000000
--- a/changelog.d/fixes.skip
+++ /dev/null
@@ -1 +0,0 @@
-fix post appearance tab bugs part I
diff --git a/changelog.d/focus-clear.add b/changelog.d/focus-clear.add
deleted file mode 100644
index 70f54ab69b..0000000000
--- a/changelog.d/focus-clear.add
+++ /dev/null
@@ -1 +0,0 @@
-Focusing into a tab clears all current desktop notifications
diff --git a/changelog.d/group-actor.add b/changelog.d/group-actor.add
deleted file mode 100644
index 7b62676ae3..0000000000
--- a/changelog.d/group-actor.add
+++ /dev/null
@@ -1 +0,0 @@
-Support group actors
diff --git a/changelog.d/hide-custom-emojis-in-picker.add b/changelog.d/hide-custom-emojis-in-picker.add
deleted file mode 100644
index 4cfd2ca815..0000000000
--- a/changelog.d/hide-custom-emojis-in-picker.add
+++ /dev/null
@@ -1 +0,0 @@
-Allow hiding custom emojis in picker.
diff --git a/changelog.d/mobile-chrome-notifs.fix b/changelog.d/mobile-chrome-notifs.fix
deleted file mode 100644
index 7db10c56a0..0000000000
--- a/changelog.d/mobile-chrome-notifs.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fixed error that appeared on mobile Chrome(ium) (and derivatives) when native notifications are allowed
diff --git a/changelog.d/mobile-drawer-notifications.change b/changelog.d/mobile-drawer-notifications.change
deleted file mode 100644
index 9353c709dd..0000000000
--- a/changelog.d/mobile-drawer-notifications.change
+++ /dev/null
@@ -1 +0,0 @@
-Added option to not mark all notifications when closing notifications drawer on mobile, this creates a new button to mark all as seen.
diff --git a/changelog.d/more-notification-types-setting.fix b/changelog.d/more-notification-types-setting.fix
deleted file mode 100644
index 2d71b59933..0000000000
--- a/changelog.d/more-notification-types-setting.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fixed being unable to set notification visibility for reports and follow requests
diff --git a/changelog.d/mute-nsfw.add b/changelog.d/mute-nsfw.add
deleted file mode 100644
index b1794a0c25..0000000000
--- a/changelog.d/mute-nsfw.add
+++ /dev/null
@@ -1 +0,0 @@
-Added ability to mute sensitive posts (ported from eintei)
diff --git a/changelog.d/native-filtering.add b/changelog.d/native-filtering.add
deleted file mode 100644
index 82ab9a23a0..0000000000
--- a/changelog.d/native-filtering.add
+++ /dev/null
@@ -1 +0,0 @@
-Added option to toggle what notification types appear in native notifications, by default less important ones (likes, repeats, etc) will no longer show up in native notifications.
diff --git a/changelog.d/native-notifications.add b/changelog.d/native-notifications.add
deleted file mode 100644
index d896e7c04a..0000000000
--- a/changelog.d/native-notifications.add
+++ /dev/null
@@ -1 +0,0 @@
-Native notifications now also have "badge" property that matches instance's favicon (visible in Android Chromium at least)
diff --git a/changelog.d/no-preserve-selection-color.fix b/changelog.d/no-preserve-selection-color.fix
deleted file mode 100644
index 669e744cc9..0000000000
--- a/changelog.d/no-preserve-selection-color.fix
+++ /dev/null
@@ -1 +0,0 @@
-Ensure selection text color has enough contrast
diff --git a/changelog.d/non-expiring-polls-indication.fix b/changelog.d/non-expiring-polls-indication.fix
deleted file mode 100644
index 7979cc131e..0000000000
--- a/changelog.d/non-expiring-polls-indication.fix
+++ /dev/null
@@ -1 +0,0 @@
-The expiry date indication won't be shown if the poll never expires
diff --git a/changelog.d/noninteractive-ignore-read.add b/changelog.d/noninteractive-ignore-read.add
deleted file mode 100644
index 5e8710cf02..0000000000
--- a/changelog.d/noninteractive-ignore-read.add
+++ /dev/null
@@ -1 +0,0 @@
-Added option to treat non-interactive notifications (likes, repeats et all) as seen for visual purposes (no read mark, ignored in counters, still can show in native notifications)
diff --git a/changelog.d/notif-types.fix b/changelog.d/notif-types.fix
deleted file mode 100644
index fb0e5046b7..0000000000
--- a/changelog.d/notif-types.fix
+++ /dev/null
@@ -1 +0,0 @@
-Synchronized requested notification types with backend, hopefully should fix missing notifications for polls and follow requests
diff --git a/changelog.d/notification-read.add b/changelog.d/notification-read.add
deleted file mode 100644
index e5027a954a..0000000000
--- a/changelog.d/notification-read.add
+++ /dev/null
@@ -1 +0,0 @@
-Interacting (opening reply box etc) or simply clicking on non-interactive notifications now marks them as read. Clicking on native notifications for non-interactive ones also marks them as seen.
diff --git a/changelog.d/notifications-sorting.change b/changelog.d/notifications-sorting.change
deleted file mode 100644
index 3a61624418..0000000000
--- a/changelog.d/notifications-sorting.change
+++ /dev/null
@@ -1 +0,0 @@
-Notifications are no longer sorted by "seen" status since interacting with them can change their read status and makes UI jumpy. Old behavior can be restored in settings.
diff --git a/changelog.d/poll-ended-notifications.fix b/changelog.d/poll-ended-notifications.fix
deleted file mode 100644
index d04b8cb00b..0000000000
--- a/changelog.d/poll-ended-notifications.fix
+++ /dev/null
@@ -1 +0,0 @@
-Add poll end notifications to fetched types.
diff --git a/changelog.d/preview-interference.skip b/changelog.d/preview-interference.skip
deleted file mode 100644
index e32e76dd5f..0000000000
--- a/changelog.d/preview-interference.skip
+++ /dev/null
@@ -1 +0,0 @@
-skip
diff --git a/changelog.d/profile-mentions.fix b/changelog.d/profile-mentions.fix
deleted file mode 100644
index 3f38ab0c45..0000000000
--- a/changelog.d/profile-mentions.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fix profile mentions causing a 422 error
diff --git a/changelog.d/public-favorites.add b/changelog.d/public-favorites.add
deleted file mode 100644
index 183fcc8534..0000000000
--- a/changelog.d/public-favorites.add
+++ /dev/null
@@ -1 +0,0 @@
-Display public favorites on user profiles
\ No newline at end of file
diff --git a/changelog.d/public-favorites.skip b/changelog.d/public-favorites.skip
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/changelog.d/quotes-count.add b/changelog.d/quotes-count.add
deleted file mode 100644
index 86779b96b6..0000000000
--- a/changelog.d/quotes-count.add
+++ /dev/null
@@ -1 +0,0 @@
-Display quotes count on posts and add quotes list page
\ No newline at end of file
diff --git a/changelog.d/registration-notice.add b/changelog.d/registration-notice.add
deleted file mode 100644
index b2777ba2de..0000000000
--- a/changelog.d/registration-notice.add
+++ /dev/null
@@ -1 +0,0 @@
-Show a dedicated registration notice page when further action is required after registering
diff --git a/changelog.d/scrobbles-age-filter.add b/changelog.d/scrobbles-age-filter.add
deleted file mode 100644
index ecd3c7d822..0000000000
--- a/changelog.d/scrobbles-age-filter.add
+++ /dev/null
@@ -1 +0,0 @@
-Option to only show scrobbles that are recent enough
diff --git a/changelog.d/serviceworkers.change b/changelog.d/serviceworkers.change
deleted file mode 100644
index b3b64f6d88..0000000000
--- a/changelog.d/serviceworkers.change
+++ /dev/null
@@ -1 +0,0 @@
-Notifications are now shown through a serviceworker (since mobile chrome does not allow them otherwise), it's always enabled, even if previously we only enabled it for WebPush notifications only. If you don't like websites "running" while closed, check how to disable them in your browser. Old way to show notifications will be used as a fallback but might not have all the new features.
diff --git a/changelog.d/show-recent-scrobble.skip b/changelog.d/show-recent-scrobble.skip
deleted file mode 100644
index 9227de0690..0000000000
--- a/changelog.d/show-recent-scrobble.skip
+++ /dev/null
@@ -1 +0,0 @@
-Shows the most recent scrobble under each post when available
diff --git a/changelog.d/status-loading-indicator.add b/changelog.d/status-loading-indicator.add
deleted file mode 100644
index d07256776c..0000000000
--- a/changelog.d/status-loading-indicator.add
+++ /dev/null
@@ -1 +0,0 @@
-Display loading and error indicator for conversation page
diff --git a/changelog.d/status-notification-type.add b/changelog.d/status-notification-type.add
deleted file mode 100644
index 0917dad4a9..0000000000
--- a/changelog.d/status-notification-type.add
+++ /dev/null
@@ -1 +0,0 @@
-Support `status` notification type
\ No newline at end of file
diff --git a/changelog.d/theme-selector.add b/changelog.d/theme-selector.add
deleted file mode 100644
index c303f97c0d..0000000000
--- a/changelog.d/theme-selector.add
+++ /dev/null
@@ -1 +0,0 @@
-Theme selector with visual previews of the theme
diff --git a/changelog.d/themes3-cache.add b/changelog.d/themes3-cache.add
deleted file mode 100644
index a275e44c68..0000000000
--- a/changelog.d/themes3-cache.add
+++ /dev/null
@@ -1 +0,0 @@
-Add caching system for themes3
diff --git a/changelog.d/themes3-fixes.fix b/changelog.d/themes3-fixes.fix
deleted file mode 100644
index 15c31e823f..0000000000
--- a/changelog.d/themes3-fixes.fix
+++ /dev/null
@@ -1 +0,0 @@
-fix color inputs and some in-development themes3 issues
diff --git a/changelog.d/themes3.change b/changelog.d/themes3.change
deleted file mode 100644
index 5255f9b1d8..0000000000
--- a/changelog.d/themes3.change
+++ /dev/null
@@ -1 +0,0 @@
-Overhauled the way themes work, migrating to new Pleroma Interface Style Sheets system.
diff --git a/changelog.d/themesv3-on-safari.fix b/changelog.d/themesv3-on-safari.fix
deleted file mode 100644
index af767b3aaa..0000000000
--- a/changelog.d/themesv3-on-safari.fix
+++ /dev/null
@@ -1 +0,0 @@
-Fix Themes v3 on Safari not working
diff --git a/changelog.d/ui-scale.add b/changelog.d/ui-scale.add
deleted file mode 100644
index 594a9aa58a..0000000000
--- a/changelog.d/ui-scale.add
+++ /dev/null
@@ -1 +0,0 @@
-Ability to resize UI (and certain components) scale independent of browser/text scale
diff --git a/changelog.d/unreads-sync.fix b/changelog.d/unreads-sync.fix
deleted file mode 100644
index 1eac33649e..0000000000
--- a/changelog.d/unreads-sync.fix
+++ /dev/null
@@ -1 +0,0 @@
-unread notifications should now properly catch up (eventually) in polling mode
diff --git a/changelog.d/user-overrides.add b/changelog.d/user-overrides.add
deleted file mode 100644
index c0cb839a3f..0000000000
--- a/changelog.d/user-overrides.add
+++ /dev/null
@@ -1 +0,0 @@
-Ability to override certain aspects of UI style independent of theme used (UI roundness, fonts, underlay)
diff --git a/changelog.d/video-poster.fix b/changelog.d/video-poster.fix
deleted file mode 100644
index 43e95f6e1f..0000000000
--- a/changelog.d/video-poster.fix
+++ /dev/null
@@ -1 +0,0 @@
-Video posters on Safari
diff --git a/changelog.d/video-poster.update.skip b/changelog.d/video-poster.update.skip
deleted file mode 100644
index 9dafe9be20..0000000000
--- a/changelog.d/video-poster.update.skip
+++ /dev/null
@@ -1 +0,0 @@
-nothing
diff --git a/changelog.d/web-push-always.add b/changelog.d/web-push-always.add
deleted file mode 100644
index f8b8888a23..0000000000
--- a/changelog.d/web-push-always.add
+++ /dev/null
@@ -1 +0,0 @@
-Added option to always "show" notifications when using web push for better compatibility with some browsers (chrome, edge, safari)
diff --git a/package.json b/package.json
index fb157fae65..45b0466038 100644
--- a/package.json
+++ b/package.json
@@ -1,136 +1,136 @@
{
"name": "pleroma_fe",
- "version": "2.6.1",
+ "version": "2.7.1",
"description": "Pleroma frontend, the default frontend of Pleroma social network server",
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/CONTRIBUTORS.md>",
"private": false,
"scripts": {
"dev": "node build/dev-server.js",
"build": "node build/build.js",
"unit": "karma start test/unit/karma.conf.js --single-run",
"unit:watch": "karma start test/unit/karma.conf.js --single-run=false",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"stylelint": "npx stylelint '**/*.scss' '**/*.vue'",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
},
"dependencies": {
"@babel/runtime": "7.21.5",
"@chenfengyuan/vue-qrcode": "2.0.0",
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-regular-svg-icons": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@fortawesome/vue-fontawesome": "3.0.3",
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
"@ruffle-rs/ruffle": "0.1.0-nightly.2024.3.17",
"@vuelidate/core": "2.0.3",
"@vuelidate/validators": "2.0.4",
"body-scroll-lock": "3.1.5",
"chromatism": "3.0.0",
"click-outside-vue3": "4.0.1",
"cropperjs": "1.5.13",
"escape-html": "1.0.3",
"hash-sum": "^2.0.0",
"js-cookie": "3.0.5",
"localforage": "1.10.0",
"parse-link-header": "2.0.0",
"phoenix": "1.7.7",
"punycode.js": "2.3.0",
"qrcode": "1.5.3",
"querystring-es3": "0.2.1",
"url": "0.11.0",
"utf8": "3.0.0",
"vue": "3.2.45",
"vue-i18n": "9.2.2",
"vue-router": "4.1.6",
"vue-template-compiler": "2.7.14",
"vue-virtual-scroller": "^2.0.0-beta.7",
"vuex": "4.1.0"
},
"devDependencies": {
"@babel/core": "7.21.8",
"@babel/eslint-parser": "7.21.8",
"@babel/plugin-transform-runtime": "7.21.4",
"@babel/preset-env": "7.21.5",
"@babel/register": "7.21.0",
"@intlify/vue-i18n-loader": "5.0.1",
"@ungap/event-target": "0.2.4",
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
"@vue/babel-plugin-jsx": "1.2.1",
"@vue/compiler-sfc": "3.2.45",
"@vue/test-utils": "2.2.8",
"autoprefixer": "10.4.19",
"babel-loader": "9.1.3",
"babel-plugin-lodash": "3.3.4",
"chai": "4.3.7",
"chalk": "1.1.3",
"chromedriver": "108.0.0",
"connect-history-api-fallback": "2.0.0",
"copy-webpack-plugin": "11.0.0",
"cross-spawn": "7.0.3",
"css-loader": "6.10.0",
"css-minimizer-webpack-plugin": "4.2.2",
"custom-event-polyfill": "1.0.7",
"eslint": "8.33.0",
"eslint-config-standard": "17.0.0",
"eslint-formatter-friendly": "7.0.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-n": "15.6.1",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-vue": "9.9.0",
"eslint-webpack-plugin": "3.2.0",
"eventsource-polyfill": "0.9.6",
"express": "4.18.2",
"function-bind": "1.1.1",
"html-webpack-plugin": "5.5.1",
"http-proxy-middleware": "2.0.6",
"iso-639-1": "2.1.15",
"json-loader": "0.5.7",
"karma": "6.4.2",
"karma-coverage": "2.2.0",
"karma-firefox-launcher": "2.1.2",
"karma-mocha": "2.0.1",
"karma-mocha-reporter": "2.2.5",
"karma-sinon-chai": "2.0.2",
"karma-sourcemap-loader": "0.3.8",
"karma-spec-reporter": "0.0.36",
"karma-webpack": "5.0.0",
"lodash": "4.17.21",
"mini-css-extract-plugin": "2.7.6",
"mocha": "10.2.0",
"nightwatch": "2.6.25",
"opn": "5.5.0",
"ora": "0.4.1",
"postcss": "8.4.23",
"postcss-html": "^1.5.0",
"postcss-loader": "7.0.2",
"postcss-scss": "^4.0.6",
"sass": "1.60.0",
"sass-loader": "13.2.2",
"selenium-server": "2.53.1",
"semver": "7.3.8",
"serviceworker-webpack5-plugin": "2.0.0",
"shelljs": "0.8.5",
"sinon": "15.0.4",
"sinon-chai": "3.7.0",
"stylelint": "14.16.1",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recommended-scss": "^8.0.0",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "29.0.0",
"stylelint-rscss": "0.4.0",
"stylelint-webpack-plugin": "^3.3.0",
"vue-loader": "17.0.1",
"vue-style-loader": "4.1.3",
"webpack": "5.75.0",
"webpack-dev-middleware": "3.7.3",
"webpack-hot-middleware": "2.25.3",
"webpack-merge": "0.20.0"
},
"engines": {
"node": ">= 16.0.0",
"npm": ">= 3.0.0"
}
}
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index a486bd4c79..6cad05f62a 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -1,403 +1,404 @@
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import vClickOutside from 'click-outside-vue3'
import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
import App from '../App.vue'
import routes from './routes'
import VBodyScrollLock from 'src/directives/body_scroll_lock'
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { applyConfig } from '../services/style_setter/style_setter.js'
import FaviconService from '../services/favicon_service/favicon_service.js'
import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
let staticInitialResults = null
const parsedInitialResults = () => {
if (!document.getElementById('initial-results')) {
return null
}
if (!staticInitialResults) {
staticInitialResults = JSON.parse(document.getElementById('initial-results').textContent)
}
return staticInitialResults
}
const decodeUTF8Base64 = (data) => {
const rawData = atob(data)
const array = Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)))
const text = new TextDecoder().decode(array)
return text
}
const preloadFetch = async (request) => {
const data = parsedInitialResults()
if (!data || !data[request]) {
return window.fetch(request)
}
const decoded = decodeUTF8Base64(data[request])
const requestData = JSON.parse(decoded)
return {
ok: true,
json: () => requestData,
text: () => requestData
}
}
const getInstanceConfig = async ({ store }) => {
try {
const res = await preloadFetch('/api/v1/instance')
if (res.ok) {
const data = await res.json()
const textlimit = data.max_toot_chars
const vapidPublicKey = data.pleroma.vapid_public_key
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required })
store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 })
if (vapidPublicKey) {
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
}
} else {
throw (res)
}
} catch (error) {
console.error('Could not load instance config, potentially fatal')
console.error(error)
}
}
const getBackendProvidedConfig = async ({ store }) => {
try {
const res = await window.fetch('/api/pleroma/frontend_configurations')
if (res.ok) {
const data = await res.json()
return data.pleroma_fe
} else {
throw (res)
}
} catch (error) {
console.error('Could not load backend-provided frontend config, potentially fatal')
console.error(error)
}
}
const getStaticConfig = async () => {
try {
const res = await window.fetch('/static/config.json')
if (res.ok) {
return res.json()
} else {
throw (res)
}
} catch (error) {
console.warn('Failed to load static/config.json, continuing without it.')
console.warn(error)
return {}
}
}
const setSettings = async ({ apiConfig, staticConfig, store }) => {
const overrides = window.___pleromafe_dev_overrides || {}
const env = window.___pleromafe_mode.NODE_ENV
// This takes static config and overrides properties that are present in apiConfig
let config = {}
if (overrides.staticConfigPreference && env === 'development') {
console.warn('OVERRIDING API CONFIG WITH STATIC CONFIG')
config = Object.assign({}, apiConfig, staticConfig)
} else {
config = Object.assign({}, staticConfig, apiConfig)
}
const copyInstanceOption = (name) => {
store.dispatch('setInstanceOption', { name, value: config[name] })
}
+ copyInstanceOption('theme')
copyInstanceOption('nsfwCensorImage')
copyInstanceOption('background')
copyInstanceOption('hidePostStats')
copyInstanceOption('hideBotIndication')
copyInstanceOption('hideUserStats')
copyInstanceOption('hideFilteredStatuses')
copyInstanceOption('logo')
store.dispatch('setInstanceOption', {
name: 'logoMask',
value: typeof config.logoMask === 'undefined'
? true
: config.logoMask
})
store.dispatch('setInstanceOption', {
name: 'logoMargin',
value: typeof config.logoMargin === 'undefined'
? 0
: config.logoMargin
})
copyInstanceOption('logoLeft')
store.commit('authFlow/setInitialStrategy', config.loginMethod)
copyInstanceOption('redirectRootNoLogin')
copyInstanceOption('redirectRootLogin')
copyInstanceOption('showInstanceSpecificPanel')
copyInstanceOption('minimalScopesMode')
copyInstanceOption('hideMutedPosts')
copyInstanceOption('collapseMessageWithSubject')
copyInstanceOption('scopeCopy')
copyInstanceOption('subjectLineBehavior')
copyInstanceOption('postContentType')
copyInstanceOption('alwaysShowSubjectInput')
copyInstanceOption('showFeaturesPanel')
copyInstanceOption('hideSitename')
copyInstanceOption('sidebarRight')
}
const getTOS = async ({ store }) => {
try {
const res = await window.fetch('/static/terms-of-service.html')
if (res.ok) {
const html = await res.text()
store.dispatch('setInstanceOption', { name: 'tos', value: html })
} else {
throw (res)
}
} catch (e) {
console.warn("Can't load TOS")
console.warn(e)
}
}
const getInstancePanel = async ({ store }) => {
try {
const res = await preloadFetch('/instance/panel.html')
if (res.ok) {
const html = await res.text()
store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html })
} else {
throw (res)
}
} catch (e) {
console.warn("Can't load instance panel")
console.warn(e)
}
}
const getStickers = async ({ store }) => {
try {
const res = await window.fetch('/static/stickers.json')
if (res.ok) {
const values = await res.json()
const stickers = (await Promise.all(
Object.entries(values).map(async ([name, path]) => {
const resPack = await window.fetch(path + 'pack.json')
let meta = {}
if (resPack.ok) {
meta = await resPack.json()
}
return {
pack: name,
path,
meta
}
})
)).sort((a, b) => {
return a.meta.title.localeCompare(b.meta.title)
})
store.dispatch('setInstanceOption', { name: 'stickers', value: stickers })
} else {
throw (res)
}
} catch (e) {
console.warn("Can't load stickers")
console.warn(e)
}
}
const getAppSecret = async ({ store }) => {
const { state, commit } = store
const { oauth, instance } = state
return getOrCreateApp({ ...oauth, instance: instance.server, commit })
.then((app) => getClientToken({ ...app, instance: instance.server }))
.then((token) => {
commit('setAppToken', token.access_token)
commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
})
}
const resolveStaffAccounts = ({ store, accounts }) => {
const nicknames = accounts.map(uri => uri.split('/').pop())
store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
}
const getNodeInfo = async ({ store }) => {
try {
const res = await preloadFetch('/nodeinfo/2.0.json')
if (res.ok) {
const data = await res.json()
const metadata = data.metadata
const features = metadata.features
store.dispatch('setInstanceOption', { name: 'name', value: metadata.nodeName })
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
store.dispatch('setInstanceOption', { name: 'pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') })
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
const uploadLimits = metadata.uploadLimits
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadLimits.avatar) })
store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadLimits.background) })
store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadLimits.banner) })
store.dispatch('setInstanceOption', { name: 'fieldsLimits', value: metadata.fieldsLimits })
store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
const suggestions = metadata.suggestions
store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled })
store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web })
const software = data.software
store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
const priv = metadata.private
store.dispatch('setInstanceOption', { name: 'private', value: priv })
const frontendVersion = window.___pleromafe_commit_hash
store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
const federation = metadata.federation
store.dispatch('setInstanceOption', {
name: 'tagPolicyAvailable',
value: typeof federation.mrf_policies === 'undefined'
? false
: metadata.federation.mrf_policies.includes('TagPolicy')
})
store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
store.dispatch('setInstanceOption', {
name: 'federating',
value: typeof federation.enabled === 'undefined'
? true
: federation.enabled
})
const accountActivationRequired = metadata.accountActivationRequired
store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired })
const accounts = metadata.staffAccounts
resolveStaffAccounts({ store, accounts })
} else {
throw (res)
}
} catch (e) {
console.warn('Could not load nodeinfo')
console.warn(e)
}
}
const setConfig = async ({ store }) => {
// apiConfig, staticConfig
const configInfos = await Promise.all([getBackendProvidedConfig({ store }), getStaticConfig()])
const apiConfig = configInfos[0]
const staticConfig = configInfos[1]
await setSettings({ store, apiConfig, staticConfig }).then(getAppSecret({ store }))
}
const checkOAuthToken = async ({ store }) => {
if (store.getters.getUserToken()) {
try {
await store.dispatch('loginUser', store.getters.getUserToken())
} catch (e) {
console.error(e)
}
}
return Promise.resolve()
}
const afterStoreSetup = async ({ store, i18n }) => {
store.dispatch('setLayoutWidth', windowWidth())
store.dispatch('setLayoutHeight', windowHeight())
FaviconService.initFaviconService()
initServiceWorker(store)
window.addEventListener('focus', () => updateFocus())
const overrides = window.___pleromafe_dev_overrides || {}
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
store.dispatch('setInstanceOption', { name: 'server', value: server })
await setConfig({ store })
await store.dispatch('setTheme')
applyConfig(store.state.config)
// Now we can try getting the server settings and logging in
// Most of these are preloaded into the index.html so blocking is minimized
await Promise.all([
checkOAuthToken({ store }),
getInstancePanel({ store }),
getNodeInfo({ store }),
getInstanceConfig({ store })
])
// Start fetching things that don't need to block the UI
store.dispatch('fetchMutes')
store.dispatch('startFetchingAnnouncements')
getTOS({ store })
getStickers({ store })
const router = createRouter({
history: createWebHistory(),
routes: routes(store),
scrollBehavior: (to, _from, savedPosition) => {
if (to.matched.some(m => m.meta.dontScroll)) {
return false
}
return savedPosition || { left: 0, top: 0 }
}
})
const app = createApp(App)
app.use(router)
app.use(store)
app.use(i18n)
app.use(vClickOutside)
app.use(VBodyScrollLock)
app.use(VueVirtualScroller)
app.component('FAIcon', FontAwesomeIcon)
app.component('FALayers', FontAwesomeLayers)
// remove after vue 3.3
app.config.unwrapInjectedRef = true
app.mount('#app')
return app
}
export default afterStoreSetup
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js
index d71bc1bbfc..9ea5c877ad 100644
--- a/src/components/emoji_picker/emoji_picker.js
+++ b/src/components/emoji_picker/emoji_picker.js
@@ -1,385 +1,387 @@
import { defineAsyncComponent } from 'vue'
import Checkbox from '../checkbox/checkbox.vue'
import Popover from 'src/components/popover/popover.vue'
import StillImage from '../still-image/still-image.vue'
import { ensureFinalFallback } from '../../i18n/languages.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faBoxOpen,
faStickyNote,
faSmileBeam,
faSmile,
faUser,
faPaw,
faIceCream,
faBus,
faBasketballBall,
faLightbulb,
faCode,
faFlag
} from '@fortawesome/free-solid-svg-icons'
import { debounce, trim, chunk } from 'lodash'
library.add(
faBoxOpen,
faStickyNote,
faSmileBeam,
faSmile,
faUser,
faPaw,
faIceCream,
faBus,
faBasketballBall,
faLightbulb,
faCode,
faFlag
)
const UNICODE_EMOJI_GROUP_ICON = {
'smileys-and-emotion': 'smile',
'people-and-body': 'user',
'animals-and-nature': 'paw',
'food-and-drink': 'ice-cream',
'travel-and-places': 'bus',
activities: 'basketball-ball',
objects: 'lightbulb',
symbols: 'code',
flags: 'flag'
}
const maybeLocalizedKeywords = (emoji, languages, nameLocalizer) => {
const res = [emoji.displayText, nameLocalizer(emoji)]
if (emoji.annotations) {
languages.forEach(lang => {
const keywords = emoji.annotations[lang]?.keywords || []
const name = emoji.annotations[lang]?.name
res.push(...(keywords.concat([name]).filter(k => k)))
})
}
return res
}
const filterByKeyword = (list, keyword = '', languages, nameLocalizer) => {
if (keyword === '') return list
const keywordLowercase = keyword.toLowerCase()
const orderedEmojiList = []
for (const emoji of list) {
const indices = maybeLocalizedKeywords(emoji, languages, nameLocalizer)
.map(k => k.toLowerCase().indexOf(keywordLowercase))
.filter(k => k > -1)
const indexOfKeyword = indices.length ? Math.min(...indices) : -1
if (indexOfKeyword > -1) {
if (!Array.isArray(orderedEmojiList[indexOfKeyword])) {
orderedEmojiList[indexOfKeyword] = []
}
orderedEmojiList[indexOfKeyword].push(emoji)
}
}
return orderedEmojiList.flat()
}
const getOffset = (elem) => {
const style = elem.style.transform
const res = /translateY\((\d+)px\)/.exec(style)
if (!res) { return 0 }
return res[1]
}
const toHeaderId = id => {
return id.replace(/^row-\d+-/, '')
}
const EmojiPicker = {
props: {
enableStickerPicker: {
required: false,
type: Boolean,
default: false
},
hideCustomEmoji: {
required: false,
type: Boolean,
default: false
}
},
inject: ['popoversZLayer'],
data () {
return {
keyword: '',
activeGroup: 'custom',
showingStickers: false,
groupsScrolledClass: 'scrolled-top',
keepOpen: false,
customEmojiTimeout: null,
hideCustomEmojiInPicker: false,
// Lazy-load only after the first time `showing` becomes true.
contentLoaded: false,
groupRefs: {},
emojiRefs: {},
filteredEmojiGroups: [],
emojiSize: 0,
width: 0
}
},
components: {
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
Checkbox,
StillImage,
Popover
},
methods: {
updateEmojiSize () {
const css = window.getComputedStyle(this.$refs.popover.$el)
const emojiSize = css.getPropertyValue('--emojiSize')
const emojiSizeUnit = emojiSize.replace(/[0-9,.]+/, '')
const emojiSizeValue = Number(emojiSize.replace(/[^0-9,.]+/, ''))
const fontSize = css.getPropertyValue('font-size').replace(/[^0-9,.]+/, '')
let emojiSizeReal
if (emojiSizeUnit.endsWith('em')) {
emojiSizeReal = emojiSizeValue * fontSize
} else {
emojiSizeReal = emojiSizeValue
}
const fullEmojiSize = emojiSizeReal + (2 * 0.2 * fontSize)
this.emojiSize = fullEmojiSize
},
showPicker () {
this.$refs.popover.showPopover()
- this.onShowing()
+ this.$nextTick(() => {
+ this.onShowing()
+ })
},
hidePicker () {
this.$refs.popover.hidePopover()
},
setAnchorEl (el) {
this.$refs.popover.setAnchorEl(el)
},
setGroupRef (name) {
return el => { this.groupRefs[name] = el }
},
onPopoverShown () {
this.$emit('show')
},
onPopoverClosed () {
this.$emit('close')
},
onStickerUploaded (e) {
this.$emit('sticker-uploaded', e)
},
onStickerUploadFailed (e) {
this.$emit('sticker-upload-failed', e)
},
onEmoji (emoji) {
const value = emoji.imageUrl ? `:${emoji.displayText}:` : emoji.replacement
if (!this.keepOpen) {
this.$refs.popover.hidePopover()
}
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
},
onScroll (startIndex, endIndex, visibleStartIndex, visibleEndIndex) {
const target = this.$refs['emoji-groups'].$el
this.scrolledGroup(target, visibleStartIndex, visibleEndIndex)
},
scrolledGroup (target, start, end) {
const top = target.scrollTop + 5
this.$nextTick(() => {
this.emojiItems.slice(start, end + 1).forEach(group => {
const headerId = toHeaderId(group.id)
const ref = this.groupRefs['group-' + group.id]
if (!ref) { return }
const elem = ref.$el.parentElement
if (!elem) { return }
if (elem && getOffset(elem) <= top) {
this.activeGroup = headerId
}
})
this.scrollHeader()
})
},
scrollHeader () {
// Scroll the active tab's header into view
const headerRef = this.groupRefs['group-header-' + this.activeGroup]
const left = headerRef.offsetLeft
const right = left + headerRef.offsetWidth
const headerCont = this.$refs.header
const currentScroll = headerCont.scrollLeft
const currentScrollRight = currentScroll + headerCont.clientWidth
const setScroll = s => { headerCont.scrollLeft = s }
const margin = 7 // .emoji-tabs-item: padding
if (left - margin < currentScroll) {
setScroll(left - margin)
} else if (right + margin > currentScrollRight) {
setScroll(right + margin - headerCont.clientWidth)
}
},
highlight (groupId) {
this.setShowStickers(false)
const indexInList = this.emojiItems.findIndex(k => k.id === groupId)
this.$refs['emoji-groups'].scrollToItem(indexInList)
},
updateScrolledClass (target) {
if (target.scrollTop <= 5) {
this.groupsScrolledClass = 'scrolled-top'
} else if (target.scrollTop >= target.scrollTopMax - 5) {
this.groupsScrolledClass = 'scrolled-bottom'
} else {
this.groupsScrolledClass = 'scrolled-middle'
}
},
toggleStickers () {
this.showingStickers = !this.showingStickers
},
setShowStickers (value) {
this.showingStickers = value
},
filterByKeyword (list, keyword) {
return filterByKeyword(list, keyword, this.languages, this.maybeLocalizedEmojiName)
},
onShowing () {
const oldContentLoaded = this.contentLoaded
this.updateEmojiSize()
this.recalculateItemPerRow()
this.$nextTick(() => {
this.$refs.search.focus()
})
this.contentLoaded = true
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
if (!oldContentLoaded) {
this.$nextTick(() => {
if (this.defaultGroup) {
this.highlight(this.defaultGroup)
}
})
}
},
getFilteredEmojiGroups () {
return this.allEmojiGroups
.map(group => ({
...group,
emojis: this.filterByKeyword(group.emojis, trim(this.keyword))
}))
.filter(group => group.emojis.length > 0)
},
recalculateItemPerRow () {
this.$nextTick(() => {
if (!this.$refs['emoji-groups']) {
return
}
this.width = this.$refs['emoji-groups'].$el.clientWidth
})
}
},
watch: {
keyword () {
this.onScroll()
this.debouncedHandleKeywordChange()
},
allCustomGroups () {
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
}
},
computed: {
minItemSize () {
return this.emojiSize
},
// used to watch it
fontSize () {
this.$nextTick(() => {
this.updateEmojiSize()
})
return this.$store.getters.mergedConfig.fontSize
},
emojiHeight () {
return this.emojiSize
},
itemPerRow () {
return this.width ? Math.floor(this.width / this.emojiSize) : 6
},
activeGroupView () {
return this.showingStickers ? '' : this.activeGroup
},
stickersAvailable () {
if (this.$store.state.instance.stickers) {
return this.$store.state.instance.stickers.length > 0
}
return 0
},
allCustomGroups () {
if (this.hideCustomEmoji || this.hideCustomEmojiInPicker) {
return {}
}
const emojis = this.$store.getters.groupedCustomEmojis
if (emojis.unpacked) {
emojis.unpacked.text = this.$t('emoji.unpacked')
}
return emojis
},
defaultGroup () {
return Object.keys(this.allCustomGroups)[0]
},
unicodeEmojiGroups () {
return this.$store.getters.standardEmojiGroupList.map(group => ({
id: `standard-${group.id}`,
text: this.$t(`emoji.unicode_groups.${group.id}`),
icon: UNICODE_EMOJI_GROUP_ICON[group.id],
emojis: group.emojis
}))
},
allEmojiGroups () {
return Object.entries(this.allCustomGroups)
.map(([_, v]) => v)
.concat(this.unicodeEmojiGroups)
},
stickerPickerEnabled () {
return (this.$store.state.instance.stickers || []).length !== 0
},
debouncedHandleKeywordChange () {
return debounce(() => {
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
}, 500)
},
emojiItems () {
return this.filteredEmojiGroups.map(group =>
chunk(group.emojis, this.itemPerRow)
.map((items, index) => ({
...group,
id: index === 0 ? group.id : `row-${index}-${group.id}`,
emojis: items,
isFirstRow: index === 0
})))
.reduce((a, c) => a.concat(c), [])
},
languages () {
return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
},
maybeLocalizedEmojiName () {
return emoji => {
if (!emoji.annotations) {
return emoji.displayText
}
if (emoji.displayTextI18n) {
return this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args)
}
for (const lang of this.languages) {
if (emoji.annotations[lang]?.name) {
return emoji.annotations[lang].name
}
}
return emoji.displayText
}
},
isInModal () {
return this.popoversZLayer === 'modals'
}
}
}
export default EmojiPicker
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
index 7c36deaa84..a3dc8f9eed 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -1,172 +1,173 @@
<template>
<Popover
ref="popover"
trigger="click"
popover-class="emoji-picker popover-default"
:trigger-attrs="{ 'aria-hidden': true, tabindex: -1 }"
@show="onPopoverShown"
@close="onPopoverClosed"
>
<template #content>
<div class="heading">
<!--
Body scroll lock needs to be on every scrollable element on safari iOS.
Here we tell it to enable scrolling for this element.
See https://github.com/willmcpo/body-scroll-lock#vanilla-js
-->
<span
ref="header"
v-body-scroll-lock="isInModal"
class="emoji-tabs"
>
<span
v-for="group in filteredEmojiGroups"
:ref="setGroupRef('group-header-' + group.id)"
:key="group.id"
class="button-unstyled emoji-tabs-item"
:class="{
toggled: activeGroupView === group.id
}"
:title="group.text"
role="button"
@click.prevent="highlight(group.id)"
>
<span
v-if="group.image"
class="emoji-picker-header-image"
>
<still-image
:alt="group.text"
:src="group.image"
/>
</span>
<FAIcon
v-else
:icon="group.icon"
fixed-width
/>
</span>
</span>
<span
v-if="stickerPickerEnabled"
class="additional-tabs"
>
<span
class="button-unstyled stickers-tab-icon additional-tabs-item"
:class="{toggled: showingStickers}"
:title="$t('emoji.stickers')"
@click.prevent="toggleStickers"
>
<FAIcon
icon="sticky-note"
fixed-width
/>
</span>
</span>
</div>
<div
v-if="contentLoaded"
class="content"
>
<div
class="emoji-content"
:class="{hidden: showingStickers}"
>
<div class="emoji-search">
<input
ref="search"
v-model="keyword"
type="text"
class="input form-control"
:placeholder="$t('emoji.search_emoji')"
@input="$event.target.composing = false"
>
</div>
<!-- Enables scrolling for this element on safari iOS. See comments for header. -->
<DynamicScroller
ref="emoji-groups"
v-body-scroll-lock="isInModal"
class="emoji-groups"
:class="groupsScrolledClass"
:min-item-size="minItemSize"
+ :buffer="minItemSize"
:items="emojiItems"
:emit-update="true"
@update="onScroll"
@visible="recalculateItemPerRow"
@resize="recalculateItemPerRow"
>
<template #default="{ item: group, index, active }">
<DynamicScrollerItem
:ref="setGroupRef('group-' + group.id)"
:item="group"
:active="active"
:data-index="index"
:size-dependencies="[group.emojis.length]"
>
<div
class="emoji-group"
>
<h6
v-if="group.isFirstRow"
class="emoji-group-title"
>
{{ group.text }}
</h6>
<span
v-for="emoji in group.emojis"
:key="group.id + emoji.displayText"
:title="maybeLocalizedEmojiName(emoji)"
class="emoji-item"
role="button"
@click.stop.prevent="onEmoji(emoji)"
>
<span
v-if="!emoji.imageUrl"
class="emoji-picker-emoji -unicode"
>{{ emoji.replacement }}</span>
<still-image
v-else
class="emoji-picker-emoji -custom"
loading="lazy"
:alt="maybeLocalizedEmojiName(emoji)"
:src="emoji.imageUrl"
:data-emoji-name="group.id + emoji.displayText"
/>
</span>
</div>
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div class="keep-open">
<Checkbox v-model="keepOpen">
{{ $t('emoji.keep_open') }}
</Checkbox>
</div>
<div
v-if="!hideCustomEmoji"
class="hide-custom-emoji"
>
<Checkbox
v-model="hideCustomEmojiInPicker"
@change="onShowing"
>
{{ $t('emoji.hide_custom_emoji') }}
</Checkbox>
</div>
</div>
<div
v-if="showingStickers"
class="stickers-content"
>
<sticker-picker
@uploaded="onStickerUploaded"
@upload-failed="onStickerUploadFailed"
/>
</div>
</div>
</template>
</Popover>
</template>
<script src="./emoji_picker.js"></script>
<style lang="scss" src="./emoji_picker.scss"></style>
diff --git a/src/components/timeline/timeline.scss b/src/components/timeline/timeline.scss
index 0fc0d97957..2dd66328de 100644
--- a/src/components/timeline/timeline.scss
+++ b/src/components/timeline/timeline.scss
@@ -1,51 +1,51 @@
.Timeline {
.timeline-body {
background: none;
backdrop-filter: none;
}
.alert-badge {
font-size: 0.75em;
line-height: 1;
text-align: right;
border-radius: var(--roundness);
position: absolute;
left: calc(50% - 0.5em);
top: calc(50% - 0.4em);
padding: 0.2em;
margin-left: 0.7em;
margin-top: -1em;
}
.loadmore-button {
position: relative;
}
&.-blocked {
cursor: progress;
}
.conversation-heading {
- top: calc(var(--__panel-heading-height) * var(--currentPanelStack, 2));
+ top: calc(var(--__panel-heading-height) * var(--currentPanelStack, 1) + var(--navbar-height));
z-index: 2;
}
&.-embedded {
.timeline-heading {
text-align: center;
line-height: 2.75em;
padding: 0 0.5em;
// Override the shrug empty filler
&:empty::before {
content: initial;
}
.button-default,
.alert {
line-height: 2em;
width: 100%;
}
}
}
}
diff --git a/src/services/theme_data/theme2_to_theme3.js b/src/services/theme_data/theme2_to_theme3.js
index 95eb03c19d..bcc0c961b8 100644
--- a/src/services/theme_data/theme2_to_theme3.js
+++ b/src/services/theme_data/theme2_to_theme3.js
@@ -1,539 +1,538 @@
import { convert } from 'chromatism'
import allKeys from './theme2_keys'
// keys that are meant to be used globally, i.e. what's the rest of the theme is based upon.
export const basePaletteKeys = new Set([
'bg',
'fg',
'text',
'link',
'accent',
'cBlue',
'cRed',
'cGreen',
'cOrange',
'wallpaper'
])
export const fontsKeys = new Set([
'interface',
'input',
'post',
'postCode'
])
export const opacityKeys = new Set([
'alert',
'alertPopup',
'bg',
'border',
'btn',
'faint',
'input',
'panel',
'popover',
'profileTint',
'underlay'
])
export const shadowsKeys = new Set([
'panel',
'topBar',
'popup',
'avatar',
'avatarStatus',
'panelHeader',
'button',
'buttonHover',
'buttonPressed',
'input'
])
export const radiiKeys = new Set([
'btn',
'input',
'checkbox',
'panel',
'avatar',
'avatarAlt',
'tooltip',
'attachment',
'chatMessage'
])
// Keys that are not available in editor and never meant to be edited
export const hiddenKeys = new Set([
'profileBg',
'profileTint'
])
export const extendedBasePrefixes = [
'border',
'icon',
'highlight',
'lightText',
'popover',
'panel',
'topBar',
'tab',
'btn',
'input',
'selectedMenu',
'alert',
'alertPopup',
'badge',
'post',
'selectedPost', // wrong nomenclature
'poll',
'chatBg',
'chatMessage'
]
export const nonComponentPrefixes = new Set([
'border',
'icon',
'highlight',
'lightText',
'chatBg'
])
export const extendedBaseKeys = Object.fromEntries(
extendedBasePrefixes.map(prefix => [
prefix,
allKeys.filter(k => {
if (prefix === 'alert') {
return k.startsWith(prefix) && !k.startsWith('alertPopup')
}
return k.startsWith(prefix)
})
])
)
// Keysets that are only really used intermideately, i.e. to generate other colors
export const temporary = new Set([
'',
'highlight'
])
export const temporaryColors = {}
export const convertTheme2To3 = (data) => {
data.colors.accent = data.colors.accent || data.colors.link
data.colors.link = data.colors.link || data.colors.accent
const generateRoot = () => {
const directives = {}
basePaletteKeys.forEach(key => { directives['--' + key] = 'color | ' + convert(data.colors[key]).hex })
return {
component: 'Root',
directives
}
}
const convertOpacity = () => {
const newRules = []
Object.keys(data.opacity || {}).forEach(key => {
if (!opacityKeys.has(key) || data.opacity[key] === undefined) return null
const originalOpacity = data.opacity[key]
const rule = { source: '2to3' }
switch (key) {
case 'alert':
rule.component = 'Alert'
break
case 'alertPopup':
rule.component = 'Alert'
rule.parent = { component: 'Popover' }
break
case 'bg':
rule.component = 'Panel'
break
case 'border':
rule.component = 'Border'
break
case 'btn':
rule.component = 'Button'
break
case 'faint':
rule.component = 'Text'
rule.state = ['faint']
break
case 'input':
rule.component = 'Input'
break
case 'panel':
rule.component = 'PanelHeader'
break
case 'popover':
rule.component = 'Popover'
break
case 'profileTint':
return null
case 'underlay':
rule.component = 'Underlay'
break
}
switch (key) {
case 'alert':
case 'alertPopup':
case 'bg':
case 'btn':
case 'input':
case 'panel':
case 'popover':
case 'underlay':
rule.directives = { opacity: originalOpacity }
break
case 'faint':
case 'border':
rule.directives = { textOpacity: originalOpacity }
break
}
newRules.push(rule)
if (rule.component === 'Button') {
newRules.push({ ...rule, component: 'ScrollbarElement' })
newRules.push({ ...rule, component: 'Tab' })
newRules.push({ ...rule, component: 'Tab', state: ['active'], directives: { opacity: 0 } })
}
if (rule.component === 'Panel') {
newRules.push({ ...rule, component: 'Post' })
}
})
return newRules
}
const convertRadii = () => {
const newRules = []
Object.keys(data.radii || {}).forEach(key => {
if (!radiiKeys.has(key) || data.radii[key] === undefined) return null
const originalRadius = data.radii[key]
const rule = { source: '2to3' }
switch (key) {
case 'btn':
rule.component = 'Button'
break
case 'tab':
rule.component = 'Tab'
break
case 'input':
rule.component = 'Input'
break
case 'checkbox':
rule.component = 'Input'
rule.variant = 'checkbox'
break
case 'panel':
rule.component = 'Panel'
break
case 'avatar':
rule.component = 'Avatar'
break
case 'avatarAlt':
rule.component = 'Avatar'
rule.variant = 'compact'
break
case 'tooltip':
rule.component = 'Popover'
break
case 'attachment':
rule.component = 'Attachment'
break
case 'ChatMessage':
rule.component = 'Button'
break
}
rule.directives = {
roundness: originalRadius
}
newRules.push(rule)
if (rule.component === 'Button') {
newRules.push({ ...rule, component: 'ScrollbarElement' })
newRules.push({ ...rule, component: 'Tab' })
}
})
return newRules
}
const convertFonts = () => {
const newRules = []
Object.keys(data.fonts || {}).forEach(key => {
if (!fontsKeys.has(key)) return
if (!data.fonts[key]) return
const originalFont = data.fonts[key].family
const rule = { source: '2to3' }
switch (key) {
case 'interface':
case 'postCode':
rule.component = 'Root'
break
case 'input':
rule.component = 'Input'
break
case 'post':
rule.component = 'RichContent'
break
}
switch (key) {
case 'interface':
case 'input':
case 'post':
rule.directives = { '--font': 'generic | ' + originalFont }
break
case 'postCode':
rule.directives = { '--monoFont': 'generic | ' + originalFont }
newRules.push({ ...rule, component: 'RichContent' })
break
}
newRules.push(rule)
})
return newRules
}
const convertShadows = () => {
const newRules = []
Object.keys(data.shadows || {}).forEach(key => {
if (!shadowsKeys.has(key)) return
const originalShadow = data.shadows[key]
const rule = { source: '2to3' }
switch (key) {
case 'panel':
rule.component = 'Panel'
break
case 'topBar':
rule.component = 'TopBar'
break
case 'popup':
rule.component = 'Popover'
break
case 'avatar':
rule.component = 'Avatar'
break
case 'avatarStatus':
rule.component = 'Avatar'
rule.parent = { component: 'Post' }
break
case 'panelHeader':
rule.component = 'PanelHeader'
break
case 'button':
rule.component = 'Button'
break
case 'buttonHover':
rule.component = 'Button'
rule.state = ['hover']
break
case 'buttonPressed':
rule.component = 'Button'
rule.state = ['pressed']
break
case 'input':
rule.component = 'Input'
break
}
rule.directives = {
shadow: originalShadow
}
newRules.push(rule)
if (key === 'topBar') {
newRules.push({ ...rule, component: 'PanelHeader', parent: { component: 'MobileDrawer' } })
}
if (key === 'avatarStatus') {
newRules.push({ ...rule, parent: { component: 'Notification' } })
}
if (key === 'buttonPressed') {
newRules.push({ ...rule, state: ['toggled'] })
newRules.push({ ...rule, state: ['toggled', 'focus'] })
newRules.push({ ...rule, state: ['pressed', 'focus'] })
}
if (key === 'buttonHover') {
newRules.push({ ...rule, state: ['toggled', 'hover'] })
newRules.push({ ...rule, state: ['pressed', 'hover'] })
newRules.push({ ...rule, state: ['toggled', 'focus', 'hover'] })
newRules.push({ ...rule, state: ['pressed', 'focus', 'hover'] })
}
if (rule.component === 'Button') {
newRules.push({ ...rule, component: 'ScrollbarElement' })
newRules.push({ ...rule, component: 'Tab' })
}
})
return newRules
}
const extendedRules = Object.entries(extendedBaseKeys).map(([prefix, keys]) => {
if (nonComponentPrefixes.has(prefix)) return null
const rule = { source: '2to3' }
if (prefix === 'alertPopup') {
rule.component = 'Alert'
rule.parent = { component: 'Popover' }
} else if (prefix === 'selectedPost') {
rule.component = 'Post'
rule.state = ['selected']
} else if (prefix === 'selectedMenu') {
rule.component = 'MenuItem'
rule.state = ['hover']
} else if (prefix === 'chatMessageIncoming') {
rule.component = 'ChatMessage'
} else if (prefix === 'chatMessageOutgoing') {
rule.component = 'ChatMessage'
rule.variant = 'outgoing'
} else if (prefix === 'panel') {
rule.component = 'PanelHeader'
} else if (prefix === 'topBar') {
rule.component = 'TopBar'
} else if (prefix === 'chatMessage') {
rule.component = 'ChatMessage'
} else if (prefix === 'poll') {
rule.component = 'PollGraph'
} else if (prefix === 'btn') {
rule.component = 'Button'
} else {
rule.component = prefix[0].toUpperCase() + prefix.slice(1).toLowerCase()
}
return keys.map((key) => {
if (!data.colors[key]) return null
const leftoverKey = key.replace(prefix, '')
const parts = (leftoverKey || 'Bg').match(/[A-Z][a-z]*/g)
const last = parts.slice(-1)[0]
let newRule = { source: '2to3', directives: {} }
let variantArray = []
switch (last) {
case 'Text':
case 'Faint': // typo
case 'Link':
case 'Icon':
case 'Greentext':
case 'Cyantext':
case 'Border':
newRule.parent = rule
newRule.directives.textColor = data.colors[key]
- newRule.directives.textAuto = 'no-auto'
variantArray = parts.slice(0, -1)
break
default:
newRule = { ...rule, directives: {} }
newRule.directives.background = data.colors[key]
variantArray = parts
break
}
if (last === 'Text' || last === 'Link') {
const secondLast = parts.slice(-2)[0]
if (secondLast === 'Light') {
return null // unsupported
} else if (secondLast === 'Faint') {
newRule.state = ['faint']
variantArray = parts.slice(0, -2)
}
}
switch (last) {
case 'Text':
case 'Link':
case 'Icon':
case 'Border':
newRule.component = last
break
case 'Greentext':
case 'Cyantext':
newRule.component = 'FunText'
newRule.variant = last.toLowerCase()
break
case 'Faint':
newRule.component = 'Text'
newRule.state = ['faint']
break
}
variantArray = variantArray.filter(x => x !== 'Bg')
if (last === 'Link' && prefix === 'selectedPost') {
// selectedPost has typo - duplicate 'Post'
variantArray = variantArray.filter(x => x !== 'Post')
}
if (prefix === 'popover' && variantArray[0] === 'Post') {
newRule.component = 'Post'
newRule.parent = { source: '2to3hack', component: 'Popover' }
variantArray = variantArray.filter(x => x !== 'Post')
}
if (prefix === 'selectedMenu' && variantArray[0] === 'Popover') {
newRule.parent = { source: '2to3hack', component: 'Popover' }
variantArray = variantArray.filter(x => x !== 'Popover')
}
switch (prefix) {
case 'btn':
case 'input':
case 'alert': {
const hasPanel = variantArray.find(x => x === 'Panel')
if (hasPanel) {
newRule.parent = { source: '2to3hack', component: 'PanelHeader', parent: newRule.parent }
variantArray = variantArray.filter(x => x !== 'Panel')
}
const hasTop = variantArray.find(x => x === 'Top') // TopBar
if (hasTop) {
newRule.parent = { source: '2to3hack', component: 'TopBar', parent: newRule.parent }
variantArray = variantArray.filter(x => x !== 'Top' && x !== 'Bar')
}
break
}
}
if (variantArray.length > 0) {
if (prefix === 'btn') {
newRule.state = variantArray.map(x => x.toLowerCase())
} else {
newRule.variant = variantArray[0].toLowerCase()
}
}
if (newRule.component === 'Panel') {
return [newRule, { ...newRule, component: 'MobileDrawer' }]
} else if (newRule.component === 'Button') {
const rules = [
newRule,
{ ...newRule, component: 'Tab' },
{ ...newRule, component: 'ScrollbarElement' }
]
if (newRule.state?.indexOf('toggled') >= 0) {
rules.push({ ...newRule, state: [...newRule.state, 'focused'] })
rules.push({ ...newRule, state: [...newRule.state, 'hover'] })
rules.push({ ...newRule, state: [...newRule.state, 'hover', 'focused'] })
}
if (newRule.state?.indexOf('hover') >= 0) {
rules.push({ ...newRule, state: [...newRule.state, 'focused'] })
}
return rules
} else if (newRule.component === 'Badge') {
if (newRule.variant === 'notification') {
return [newRule, { component: 'Root', directives: { '--badgeNotification': 'color | ' + newRule.directives.background } }]
} else if (newRule.variant === 'neutral') {
return [{ ...newRule, variant: 'normal' }]
} else {
return [newRule]
}
} else if (newRule.component === 'TopBar') {
return [newRule, { ...newRule, parent: { component: 'MobileDrawer' }, component: 'PanelHeader' }]
} else {
return [newRule]
}
})
})
const flatExtRules = extendedRules.filter(x => x).reduce((acc, x) => [...acc, ...x], []).filter(x => x).reduce((acc, x) => [...acc, ...x], [])
return [generateRoot(), ...convertShadows(), ...convertRadii(), ...convertOpacity(), ...convertFonts(), ...flatExtRules]
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 19, 6:05 AM (1 d, 8 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1768979
Default Alt Text
(90 KB)

Event Timeline