Merge remote-tracking branch 'origin/develop' into sss-objects

This commit is contained in:
Henry Jameson 2025-04-08 22:36:05 +03:00
commit 4d5333102e
28 changed files with 788 additions and 983 deletions

View file

@ -0,0 +1 @@
Add support for detachable scrollTop button

View file

View file

@ -72,19 +72,20 @@
"babel-plugin-lodash": "3.3.4", "babel-plugin-lodash": "3.3.4",
"chai": "5.2.0", "chai": "5.2.0",
"chalk": "5.4.1", "chalk": "5.4.1",
"chromedriver": "134.0.5", "chromedriver": "135.0.0",
"connect-history-api-fallback": "2.0.0", "connect-history-api-fallback": "2.0.0",
"cross-spawn": "7.0.6", "cross-spawn": "7.0.6",
"custom-event-polyfill": "1.0.7", "custom-event-polyfill": "1.0.7",
"eslint": "9.23.0", "eslint": "9.24.0",
"vue-eslint-parser": "10.1.3",
"eslint-config-standard": "17.1.0", "eslint-config-standard": "17.1.0",
"eslint-formatter-friendly": "7.0.0", "eslint-formatter-friendly": "7.0.0",
"eslint-plugin-import": "2.31.0", "eslint-plugin-import": "2.31.0",
"eslint-plugin-n": "17.17.0", "eslint-plugin-n": "17.17.0",
"eslint-plugin-promise": "7.2.1", "eslint-plugin-promise": "7.2.1",
"eslint-plugin-vue": "9.33.0", "eslint-plugin-vue": "10.0.0",
"eventsource-polyfill": "0.9.6", "eventsource-polyfill": "0.9.6",
"express": "4.21.2", "express": "5.1.0",
"function-bind": "1.1.2", "function-bind": "1.1.2",
"http-proxy-middleware": "3.0.3", "http-proxy-middleware": "3.0.3",
"iso-639-1": "3.1.5", "iso-639-1": "3.1.5",
@ -95,19 +96,19 @@
"postcss": "8.5.3", "postcss": "8.5.3",
"postcss-html": "^1.5.0", "postcss-html": "^1.5.0",
"postcss-scss": "^4.0.6", "postcss-scss": "^4.0.6",
"sass": "1.86.1", "sass": "1.86.3",
"selenium-server": "3.141.59", "selenium-server": "3.141.59",
"semver": "7.7.1", "semver": "7.7.1",
"serve-static": "2.2.0", "serve-static": "2.2.0",
"shelljs": "0.9.2", "shelljs": "0.9.2",
"sinon": "20.0.0", "sinon": "20.0.0",
"sinon-chai": "4.0.0", "sinon-chai": "4.0.0",
"stylelint": "16.17.0", "stylelint": "16.18.0",
"stylelint-config-html": "^1.1.0", "stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^14.0.0", "stylelint-config-recommended": "^16.0.0",
"stylelint-config-recommended-scss": "^14.0.0", "stylelint-config-recommended-scss": "^14.0.0",
"stylelint-config-recommended-vue": "^1.6.0", "stylelint-config-recommended-vue": "^1.6.0",
"stylelint-config-standard": "37.0.0", "stylelint-config-standard": "38.0.0",
"vite": "^6.1.0", "vite": "^6.1.0",
"vite-plugin-eslint2": "^5.0.3", "vite-plugin-eslint2": "^5.0.3",
"vite-plugin-stylelint": "^6.0.0", "vite-plugin-stylelint": "^6.0.0",

View file

@ -20,6 +20,8 @@ import { defineAsyncComponent } from 'vue'
import { useShoutStore } from './stores/shout' import { useShoutStore } from './stores/shout'
import { useInterfaceStore } from './stores/interface' import { useInterfaceStore } from './stores/interface'
import { throttle } from 'lodash'
export default { export default {
name: 'app', name: 'app',
components: { components: {
@ -58,16 +60,23 @@ export default {
// Load the locale from the storage // Load the locale from the storage
const val = this.$store.getters.mergedConfig.interfaceLanguage const val = this.$store.getters.mergedConfig.interfaceLanguage
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
window.addEventListener('resize', this.updateMobileState)
document.getElementById('modal').classList = ['-' + this.layoutType] document.getElementById('modal').classList = ['-' + this.layoutType]
// Create bound handlers
this.updateScrollState = throttle(this.scrollHandler, 200)
this.updateMobileState = throttle(this.resizeHandler, 200)
}, },
mounted () { mounted () {
window.addEventListener('resize', this.updateMobileState)
this.scrollParent.addEventListener('scroll', this.updateScrollState)
if (useInterfaceStore().themeApplied) { if (useInterfaceStore().themeApplied) {
this.removeSplash() this.removeSplash()
} }
}, },
unmounted () { unmounted () {
window.removeEventListener('resize', this.updateMobileState) window.removeEventListener('resize', this.updateMobileState)
this.scrollParent.removeEventListener('scroll', this.updateScrollState)
}, },
computed: { computed: {
themeApplied () { themeApplied () {
@ -146,13 +155,23 @@ export default {
}, },
noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders }, noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
showScrollbars () { return this.$store.getters.mergedConfig.showScrollbars }, showScrollbars () { return this.$store.getters.mergedConfig.showScrollbars },
scrollParent () { return window; /* this.$refs.appContentRef */ },
...mapGetters(['mergedConfig']) ...mapGetters(['mergedConfig'])
}, },
methods: { methods: {
updateMobileState () { resizeHandler () {
useInterfaceStore().setLayoutWidth(windowWidth()) useInterfaceStore().setLayoutWidth(windowWidth())
useInterfaceStore().setLayoutHeight(windowHeight()) useInterfaceStore().setLayoutHeight(windowHeight())
}, },
scrollHandler () {
const scrollPosition = this.scrollParent === window ? window.scrollY : this.scrollParent.scrollTop
if (scrollPosition != 0) {
this.$refs.appContentRef.classList.add(['-scrolled'])
} else {
this.$refs.appContentRef.classList.remove(['-scrolled'])
}
},
removeSplash () { removeSplash () {
document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4)) document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4))
const splashscreenRoot = document.querySelector('#splash') const splashscreenRoot = document.querySelector('#splash')

View file

@ -16,6 +16,7 @@
<Notifications v-if="currentUser" /> <Notifications v-if="currentUser" />
<div <div
id="content" id="content"
ref="appContentRef"
class="app-layout container" class="app-layout container"
:class="classes" :class="classes"
> >

View file

@ -177,7 +177,8 @@
.text { .text {
flex: 2; flex: 2;
margin: 8px; margin: 8px;
word-break: break-all; overflow-wrap: break-word;
text-wrap: pretty;
h1 { h1 {
font-size: 1rem; font-size: 1rem;

View file

@ -17,7 +17,6 @@
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
word-wrap: break-word;
} }
.heading { .heading {

View file

@ -39,7 +39,6 @@
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
display: inline; display: inline;
word-wrap: break-word;
overflow: hidden; overflow: hidden;
} }

View file

@ -199,7 +199,7 @@ export default {
inset: 0; inset: 0;
display: grid; display: grid;
place-items: center center; place-items: center center;
background-color: rgba(100 0 0 / 50%); background-color: rgb(100 0 0 / 50%);
.alert { .alert {
padding: 0.5em 1em; padding: 0.5em 1em;
@ -261,14 +261,14 @@ export default {
.preview-window { .preview-window {
--__grid-color1: rgb(102 102 102); --__grid-color1: rgb(102 102 102);
--__grid-color2: rgb(153 153 153); --__grid-color2: rgb(153 153 153);
--__grid-color1-disabled: rgba(102 102 102 / 20%); --__grid-color1-disabled: rgb(102 102 102 / 20%);
--__grid-color2-disabled: rgba(153 153 153 / 20%); --__grid-color2-disabled: rgb(153 153 153 / 20%);
&.-light-grid { &.-light-grid {
--__grid-color1: rgb(205 205 205); --__grid-color1: rgb(205 205 205);
--__grid-color2: rgb(255 255 255); --__grid-color2: rgb(255 255 255);
--__grid-color1-disabled: rgba(205 205 205 / 20%); --__grid-color1-disabled: rgb(205 205 205 / 20%);
--__grid-color2-disabled: rgba(255 255 255 / 20%); --__grid-color2-disabled: rgb(255 255 255 / 20%);
} }
position: relative; position: relative;

View file

@ -127,7 +127,6 @@
max-width: 100%; max-width: 100%;
p { p {
word-wrap: break-word;
white-space: normal; white-space: normal;
overflow-x: hidden; overflow-x: hidden;
} }

View file

@ -72,7 +72,6 @@
flex: 1 1 0; flex: 1 1 0;
line-height: 1.2; line-height: 1.2;
white-space: normal; white-space: normal;
word-wrap: normal;
} }
.hidden { .hidden {

View file

@ -2,21 +2,24 @@
<div class="image-cropper"> <div class="image-cropper">
<div v-if="dataUrl"> <div v-if="dataUrl">
<cropper-canvas <cropper-canvas
ref="cropperCanvas"
background background
class="image-cropper-canvas" class="image-cropper-canvas"
ref="cropperCanvas"
height="25em" height="25em"
> >
<cropper-image <cropper-image
ref="cropperImage"
:src="dataUrl" :src="dataUrl"
alt="Picture" alt="Picture"
ref="cropperImage"
class="image-cropper-image" class="image-cropper-image"
translatable translatable
scalable scalable
/> />
<cropper-shade hidden /> <cropper-shade hidden />
<cropper-handle action="select" plain /> <cropper-handle
action="select"
plain
/>
<cropper-selection <cropper-selection
ref="cropperSelection" ref="cropperSelection"
initial-coverage="1" initial-coverage="1"
@ -25,17 +28,23 @@
resizable resizable
@change="onCropperSelectionChange" @change="onCropperSelectionChange"
> >
<cropper-grid role="grid" covered></cropper-grid> <cropper-grid
<cropper-crosshair centered></cropper-crosshair> role="grid"
<cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle> covered
<cropper-handle action="n-resize"></cropper-handle> />
<cropper-handle action="e-resize"></cropper-handle> <cropper-crosshair centered />
<cropper-handle action="s-resize"></cropper-handle> <cropper-handle
<cropper-handle action="w-resize"></cropper-handle> action="move"
<cropper-handle action="ne-resize"></cropper-handle> theme-color="rgba(255, 255, 255, 0.35)"
<cropper-handle action="nw-resize"></cropper-handle> />
<cropper-handle action="se-resize"></cropper-handle> <cropper-handle action="n-resize" />
<cropper-handle action="sw-resize"></cropper-handle> <cropper-handle action="e-resize" />
<cropper-handle action="s-resize" />
<cropper-handle action="w-resize" />
<cropper-handle action="ne-resize" />
<cropper-handle action="nw-resize" />
<cropper-handle action="se-resize" />
<cropper-handle action="sw-resize" />
</cropper-selection> </cropper-selection>
</cropper-canvas> </cropper-canvas>
<div class="image-cropper-buttons-wrapper"> <div class="image-cropper-buttons-wrapper">

View file

@ -68,7 +68,8 @@
margin: 0.5em 0 0; margin: 0.5em 0 0;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
word-break: break-all; overflow-wrap: break-word;
text-wrap: pretty;
line-height: 1.2em; line-height: 1.2em;
/* cap description at 3 lines, the 1px is to clean up some stray pixels /* cap description at 3 lines, the 1px is to clean up some stray pixels

View file

@ -93,4 +93,4 @@
<script src="./login_form.js"></script> <script src="./login_form.js"></script>
<style src="./login_form.scss"/> <style src="./login_form.scss" />

View file

@ -170,7 +170,8 @@ $modal-view-button-icon-margin: 0.5em;
min-height: 1em; min-height: 1em;
max-width: 500px; max-width: 500px;
max-height: 9.5em; max-height: 9.5em;
word-break: break-all; overflow-wrap: break-word;
text-wrap: pretty;
} }
.modal-image { .modal-image {

View file

@ -27,7 +27,6 @@
top: 100%; top: 100%;
left: 0; left: 0;
height: 100%; height: 100%;
word-wrap: normal;
white-space: nowrap; white-space: nowrap;
transition: opacity 0.2s ease; transition: opacity 0.2s ease;
z-index: 1; z-index: 1;

View file

@ -1,5 +1,6 @@
.MentionsLine { .MentionsLine {
word-break: break-all; overflow-wrap: break-word;
text-wrap: pretty;
.mention-link:not(:first-child)::before { .mention-link:not(:first-child)::before {
content: " "; content: " ";

View file

@ -2,8 +2,8 @@
.Notification { .Notification {
border-bottom: 1px solid; border-bottom: 1px solid;
border-color: var(--border); border-color: var(--border);
word-wrap: break-word; overflow-wrap: break-word;
word-break: break-all; text-wrap: pretty;
&.Status { &.Status {
/* stylelint-disable-next-line declaration-no-important */ /* stylelint-disable-next-line declaration-no-important */
@ -31,8 +31,6 @@
& .status-username, & .status-username,
& .mute-thread, & .mute-thread,
& .mute-words { & .mute-words {
word-wrap: normal;
word-break: normal;
white-space: nowrap; white-space: nowrap;
} }

View file

@ -128,7 +128,6 @@
.notification-details { .notification-details {
min-width: 0; min-width: 0;
word-wrap: break-word;
line-height: var(--post-line-height); line-height: var(--post-line-height);
position: relative; position: relative;
overflow: hidden; overflow: hidden;
@ -161,7 +160,8 @@
} }
h1 { h1 {
word-break: break-all; overflow-wrap: break-word;
text-wrap: pretty;
margin: 0 0 0.3em; margin: 0 0 0.3em;
padding: 0; padding: 0;
font-size: 1em; font-size: 1em;

View file

@ -26,7 +26,8 @@
align-items: center; align-items: center;
padding: 0.1em 0.25em; padding: 0.1em 0.25em;
z-index: 1; z-index: 1;
word-break: break-all; overflow-wrap: break-word;
text-wrap: pretty;
} }
.result-percentage { .result-percentage {

View file

@ -205,6 +205,7 @@
min-height: calc(var(--post-line-height) * 1em); min-height: calc(var(--post-line-height) * 1em);
resize: none; resize: none;
background: transparent; background: transparent;
text-wrap: stable;
&.scrollable-form { &.scrollable-form {
overflow-y: auto; overflow-y: auto;

View file

@ -0,0 +1,18 @@
const ScrollTopButton = {
props: {
fast: {
type: Boolean,
required: false,
default: false
}
},
methods: {
scrollToTop() {
const speed = this.fast ? 'instant' : 'smooth';
window.scrollTo({ top: 0, behavior: speed })
}
}
}
export default ScrollTopButton

View file

@ -0,0 +1,29 @@
<template>
<div class="rightside-button scroll-to-top">
<button
class="button-unstyled scroll-to-top-button"
type="button"
:title="$t('general.scroll_to_top')"
@click="scrollToTop"
>
<FALayers class="fa-scale-110 fa-old-padding-layer">
<FAIcon icon="arrow-up" />
<FAIcon
icon="minus"
transform="up-7"
/>
</FALayers>
</button>
</div>
</template>
<script src="./scroll_top_button.js"></script>
<style lang="scss">
.scroll-to-top {
display: none;
}
.-scrolled .scroll-to-top {
display: inline-block;
}
</style>

View file

@ -1,8 +1,8 @@
.Status { .Status {
min-width: 0; min-width: 0;
white-space: normal; white-space: normal;
word-wrap: break-word; overflow-wrap: break-word;
word-break: break-all; text-wrap: pretty;
&:hover { &:hover {
--_still-image-img-visibility: visible; --_still-image-img-visibility: visible;
@ -92,7 +92,10 @@
a { a {
display: inline-block; display: inline-block;
word-break: break-all; white-space: nowrap;
text-overflow: ellipsis;
overflow-x: hidden;
width: 100%
} }
} }
@ -283,8 +286,6 @@
& .status-username, & .status-username,
& .mute-reason { & .mute-reason {
word-wrap: normal;
word-break: normal;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;

View file

@ -14,8 +14,7 @@
& .summary { & .summary {
white-space: pre-wrap; white-space: pre-wrap;
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; text-wrap: pretty;
word-break: break-all;
line-height: var(--post-line-height); line-height: var(--post-line-height);
} }
@ -54,19 +53,22 @@
} }
.text-wrapper { .text-wrapper {
text-overflow: ellipsis;
overflow-wrap: break-word;
overflow: hidden;
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
&.-tall-status { &.-tall-status {
position: relative; position: relative;
height: 220px; height: 16em;
overflow: hidden; overflow: hidden;
z-index: 1; z-index: 1;
.media-body { .media-body {
min-height: 0; min-height: 0;
mask: mask:
linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat, linear-gradient(to top, white, transparent) bottom/100% 8em no-repeat,
linear-gradient(to top, white, white); linear-gradient(to top, white, white);
/* Autoprefixed seem to ignore this one, and also syntax is different */ /* Autoprefixed seem to ignore this one, and also syntax is different */
@ -87,16 +89,17 @@
& .status-unhider, & .status-unhider,
& .cw-status-hider { & .cw-status-hider {
display: inline-block; display: inline-block;
word-break: break-all; overflow-wrap: break-word;
text-wrap: pretty;
width: 100%; width: 100%;
text-align: center; text-align: center;
} }
.tall-status-hider { .tall-status-hider {
position: absolute; position: absolute;
height: 70px; height: 5em;
margin-top: 150px; margin-top: 10em;
line-height: 110px; line-height: 8em;
z-index: 2; z-index: 2;
} }
@ -107,7 +110,8 @@
& .status-unhider, & .status-unhider,
& .cw-status-hider { & .cw-status-hider {
word-break: break-all; overflow-wrap: break-word;
text-wrap: pretty;
svg { svg {
color: inherit; color: inherit;

View file

@ -5,6 +5,7 @@ import Conversation from '../conversation/conversation.vue'
import TimelineMenu from '../timeline_menu/timeline_menu.vue' import TimelineMenu from '../timeline_menu/timeline_menu.vue'
import QuickFilterSettings from '../quick_filter_settings/quick_filter_settings.vue' import QuickFilterSettings from '../quick_filter_settings/quick_filter_settings.vue'
import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue' import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue'
import ScrollTopButton from '../scroll_top_button/scroll_top_button.vue'
import { debounce, throttle, keyBy } from 'lodash' import { debounce, throttle, keyBy } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch, faCirclePlus, faCog, faMinus, faArrowUp, faCheck } from '@fortawesome/free-solid-svg-icons' import { faCircleNotch, faCirclePlus, faCog, faMinus, faArrowUp, faCheck } from '@fortawesome/free-solid-svg-icons'
@ -47,6 +48,7 @@ const Timeline = {
}, },
components: { components: {
Status, Status,
ScrollTopButton,
Conversation, Conversation,
TimelineMenu, TimelineMenu,
QuickFilterSettings, QuickFilterSettings,
@ -144,9 +146,6 @@ const Timeline = {
this.$store.commit('setLoading', { timeline: this.timelineName, value: false }) this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
}, },
methods: { methods: {
scrollToTop () {
window.scrollTo({ top: this.$el.offsetTop })
},
stopBlockingClicks: debounce(function () { stopBlockingClicks: debounce(function () {
this.blockingClicks = false this.blockingClicks = false
}, 1000), }, 1000),
@ -251,7 +250,6 @@ const Timeline = {
} }
}, },
handleScroll: throttle(function (e) { handleScroll: throttle(function (e) {
this.showScrollTop = this.$el.offsetTop < window.scrollY
this.determineVisibleStatuses() this.determineVisibleStatuses()
this.scrollLoad(e) this.scrollLoad(e)
}, 200), }, 200),

View file

@ -8,25 +8,7 @@
v-if="!embedded" v-if="!embedded"
:timeline-name="timelineName" :timeline-name="timelineName"
/> />
<div <ScrollTopButton />
v-if="showScrollTop"
class="rightside-button"
>
<button
class="button-unstyled scroll-to-top-button"
type="button"
:title="$t('general.scroll_to_top')"
@click="scrollToTop"
>
<FALayers class="fa-scale-110 fa-old-padding-layer">
<FAIcon icon="arrow-up" />
<FAIcon
icon="minus"
transform="up-7"
/>
</FALayers>
</button>
</div>
<template v-if="mobileLayout"> <template v-if="mobileLayout">
<div <div
v-if="showLoadButton" v-if="showLoadButton"

1543
yarn.lock

File diff suppressed because it is too large Load diff