diff --git a/.node-version b/.node-version index 9be0c7058..08b7109d0 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -18.20.7 +18.20.8 diff --git a/.stylelintrc.json b/.stylelintrc.json index d6689cc01..c91107595 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,6 +1,5 @@ { "extends": [ - "stylelint-rscss/config", "stylelint-config-standard", "stylelint-config-recommended-scss", "stylelint-config-html", @@ -8,15 +7,6 @@ ], "rules": { "declaration-no-important": true, - "rscss/no-descendant-combinator": false, - "rscss/class-format": [ - false, - { - "component": "pascal-case", - "variant": "^-[a-z]\\w+", - "element": "^[a-z]\\w+" - } - ], "selector-class-pattern": null, "import-notation": null, "custom-property-pattern": null, diff --git a/CHANGELOG.md b/CHANGELOG.md index 9844319e3..c2f0e7d17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,74 @@ 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.8.0 +### Changed +- BREAKING: static/img/nsfw.2958239.png is now static/img/nsfw.DepQPhG0.png, which may affect people who specify exactly this path as the cover image +- BREAKING: static/emoji.json is replaced with a properly hashed path under static/js in the production build, meaning server admins cannot provide their own set of unicode emojis by overriding this file (custom (image-based) emojis not affected) +- Speed up initial boot. +- Updated our build system to support browsers: + Safari >= 15 + Firefox >= 115 + Android > 4 + no Opera Mini support + no IE support + no "dead" (unmaintained) browsers support + +This does not guarantee that browsers will or will not work. + +- Use /api/v1/accounts/:id/follow for account subscriptions instead of the deprecated routes +- Modal layout for mobile has new layout to make it easy to use +- Better display of mute reason on posts +- Simplify the OAuth client_name to 'PleromaFE' +- Partially migrated from vuex to pinia +- Authenticate and subscribe to streaming after connection +- Tabs now have indentation for better visibility of which tab is currently active +- Upgraded Vue to version 3.5 + +### Added +- Support bookmark folders +- Some new default color schemes +- Added support for fetching /{resource}.custom.ext to allow adding instance-specific themes without altering sourcetree +- Post actions customization +- Support displaying time in absolute format +- Add draft management system +- Compress most kinds of images on upload. +- Added option to always convert images to JPEG format instead of using WebP when compressing images. +- Added configurable image compression option in general settings, allowing users to control whether images are compressed before upload. +- Inform users that Smithereen public polls are public +- Splash screen + loading indicator to make process of identifying initialization issues and load performance +- UI for making v3 themes and palettes, support for bundling v3 themes +- Make UserLink wrappable + +### Fixed +- Fixed occasional overflows in emoji picker and made header scrollable +- Updated shadow editor, hopefully fixed long-standing bugs, added ability to specify shadow's name. +- Checkbox vertical alignment +- Check for canvas extract permission when initializing favicon service +- Fix some of the color manipulation functions +- Fix draft saving when auto-save is off +- Switch from class hack to normalButton attribute for emoji count popover +- Fix emoji inconsistencies in notifications, +- Fix some emoji not scaling with interface +- Make sure hover style is also applied to :focus-visible +- Improved ToS and registration +- Fix small markup inconsistencies +- Fixed modals buttons overflow +- Fix whitespaces for multiple status mute reasons, display bot status reason +- Create an OAuth app only when needed +- Fix CSS compatibility issues in style_setter.js for older browsers like Palemoon +- Proper sticky header for conversations on user page +- Add text label for more actions button in post status form +- Reply-or-quote buttons now take less space +- Allow repeats of own posts with private scopes +- Bookmarks visible again on mobile +- Remove focusability on hidden popover in subject input +- Show only month and day instead of weird "day, hour" format. + +### Removed +- BREAKING: drop support for browsers that do not support ` diff --git a/src/components/mfa_form/totp_form.js b/src/components/mfa_form/totp_form.js index 6ee823ed3..857d055ff 100644 --- a/src/components/mfa_form/totp_form.js +++ b/src/components/mfa_form/totp_form.js @@ -1,5 +1,7 @@ import mfaApi from '../../services/new_api/mfa.js' import { mapState, mapGetters, mapActions, mapMutations } from 'vuex' +import { mapStores } from 'pinia' +import { useOAuthStore } from 'src/stores/oauth.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faTimes @@ -18,17 +20,24 @@ export default { ...mapGetters({ authSettings: 'authFlow/settings' }), + ...mapStores(useOAuthStore), ...mapState({ instance: 'instance', - oauth: 'oauth' }) }, methods: { ...mapMutations('authFlow', ['requireRecovery', 'abortMFA']), ...mapActions({ login: 'authFlow/login' }), clearError () { this.error = false }, + + focusOnCodeInput () { + const codeInput = this.$refs.codeInput + codeInput.focus() + codeInput.setSelectionRange(0, codeInput.value.length) + }, + submit () { - const { clientId, clientSecret } = this.oauth + const { clientId, clientSecret } = this.oauthStore const data = { clientId, @@ -42,6 +51,7 @@ export default { if (result.error) { this.error = result.error this.code = null + this.focusOnCodeInput() return } diff --git a/src/components/mfa_form/totp_form.vue b/src/components/mfa_form/totp_form.vue index 26fc1e804..b057f0413 100644 --- a/src/components/mfa_form/totp_form.vue +++ b/src/components/mfa_form/totp_form.vue @@ -1,5 +1,5 @@ + diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue index d5539ad89..eb950cba0 100644 --- a/src/components/mobile_nav/mobile_nav.vue +++ b/src/components/mobile_nav/mobile_nav.vue @@ -220,8 +220,7 @@ margin-top: 3.5em; width: 100vw; height: calc(100vh - var(--navbar-height)); - overflow-x: hidden; - overflow-y: scroll; + overflow: hidden scroll; .notifications { padding: 0; diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.vue b/src/components/mobile_post_status_button/mobile_post_status_button.vue index ba77cb2d6..32079c298 100644 --- a/src/components/mobile_post_status_button/mobile_post_status_button.vue +++ b/src/components/mobile_post_status_button/mobile_post_status_button.vue @@ -42,7 +42,7 @@ } } -@media all and (min-width: 801px) { +@media all and (width >= 801px) { .new-status-button:not(.always-show) { display: none; } diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue index 71a5dbb61..032e7dbeb 100644 --- a/src/components/modal/modal.vue +++ b/src/components/modal/modal.vue @@ -41,10 +41,7 @@ export default { .modal-view { z-index: var(--ZI_modals); position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; + inset: 0; display: flex; justify-content: center; align-items: center; diff --git a/src/components/notification/notification.scss b/src/components/notification/notification.scss index d46b25bed..a0338856f 100644 --- a/src/components/notification/notification.scss +++ b/src/components/notification/notification.scss @@ -3,7 +3,7 @@ border-bottom: 1px solid; border-color: var(--border); word-wrap: break-word; - word-break: break-word; + word-break: break-all; &.Status { /* stylelint-disable-next-line declaration-no-important */ diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index cfc1f3d67..e52c25e40 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -13,10 +13,7 @@ .notification-overlay { position: absolute; - top: 0; - right: 0; - left: 0; - bottom: 0; + inset: 0; pointer-events: none; } diff --git a/src/components/poll/poll.scss b/src/components/poll/poll.scss index d56358420..d043eefa6 100644 --- a/src/components/poll/poll.scss +++ b/src/components/poll/poll.scss @@ -26,7 +26,7 @@ align-items: center; padding: 0.1em 0.25em; z-index: 1; - word-break: break-word; + word-break: break-all; } .result-percentage { diff --git a/src/components/popover/popover.scss b/src/components/popover/popover.scss index de1eda866..828b81cd1 100644 --- a/src/components/popover/popover.scss +++ b/src/components/popover/popover.scss @@ -15,11 +15,7 @@ &::after { content: ""; position: absolute; - top: -1px; - bottom: -1px; - left: -1px; - right: -1px; - z-index: -1px; + inset: -1px; box-shadow: var(--_shadow); pointer-events: none; } diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue index d971ccb5e..f6b056e6b 100644 --- a/src/components/registration/registration.vue +++ b/src/components/registration/registration.vue @@ -418,7 +418,7 @@ margin: 0.6em; } -@media all and (max-width: 800px) { +@media all and (width <= 800px) { .registration-form .container { flex-direction: column-reverse; diff --git a/src/components/search/search.vue b/src/components/search/search.vue index 645a6fce8..4302f93f9 100644 --- a/src/components/search/search.vue +++ b/src/components/search/search.vue @@ -157,7 +157,7 @@ text-align: center; } -@media all and (max-width: 800px) { +@media all and (width <= 800px) { .search-nav-heading { .tab-switcher .tabs .tab-wrapper { display: block; diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.scss b/src/components/settings_modal/admin_tabs/emoji_tab.scss index 3e77e0195..eefada63a 100644 --- a/src/components/settings_modal/admin_tabs/emoji_tab.scss +++ b/src/components/settings_modal/admin_tabs/emoji_tab.scss @@ -29,7 +29,7 @@ .emoji-list { display: flex; flex-wrap: wrap; - gap: 1em 1em; + gap: 1em; } } diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.scss b/src/components/settings_modal/admin_tabs/frontends_tab.scss index 420d20b35..3bc347087 100644 --- a/src/components/settings_modal/admin_tabs/frontends_tab.scss +++ b/src/components/settings_modal/admin_tabs/frontends_tab.scss @@ -13,15 +13,11 @@ // fix buttons showing through z-index: 2; opacity: 0.9; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; } dd { text-overflow: ellipsis; - word-wrap: nowrap; white-space: nowrap; overflow-x: hidden; max-width: 10em; diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss index bd0ed4524..2c975917f 100644 --- a/src/components/settings_modal/settings_modal.scss +++ b/src/components/settings_modal/settings_modal.scss @@ -46,7 +46,7 @@ max-width: 90vw; height: 90vh; - @media all and (max-width: 800px) { + @media all and (width <= 800px) { max-width: 100vw; height: 100%; } @@ -84,7 +84,7 @@ > li { margin: 1em 0; line-height: 1.5em; - vertical-align: center; + vertical-align: middle; } &.two-column { @@ -105,7 +105,7 @@ */ transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px)); - @media all and (max-width: 800px) { + @media all and (width <= 800px) { /* For mobile, the modal takes 100% of the available screen. This ensures the minimized modal is always 50px above the browser bottom bar regardless of whether or not it is visible. diff --git a/src/components/settings_modal/tabs/appearance_tab.scss b/src/components/settings_modal/tabs/appearance_tab.scss index eff5438a7..ae8691f16 100644 --- a/src/components/settings_modal/tabs/appearance_tab.scss +++ b/src/components/settings_modal/tabs/appearance_tab.scss @@ -26,8 +26,7 @@ .palettes-container { height: 15em; - overflow-y: auto; - overflow-x: hidden; + overflow: hidden auto; scrollbar-gutter: stable; border-radius: var(--roundness); border: 1px solid var(--border); @@ -112,8 +111,7 @@ flex-wrap: wrap; margin: -0.5em 0; height: 25em; - overflow-x: hidden; - overflow-y: auto; + overflow: hidden auto; scrollbar-gutter: stable; border-radius: var(--roundness); border: 1px solid var(--border); diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index 6b3ab2615..a549a8705 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -216,7 +216,7 @@ const ProfileTab = { this.submitBackground('') } }, - submitAvatar (cropper, file) { + submitAvatar (canvas, file) { const that = this return new Promise((resolve, reject) => { function updateAvatar (avatar, avatarName) { @@ -232,8 +232,8 @@ const ProfileTab = { }) } - if (cropper) { - cropper.getCroppedCanvas().toBlob((data) => updateAvatar(data, file.name), file.type) + if (canvas) { + canvas.toBlob((data) => updateAvatar(data, file.name), file.type) } else { updateAvatar(file, file.name) } diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.scss b/src/components/settings_modal/tabs/style_tab/style_tab.scss index 6a86a3e08..545f6b117 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.scss +++ b/src/components/settings_modal/tabs/style_tab/style_tab.scss @@ -187,7 +187,7 @@ .state-selector, .variant-selector { display: grid; - grid-template-columns: 1fr minmax(1fr, 10em); + grid-template-columns: 1fr minmax(10em, 1fr); grid-template-rows: auto; grid-auto-flow: column; grid-gap: 0.5em; diff --git a/src/components/settings_modal/tabs/theme_tab/theme_preview.vue b/src/components/settings_modal/tabs/theme_tab/theme_preview.vue index 4446c18b9..e8ac0d8a3 100644 --- a/src/components/settings_modal/tabs/theme_tab/theme_preview.vue +++ b/src/components/settings_modal/tabs/theme_tab/theme_preview.vue @@ -241,10 +241,7 @@ export default { .underlay-preview { position: absolute; - top: 0; - bottom: 0; - left: 10px; - right: 10px; + inset: 0 10px; } } diff --git a/src/components/shadow_control/shadow_control.scss b/src/components/shadow_control/shadow_control.scss index 7e998cdf7..d169ea234 100644 --- a/src/components/shadow_control/shadow_control.scss +++ b/src/components/shadow_control/shadow_control.scss @@ -112,8 +112,7 @@ grid-area: preview; min-width: 25em; margin-left: 0.125em; - align-self: start; - justify-self: center; + place-self: start center; } } diff --git a/src/components/shout_panel/shout_panel.vue b/src/components/shout_panel/shout_panel.vue index 67927df88..1b2b591c2 100644 --- a/src/components/shout_panel/shout_panel.vue +++ b/src/components/shout_panel/shout_panel.vue @@ -107,8 +107,7 @@ } .shout-window { - overflow-y: auto; - overflow-x: hidden; + overflow: hidden auto; max-height: 20em; } diff --git a/src/components/status/status.scss b/src/components/status/status.scss index ffa4f532c..b6a7256a7 100644 --- a/src/components/status/status.scss +++ b/src/components/status/status.scss @@ -2,7 +2,7 @@ min-width: 0; white-space: normal; word-wrap: break-word; - word-break: break-word; + word-break: break-all; &:hover { --_still-image-img-visibility: visible; @@ -364,7 +364,7 @@ } } - @media all and (max-width: 800px) { + @media all and (width <= 800px) { .repeater-avatar { margin-left: 20px; } @@ -374,7 +374,6 @@ height: 40px; // TODO define those other way somehow? - // stylelint-disable rscss/class-format &.-compact { width: 32px; height: 32px; diff --git a/src/components/status_action_buttons/action_button.js b/src/components/status_action_buttons/action_button.js index 07affaafe..8a65b9184 100644 --- a/src/components/status_action_buttons/action_button.js +++ b/src/components/status_action_buttons/action_button.js @@ -124,6 +124,7 @@ export default { } }, doActionWrap (button, close = () => {}) { + if (this.button.interactive ? !this.button.interactive(this.funcArg) : false) return this.$emit('interacted') if (button.name === 'emoji') { this.$refs.picker.showPicker() diff --git a/src/components/status_action_buttons/action_button.vue b/src/components/status_action_buttons/action_button.vue index 9e25764c0..ad2d349c6 100644 --- a/src/components/status_action_buttons/action_button.vue +++ b/src/components/status_action_buttons/action_button.vue @@ -12,7 +12,7 @@ :title="$t(button.label(funcArg))" target="_blank" :tabindex="0" - :disabled="buttonClass.disabled" + :disabled="button.interactive ? !button.interactive(funcArg) : false" :href="getComponent(button) == 'a' ? button.link?.(funcArg) || remoteInteractionLink : undefined" @click="doActionWrap(button, outerClose)" > @@ -24,7 +24,7 @@ :style="{ '--fa-animation-duration': '750ms' }" fixed-width /> -