fix(webapp): add responsive masonry layout and skeleton loading UI (#9282)

This commit is contained in:
Ulf Gebhardt 2026-02-21 12:01:59 +01:00 committed by GitHub
parent 64594a3235
commit e3a41cb828
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 352 additions and 119 deletions

View File

@ -1,9 +1,5 @@
<template> <template>
<div <div class="ds-grid" :style="gridStyle" :class="[itemsCalculating ? 'reset-grid-height' : '']">
class="ds-grid"
:style="{ gridAutoRows: '20px', rowGap: '16px', columnGap: '16px' }"
:class="[itemsCalculating ? 'reset-grid-height' : '']"
>
<slot></slot> <slot></slot>
</div> </div>
</template> </template>
@ -13,16 +9,28 @@ export default {
data() { data() {
return { return {
itemsCalculating: 0, itemsCalculating: 0,
isMobile: false,
} }
}, },
computed: {
gridStyle() {
const size = this.isMobile ? '1px' : '2px'
return { gridAutoRows: size, rowGap: size }
},
},
watch: {
isMobile() {
this.$nextTick(() => {
this.$children.forEach((child) => {
if (child.calculateItemHeight) child.calculateItemHeight()
})
})
},
},
created() { created() {
this.$on('calculating-item-height', this.startCalculation) this.$on('calculating-item-height', this.startCalculation)
this.$on('finished-calculating-item-height', this.endCalculation) this.$on('finished-calculating-item-height', this.endCalculation)
}, },
beforeDestroy() {
this.$off('calculating-item-height', this.startCalculation)
this.$off('finished-calculating-item-height', this.endCalculation)
},
methods: { methods: {
startCalculation() { startCalculation() {
this.itemsCalculating += 1 this.itemsCalculating += 1
@ -30,6 +38,24 @@ export default {
endCalculation() { endCalculation() {
this.itemsCalculating -= 1 this.itemsCalculating -= 1
}, },
checkMobile() {
this.isMobile = window.innerWidth <= 810
},
},
mounted() {
this.checkMobile()
// Children mount before parent recalculate their spans with correct grid values
this.$nextTick(() => {
this.$children.forEach((child) => {
if (child.calculateItemHeight) child.calculateItemHeight()
})
})
window.addEventListener('resize', this.checkMobile)
},
beforeDestroy() {
this.$off('calculating-item-height', this.startCalculation)
this.$off('finished-calculating-item-height', this.endCalculation)
window.removeEventListener('resize', this.checkMobile)
}, },
} }
</script> </script>
@ -37,6 +63,11 @@ export default {
<style lang="scss"> <style lang="scss">
.ds-grid { .ds-grid {
grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
column-gap: 16px;
@media (max-width: 810px) {
column-gap: 8px;
}
} }
.reset-grid-height { .reset-grid-height {

View File

@ -7,38 +7,59 @@ describe('MasonryGridItem', () => {
let wrapper let wrapper
describe('given an imageAspectRatio', () => { describe('given an imageAspectRatio', () => {
it('sets the initial rowSpan to 13 when the ratio is higher than 1.3', () => { it('sets the initial rowSpan to 114 when the ratio is higher than 1.3', () => {
const propsData = { imageAspectRatio: 2 } const propsData = { imageAspectRatio: 2 }
wrapper = mount(MasonryGridItem, { localVue, propsData }) wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(13) expect(wrapper.vm.rowSpan).toBe(114)
}) })
it('sets the initial rowSpan to 15 when the ratio is between 1.3 and 1', () => { it('sets the initial rowSpan to 114 when the ratio is exactly 1.3', () => {
const propsData = { imageAspectRatio: 1.3 }
wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(114)
})
it('sets the initial rowSpan to 132 when the ratio is between 1.3 and 1', () => {
const propsData = { imageAspectRatio: 1.1 } const propsData = { imageAspectRatio: 1.1 }
wrapper = mount(MasonryGridItem, { localVue, propsData }) wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(15) expect(wrapper.vm.rowSpan).toBe(132)
}) })
it('sets the initial rowSpan to 18 when the ratio is between 1 and 0.7', () => { it('sets the initial rowSpan to 132 when the ratio is exactly 1', () => {
const propsData = { imageAspectRatio: 1.0 }
wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(132)
})
it('sets the initial rowSpan to 159 when the ratio is between 1 and 0.7', () => {
const propsData = { imageAspectRatio: 0.8 }
wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(159)
})
it('sets the initial rowSpan to 159 when the ratio is exactly 0.7', () => {
const propsData = { imageAspectRatio: 0.7 } const propsData = { imageAspectRatio: 0.7 }
wrapper = mount(MasonryGridItem, { localVue, propsData }) wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(18) expect(wrapper.vm.rowSpan).toBe(159)
}) })
it('sets the initial rowSpan to 25 when the ratio is lower than 0.7', () => { it('sets the initial rowSpan to 222 when the ratio is lower than 0.7', () => {
const propsData = { imageAspectRatio: 0.3 } const propsData = { imageAspectRatio: 0.3 }
wrapper = mount(MasonryGridItem, { localVue, propsData }) wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(25) expect(wrapper.vm.rowSpan).toBe(222)
}) })
})
describe('given no aspect ratio', () => { describe('given no aspect ratio', () => {
it('sets the initial rowSpan to 8 when not given an imageAspectRatio', () => { it('sets the initial rowSpan to 69', () => {
wrapper = mount(MasonryGridItem, { localVue }) wrapper = mount(MasonryGridItem, { localVue })
expect(wrapper.vm.rowSpan).toBe(8) expect(wrapper.vm.rowSpan).toBe(69)
})
}) })
}) })
}) })

View File

@ -9,10 +9,10 @@ const landscapeRatio = 1.3
const squareRatio = 1 const squareRatio = 1
const portraitRatio = 0.7 const portraitRatio = 0.7
const getRowSpan = (aspectRatio) => { const getRowSpan = (aspectRatio) => {
if (aspectRatio >= landscapeRatio) return 13 if (aspectRatio >= landscapeRatio) return 114
else if (aspectRatio >= squareRatio) return 15 else if (aspectRatio >= squareRatio) return 132
else if (aspectRatio >= portraitRatio) return 18 else if (aspectRatio >= portraitRatio) return 159
else return 25 else return 222
} }
export default { export default {
@ -24,7 +24,7 @@ export default {
}, },
data() { data() {
return { return {
rowSpan: this.imageAspectRatio ? getRowSpan(this.imageAspectRatio) : 8, rowSpan: this.imageAspectRatio ? getRowSpan(this.imageAspectRatio) : 69,
} }
}, },
methods: { methods: {

View File

@ -12,7 +12,9 @@
:highlight="isPinned" :highlight="isPinned"
> >
<template v-if="post.image" #heroImage> <template v-if="post.image" #heroImage>
<responsive-image :image="post.image" sizes="640px" class="image" /> <div class="image-placeholder" :style="{ aspectRatio: post.image.aspectRatio }">
<responsive-image :image="post.image" sizes="640px" class="image" />
</div>
</template> </template>
<client-only> <client-only>
<div class="post-user-row"> <div class="post-user-row">
@ -23,6 +25,17 @@
:typ="post.postType[0]" :typ="post.postType[0]"
/> />
</div> </div>
<template #placeholder>
<div class="post-user-row">
<div class="user-teaser-placeholder">
<div class="placeholder-avatar" />
<div class="placeholder-text">
<div class="placeholder-line placeholder-line--name" />
<div class="placeholder-line placeholder-line--date" />
</div>
</div>
</div>
</template>
</client-only> </client-only>
<h2 class="title hyphenate-text">{{ post.title }}</h2> <h2 class="title hyphenate-text">{{ post.title }}</h2>
<client-only> <client-only>
@ -133,6 +146,11 @@
<slot name="dateTime"></slot> <slot name="dateTime"></slot>
</span> </span>
</div> </div>
<template v-if="post.createdAt" #placeholder>
<div class="date-row">
<span class="placeholder-line placeholder-line--date-footer" />
</div>
</template>
</client-only> </client-only>
</os-card> </os-card>
</nuxt-link> </nuxt-link>
@ -189,16 +207,6 @@ export default {
default: false, default: false,
}, },
}, },
mounted() {
const { image } = this.post
if (!image) return
const width = this.$el.offsetWidth
const height = Math.min(width / image.aspectRatio, 2000)
const imageElement = this.$el.querySelector('.os-card__hero-image')
if (imageElement) {
imageElement.style.height = `${height}px`
}
},
computed: { computed: {
...mapGetters({ ...mapGetters({
user: 'auth/user', user: 'auth/user',
@ -289,6 +297,10 @@ export default {
height: 100%; height: 100%;
color: $text-color-base; color: $text-color-base;
padding-top: 16px; padding-top: 16px;
@media (max-width: 810px) {
padding-top: 8px;
}
} }
.post-user-row { .post-user-row {
@ -315,13 +327,24 @@ export default {
flex-direction: column; flex-direction: column;
overflow: visible; overflow: visible;
height: 100%; height: 100%;
padding-bottom: $space-x-small;
> .os-card__hero-image { > .os-card__hero-image {
border-top-left-radius: 5px; border-top-left-radius: 5px;
border-top-right-radius: 5px; border-top-right-radius: 5px;
} }
.image-placeholder {
width: 100%;
background-color: $color-neutral-80;
> .image {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
}
&.--blur-image > .os-card__hero-image .image { &.--blur-image > .os-card__hero-image .image {
filter: blur($blur-radius); filter: blur($blur-radius);
} }
@ -333,6 +356,8 @@ export default {
padding-bottom: 0 !important; padding-bottom: 0 !important;
} }
padding-bottom: $space-x-small !important;
.content { .content {
flex-grow: 1; flex-grow: 1;
margin-bottom: $space-small; margin-bottom: $space-small;
@ -368,10 +393,55 @@ export default {
margin-bottom: $space-small; margin-bottom: $space-small;
} }
.user-teaser-placeholder {
display: flex;
align-items: center;
margin-bottom: $space-small;
.placeholder-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: currentColor;
opacity: 0.15;
flex-shrink: 0;
}
.placeholder-text {
display: flex;
flex-direction: column;
gap: 6px;
padding-left: 10px;
flex: 1;
}
.placeholder-line {
height: 10px;
border-radius: 5px;
background: currentColor;
opacity: 0.15;
&--name {
width: 120px;
}
&--date {
width: 80px;
}
}
}
.date-row { .date-row {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
margin-top: $space-small; margin-top: $space-small;
> .placeholder-line--date-footer {
width: 100px;
height: 10px;
border-radius: 5px;
background: currentColor;
opacity: 0.15;
}
> .text { > .text {
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;

View File

@ -0,0 +1,127 @@
<template>
<div class="post-teaser-skeleton">
<div class="skeleton-image" />
<div class="skeleton-body">
<div class="skeleton-user">
<div class="skeleton-avatar" />
<div class="skeleton-user-text">
<div class="skeleton-line skeleton-line--short" />
<div class="skeleton-line skeleton-line--xshort" />
</div>
</div>
<div class="skeleton-title">
<div class="skeleton-line" />
<div class="skeleton-line skeleton-line--medium" />
</div>
<div class="skeleton-content">
<div class="skeleton-line" />
<div class="skeleton-line" />
<div class="skeleton-line skeleton-line--long" />
</div>
<div class="skeleton-footer">
<div class="skeleton-line skeleton-line--short" />
</div>
</div>
</div>
</template>
<script>
export default {
name: 'PostTeaserSkeleton',
}
</script>
<style lang="scss">
@keyframes skeleton-pulse {
0% {
opacity: 0.15;
}
50% {
opacity: 0.3;
}
100% {
opacity: 0.15;
}
}
.post-teaser-skeleton {
background: #fff;
border-radius: $border-radius-base;
box-shadow: $box-shadow-base;
overflow: hidden;
height: 100%;
}
.skeleton-image {
width: 100%;
padding-bottom: 56%;
background: currentColor;
animation: skeleton-pulse 1.5s ease-in-out infinite;
}
.skeleton-body {
padding: 12px;
display: flex;
flex-direction: column;
gap: 12px;
}
.skeleton-user {
display: flex;
align-items: center;
gap: 8px;
}
.skeleton-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: currentColor;
flex-shrink: 0;
animation: skeleton-pulse 1.5s ease-in-out infinite;
}
.skeleton-user-text {
display: flex;
flex-direction: column;
gap: 6px;
flex: 1;
}
.skeleton-title {
display: flex;
flex-direction: column;
gap: 6px;
}
.skeleton-content {
display: flex;
flex-direction: column;
gap: 6px;
}
.skeleton-footer {
margin-top: 4px;
}
.skeleton-line {
height: 12px;
border-radius: 6px;
background: currentColor;
animation: skeleton-pulse 1.5s ease-in-out infinite;
width: 100%;
&--xshort {
width: 25%;
}
&--short {
width: 40%;
}
&--medium {
width: 65%;
}
&--long {
width: 85%;
}
}
</style>

View File

@ -35,6 +35,7 @@ export default {
}, },
computed: { computed: {
progressBarWidth() { progressBarWidth() {
if (!this.goal) return 'width: 0%;'
return `width: ${(this.progress / this.goal) * 100}%;` return `width: ${(this.progress / this.goal) * 100}%;`
}, },
progressBarColorClass() { progressBarColorClass() {
@ -59,7 +60,6 @@ export default {
position: relative; position: relative;
height: 100%; height: 100%;
flex: 1; flex: 1;
margin-right: $space-x-small;
} }
.progress-bar__goal { .progress-bar__goal {
@ -108,7 +108,6 @@ export default {
.progress-bar__label { .progress-bar__label {
position: relative; position: relative;
float: right;
@media (max-width: 350px) { @media (max-width: 350px) {
font-size: $font-size-small; font-size: $font-size-small;
@ -117,7 +116,7 @@ export default {
.progress-bar-button { .progress-bar-button {
position: relative; position: relative;
float: right; margin-left: $space-x-small;
@media (max-width: 810px) { @media (max-width: 810px) {
display: none; display: none;

View File

@ -117,10 +117,6 @@ export default {
if (!(id && slug)) return '' if (!(id && slug)) return ''
return { name: 'groups-id-slug', params: { slug, id } } return { name: 'groups-id-slug', params: { slug, id } }
}, },
groupSlug() {
const { slug } = this.group || {}
return slug && `&${slug}`
},
groupName() { groupName() {
const { name } = this.group || {} const { name } = this.group || {}
return name || this.$t('profile.userAnonym') return name || this.$t('profile.userAnonym')

View File

@ -66,10 +66,15 @@ export default {
padding-bottom: 8rem; padding-bottom: 8rem;
} }
.desktop-footer { @media (max-width: 810px) {
@media (max-width: 810px) { .desktop-footer {
display: none; display: none;
} }
.ds-container {
padding-left: $space-x-small !important;
padding-right: $space-x-small !important;
}
} }
.chat-modul { .chat-modul {

View File

@ -224,8 +224,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-my-small" class="ds-my-small"
> >
<h3 <h3
class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin" class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin word-break-all"
style="word-break: break-all;"
> >
School For Citizens School For Citizens
@ -233,8 +232,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</h3> </h3>
<p <p
class="ds-text ds-text-center ds-text-soft" class="ds-text ds-text-center ds-text-soft word-break-all"
style="word-break: break-all;"
> >
&school-for-citizens &school-for-citizens
@ -536,8 +534,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="chip" class="chip"
> >
<span <span
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]" class="word-break-all os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em] word-break-all"
style="word-break: break-all;"
> >
Our children shall receive education for life. Our children shall receive education for life.
</span> </span>
@ -990,7 +987,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<div <div
class="ds-grid" class="ds-grid"
style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;" style="grid-auto-rows: 2px; row-gap: 2px;"
> >
<div <div
style="grid-row-end: span 4; grid-column: 1 / -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
@ -1148,8 +1145,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-my-small" class="ds-my-small"
> >
<h3 <h3
class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin" class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin word-break-all"
style="word-break: break-all;"
> >
School For Citizens School For Citizens
@ -1157,8 +1153,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</h3> </h3>
<p <p
class="ds-text ds-text-center ds-text-soft" class="ds-text ds-text-center ds-text-soft word-break-all"
style="word-break: break-all;"
> >
&school-for-citizens &school-for-citizens
@ -1413,8 +1408,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="chip" class="chip"
> >
<span <span
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]" class="word-break-all os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em] word-break-all"
style="word-break: break-all;"
> >
Our children shall receive education for life. Our children shall receive education for life.
</span> </span>
@ -1498,7 +1492,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<div <div
class="ds-grid" class="ds-grid"
style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;" style="grid-auto-rows: 2px; row-gap: 2px;"
> >
<div <div
style="grid-row-end: span 4; grid-column: 1 / -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
@ -1656,8 +1650,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-my-small" class="ds-my-small"
> >
<h3 <h3
class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin" class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin word-break-all"
style="word-break: break-all;"
> >
School For Citizens School For Citizens
@ -1665,8 +1658,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</h3> </h3>
<p <p
class="ds-text ds-text-center ds-text-soft" class="ds-text ds-text-center ds-text-soft word-break-all"
style="word-break: break-all;"
> >
&school-for-citizens &school-for-citizens
@ -1940,8 +1932,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="chip" class="chip"
> >
<span <span
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]" class="word-break-all os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em] word-break-all"
style="word-break: break-all;"
> >
Our children shall receive education for life. Our children shall receive education for life.
</span> </span>
@ -2025,7 +2016,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<div <div
class="ds-grid" class="ds-grid"
style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;" style="grid-auto-rows: 2px; row-gap: 2px;"
> >
<div <div
style="grid-row-end: span 4; grid-column: 1 / -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
@ -2278,8 +2269,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-my-small" class="ds-my-small"
> >
<h3 <h3
class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin" class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin word-break-all"
style="word-break: break-all;"
> >
School For Citizens School For Citizens
@ -2287,8 +2277,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</h3> </h3>
<p <p
class="ds-text ds-text-center ds-text-soft" class="ds-text ds-text-center ds-text-soft word-break-all"
style="word-break: break-all;"
> >
&school-for-citizens &school-for-citizens
@ -2589,8 +2578,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="chip" class="chip"
> >
<span <span
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]" class="word-break-all os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em] word-break-all"
style="word-break: break-all;"
> >
Our children shall receive education for life. Our children shall receive education for life.
</span> </span>
@ -3043,7 +3031,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<div <div
class="ds-grid" class="ds-grid"
style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;" style="grid-auto-rows: 2px; row-gap: 2px;"
> >
<div <div
style="grid-row-end: span 4; grid-column: 1 / -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
@ -3384,8 +3372,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-my-small" class="ds-my-small"
> >
<h3 <h3
class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin" class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin word-break-all"
style="word-break: break-all;"
> >
Yoga Practice Yoga Practice
@ -3393,8 +3380,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</h3> </h3>
<p <p
class="ds-text ds-text-center ds-text-soft" class="ds-text ds-text-center ds-text-soft word-break-all"
style="word-break: break-all;"
> >
&yoga-practice &yoga-practice
@ -4081,7 +4067,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<div <div
class="ds-grid" class="ds-grid"
style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;" style="grid-auto-rows: 2px; row-gap: 2px;"
> >
<div <div
style="grid-row-end: span 4; grid-column: 1 / -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
@ -4239,8 +4225,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-my-small" class="ds-my-small"
> >
<h3 <h3
class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin" class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin word-break-all"
style="word-break: break-all;"
> >
Yoga Practice Yoga Practice
@ -4248,8 +4233,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</h3> </h3>
<p <p
class="ds-text ds-text-center ds-text-soft" class="ds-text ds-text-center ds-text-soft word-break-all"
style="word-break: break-all;"
> >
&yoga-practice &yoga-practice
@ -4882,7 +4866,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<div <div
class="ds-grid" class="ds-grid"
style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;" style="grid-auto-rows: 2px; row-gap: 2px;"
> >
<div <div
style="grid-row-end: span 4; grid-column: 1 / -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
@ -5040,8 +5024,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-my-small" class="ds-my-small"
> >
<h3 <h3
class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin" class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin word-break-all"
style="word-break: break-all;"
> >
Yoga Practice Yoga Practice
@ -5049,8 +5032,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</h3> </h3>
<p <p
class="ds-text ds-text-center ds-text-soft" class="ds-text ds-text-center ds-text-soft word-break-all"
style="word-break: break-all;"
> >
&yoga-practice &yoga-practice
@ -5702,7 +5684,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<div <div
class="ds-grid" class="ds-grid"
style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;" style="grid-auto-rows: 2px; row-gap: 2px;"
> >
<div <div
style="grid-row-end: span 4; grid-column: 1 / -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
@ -5955,8 +5937,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-my-small" class="ds-my-small"
> >
<h3 <h3
class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin" class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin word-break-all"
style="word-break: break-all;"
> >
Yoga Practice Yoga Practice
@ -5964,8 +5945,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</h3> </h3>
<p <p
class="ds-text ds-text-center ds-text-soft" class="ds-text ds-text-center ds-text-soft word-break-all"
style="word-break: break-all;"
> >
&yoga-practice &yoga-practice
@ -6651,7 +6631,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<div <div
class="ds-grid" class="ds-grid"
style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;" style="grid-auto-rows: 2px; row-gap: 2px;"
> >
<div <div
style="grid-row-end: span 4; grid-column: 1 / -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
@ -6992,8 +6972,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
class="ds-my-small" class="ds-my-small"
> >
<h3 <h3
class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin" class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin word-break-all"
style="word-break: break-all;"
> >
Investigative Journalism Investigative Journalism
@ -7001,8 +6980,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
</h3> </h3>
<p <p
class="ds-text ds-text-center ds-text-soft" class="ds-text ds-text-center ds-text-soft word-break-all"
style="word-break: break-all;"
> >
&investigative-journalism &investigative-journalism
@ -7303,8 +7281,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
class="chip" class="chip"
> >
<span <span
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]" class="word-break-all os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em] word-break-all"
style="word-break: break-all;"
> >
Investigative journalists share ideas and insights and can collaborate. Investigative journalists share ideas and insights and can collaborate.
</span> </span>
@ -7774,7 +7751,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<div <div
class="ds-grid" class="ds-grid"
style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;" style="grid-auto-rows: 2px; row-gap: 2px;"
> >
<div <div
style="grid-row-end: span 4; grid-column: 1 / -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
@ -8039,8 +8016,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
class="ds-my-small" class="ds-my-small"
> >
<h3 <h3
class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin" class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin word-break-all"
style="word-break: break-all;"
> >
Investigative Journalism Investigative Journalism
@ -8048,8 +8024,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
</h3> </h3>
<p <p
class="ds-text ds-text-center ds-text-soft" class="ds-text ds-text-center ds-text-soft word-break-all"
style="word-break: break-all;"
> >
&investigative-journalism &investigative-journalism
@ -8349,8 +8324,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
class="chip" class="chip"
> >
<span <span
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]" class="word-break-all os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em] word-break-all"
style="word-break: break-all;"
> >
Investigative journalists share ideas and insights and can collaborate. Investigative journalists share ideas and insights and can collaborate.
</span> </span>
@ -8820,7 +8794,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<div <div
class="ds-grid" class="ds-grid"
style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;" style="grid-auto-rows: 2px; row-gap: 2px;"
> >
<div <div
style="grid-row-end: span 4; grid-column: 1 / -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"

View File

@ -30,13 +30,12 @@
<div class="ds-my-small"> <div class="ds-my-small">
<!-- group name --> <!-- group name -->
<h3 <h3
class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin" class="ds-heading ds-heading-h3 ds-heading-align-center ds-heading-no-margin word-break-all"
style="word-break: break-all"
> >
{{ groupName }} {{ groupName }}
</h3> </h3>
<!-- group slug --> <!-- group slug -->
<p class="ds-text ds-text-center ds-text-soft" style="word-break: break-all"> <p class="ds-text ds-text-center ds-text-soft word-break-all">
{{ `&${groupSlug}` }} {{ `&${groupSlug}` }}
</p> </p>
<!-- group location --> <!-- group location -->
@ -163,7 +162,7 @@
</p> </p>
<div class="ds-my-xx-small"></div> <div class="ds-my-xx-small"></div>
<div class="chip" align="center"> <div class="chip" align="center">
<os-badge style="word-break: break-all">{{ group.about }}</os-badge> <os-badge class="word-break-all">{{ group.about }}</os-badge>
</div> </div>
</div> </div>
</template> </template>
@ -710,6 +709,9 @@ export default {
margin-bottom: $space-small; margin-bottom: $space-small;
} }
} }
.word-break-all {
word-break: break-all;
}
.collaps-button { .collaps-button {
align-self: flex-end; align-self: flex-end;
} }

View File

@ -110,8 +110,14 @@
!isMobile && posts.length <= 2 ? 'grid-column-helper' : '', !isMobile && posts.length <= 2 ? 'grid-column-helper' : '',
]" ]"
> >
<!-- skeleton placeholders while loading -->
<template v-if="$apollo.loading && posts.length === 0">
<masonry-grid-item v-for="n in 6" :key="'skeleton-' + n" :imageAspectRatio="1.5">
<post-teaser-skeleton />
</masonry-grid-item>
</template>
<!-- news feed --> <!-- news feed -->
<template v-if="hasResults"> <template v-else-if="hasResults">
<masonry-grid-item <masonry-grid-item
v-for="post in posts" v-for="post in posts"
:key="post.id" :key="post.id"
@ -160,6 +166,7 @@ import DonationInfo from '~/components/DonationInfo/DonationInfo.vue'
import HashtagsFilter from '~/components/HashtagsFilter/HashtagsFilter.vue' import HashtagsFilter from '~/components/HashtagsFilter/HashtagsFilter.vue'
import HcEmpty from '~/components/Empty/Empty' import HcEmpty from '~/components/Empty/Empty'
import PostTeaser from '~/components/PostTeaser/PostTeaser.vue' import PostTeaser from '~/components/PostTeaser/PostTeaser.vue'
import PostTeaserSkeleton from '~/components/PostTeaser/PostTeaserSkeleton.vue'
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue' import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue'
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue' import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
import HeaderButton from '~/components/FilterMenu/HeaderButton' import HeaderButton from '~/components/FilterMenu/HeaderButton'
@ -179,6 +186,7 @@ export default {
OsIcon, OsIcon,
OsSpinner, OsSpinner,
PostTeaser, PostTeaser,
PostTeaserSkeleton,
HcEmpty, HcEmpty,
MasonryGrid, MasonryGrid,
MasonryGridItem, MasonryGridItem,

View File

@ -445,7 +445,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
> >
<div <div
class="ds-grid" class="ds-grid"
style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;" style="grid-auto-rows: 2px; row-gap: 2px;"
> >
<div <div
class="tab-navigation" class="tab-navigation"
@ -1168,7 +1168,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
> >
<div <div
class="ds-grid" class="ds-grid"
style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;" style="grid-auto-rows: 2px; row-gap: 2px;"
> >
<div <div
class="tab-navigation" class="tab-navigation"
@ -1715,7 +1715,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
> >
<div <div
class="ds-grid" class="ds-grid"
style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;" style="grid-auto-rows: 2px; row-gap: 2px;"
> >
<div <div
class="tab-navigation" class="tab-navigation"
@ -2341,7 +2341,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
> >
<div <div
class="ds-grid" class="ds-grid"
style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;" style="grid-auto-rows: 2px; row-gap: 2px;"
> >
<div <div
class="tab-navigation" class="tab-navigation"