Skip to content

Commit eae1527

Browse files
Merge pull request #7342 from christianbeeznest/fixes-updates217
Social: Fix Social Wall JS breakage: correct wall routing, placeholder, emoticons path, and online badge
2 parents 8865b10 + 016ac41 commit eae1527

14 files changed

Lines changed: 20090 additions & 163 deletions

File tree

assets/vue/components/social/MyFriendsCard.vue

Lines changed: 197 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,21 @@
55
>
66
<template #header>
77
<div class="px-4 py-3 bg-gray-200">
8-
<h2 class="text-xl font-semibold">{{ t("My friends") }}</h2>
8+
<h2 class="text-xl font-semibold">{{ friendsTitle }}</h2>
99
</div>
1010
</template>
1111
<hr class="my-2" />
1212
<div class="px-4">
1313
<div
14-
v-if="isCurrentUser"
14+
v-if="isOwnWall"
1515
class="flex items-center mb-4"
1616
>
1717
<input
1818
v-model="searchQuery"
1919
:placeholder="t('Search')"
2020
class="flex-grow p-2 h-[44px] border border-gray-300 rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500"
2121
type="search"
22-
@input="fetchFriends"
22+
@input="onSearchInput"
2323
/>
2424
<button
2525
class="p-2 h-[44px] bg-gray-200 border border-gray-300 rounded-r-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500"
@@ -36,28 +36,25 @@
3636
class="list-group-item friend-item d-flex align-items-center mb-2"
3737
>
3838
<a
39-
:href="`/social?id=${friend.friend.id}`"
39+
href="#"
4040
class="d-flex align-items-center text-decoration-none"
41+
@click.prevent="goToWall(friend.friend.id)"
4142
>
42-
<BaseUserAvatar
43-
:alt="t('Picture')"
44-
:image-url="friend.friend.illustrationUrl"
45-
class="mr-2"
46-
/>
47-
<span
48-
>{{ friend.friend.firstname }} {{ friend.friend.lastname }}
49-
<small class="text-muted">({{ friend.friend.username }})</small></span
50-
>
51-
<span
52-
v-if="friend.friend.isOnline"
53-
class="mdi mdi-circle circle-green mx-2"
54-
title="Online"
55-
></span>
56-
<span
57-
v-else
58-
class="mdi mdi-circle circle-gray mx-2"
59-
title="Offline"
60-
></span>
43+
<div class="relative mr-2 inline-block">
44+
<BaseUserAvatar
45+
:alt="t('Picture')"
46+
:image-url="friend.friend.illustrationUrl"
47+
/>
48+
<span>
49+
{{ friend.friend.firstname }} {{ friend.friend.lastname }}
50+
<small class="text-muted">({{ friend.friend.username }})</small>
51+
</span>
52+
<span
53+
class="absolute -top-0.5 -right-0.5 h-3 w-3 rounded-full border-2 border-white"
54+
:style="{ backgroundColor: friend.friend.isOnline ? '#22c55e' : '#9ca3af' }"
55+
:title="friend.friend.isOnline ? 'Online' : 'Offline'"
56+
></span>
57+
</div>
6158
</a>
6259
</li>
6360
</ul>
@@ -67,13 +64,14 @@
6764
>
6865
<a
6966
href="#"
70-
@click="viewAll"
71-
>{{ t("View all friends") }}</a
67+
@click.prevent="viewAll"
7268
>
69+
{{ t("View all friends") }}
70+
</a>
7371
</div>
7472
</div>
7573
<div
76-
v-if="allowSocialMap && isCurrentUser"
74+
v-if="allowSocialMap && isOwnWall"
7775
class="text-center mt-3"
7876
>
7977
<BaseButton
@@ -88,66 +86,203 @@
8886

8987
<script setup>
9088
import BaseCard from "../basecomponents/BaseCard.vue"
89+
import BaseUserAvatar from "../basecomponents/BaseUserAvatar.vue"
90+
import BaseButton from "../basecomponents/BaseButton.vue"
9191
import { useI18n } from "vue-i18n"
92-
import { computed, inject, ref, watchEffect } from "vue"
92+
import { computed, ref, watch } from "vue"
93+
import { useRoute, useRouter } from "vue-router"
9394
import axios from "axios"
94-
import BaseUserAvatar from "../basecomponents/BaseUserAvatar.vue"
9595
import { ENTRYPOINT } from "../../config/entrypoint"
96-
import { useRouter } from "vue-router"
9796
import { usePlatformConfig } from "../../store/platformConfig"
98-
import BaseButton from "../basecomponents/BaseButton.vue"
97+
import { useSecurityStore } from "../../store/securityStore"
9998
10099
const { t } = useI18n()
101-
const friends = ref([])
102-
const searchQuery = ref("")
103-
const user = inject("social-user")
104-
const isCurrentUser = inject("is-current-user")
100+
const route = useRoute()
105101
const router = useRouter()
106102
const platformConfigStore = usePlatformConfig()
107-
103+
const securityStore = useSecurityStore()
108104
const allowSocialMap = computed(() => platformConfigStore.getSetting("profile.allow_social_map_fields"))
105+
const wallIdFromRoute = computed(() => {
106+
const raw = route.query.id
107+
if (!raw) return null
108+
const n = Number(raw)
109+
return Number.isFinite(n) && n > 0 ? n : null
110+
})
111+
const isOwnWall = computed(() => !wallIdFromRoute.value)
112+
const targetUserId = computed(() => Number(wallIdFromRoute.value || securityStore.user?.id || 0))
113+
const titleUser = ref(null)
114+
const titleUserName = computed(() => {
115+
const u = titleUser.value
116+
return u?.fullName || [u?.firstname, u?.lastname].filter(Boolean).join(" ") || u?.username || ""
117+
})
118+
const friendsTitle = computed(() => {
119+
if (isOwnWall.value) return t("My friends")
120+
if (!titleUserName.value) return t("Friends")
121+
return `${t("Friends of")} ${titleUserName.value}`.trim()
122+
})
123+
124+
async function loadTitleUser() {
125+
// Own wall: logged-in user
126+
if (isOwnWall.value) {
127+
titleUser.value = securityStore.user || null
128+
return
129+
}
130+
131+
const id = targetUserId.value
132+
if (!id) {
133+
titleUser.value = null
134+
return
135+
}
136+
137+
titleUser.value = {
138+
id,
139+
"@id": `/api/users/${id}`,
140+
fullName: "",
141+
firstname: "",
142+
lastname: "",
143+
username: "",
144+
}
145+
146+
try {
147+
const { data } = await axios.get(`${ENTRYPOINT}users/${id}`)
148+
titleUser.value = data
149+
} catch (e) {
150+
console.warn("Failed to load wall owner for friends card title.", e)
151+
}
152+
}
153+
154+
watch([() => targetUserId.value, () => securityStore.user?.["@id"]], () => loadTitleUser(), { immediate: true })
155+
156+
const friends = ref([])
157+
const allFriends = ref([])
158+
const searchQuery = ref("")
159+
const limitedFriends = computed(() => friends.value.slice(0, 10))
109160
const search = () => {
110161
router.push({ name: "SocialSearch", query: { query: searchQuery.value, type: "user" } })
111162
}
112-
const limitedFriends = computed(() => {
113-
return friends.value.slice(0, 10)
114-
})
115-
116163
const redirectToGeolocalization = () => {
117164
window.location.href = "/main/social/map.php"
118165
}
119166
120-
async function fetchFriends(userId) {
167+
function goToWall(userId) {
168+
router.push({ path: "/social", query: { id: userId } })
169+
}
170+
171+
function buildUserIri(userId) {
172+
return `/api/users/${userId}`
173+
}
174+
175+
function normalizeFriendRelation(rel, meIri) {
176+
const userIri = rel?.user?.["@id"]
177+
const friendIri = rel?.friend?.["@id"]
178+
179+
if (!userIri || !friendIri) return null
180+
if (userIri === meIri && friendIri === meIri) return null
181+
182+
if (userIri === meIri) {
183+
if (friendIri === meIri) return null
184+
return rel
185+
}
186+
187+
if (friendIri === meIri) {
188+
const swapped = { ...rel, user: rel.friend, friend: rel.user }
189+
if (swapped.friend?.["@id"] === meIri) return null
190+
return swapped
191+
}
192+
193+
return null
194+
}
195+
196+
function applySearchFilter() {
197+
const q = (searchQuery.value || "").trim().toLowerCase()
198+
if (!q) {
199+
friends.value = [...allFriends.value]
200+
return
201+
}
202+
203+
friends.value = allFriends.value.filter((rel) => {
204+
const username = (rel.friend?.username || "").toLowerCase()
205+
const firstname = (rel.friend?.firstname || "").toLowerCase()
206+
const lastname = (rel.friend?.lastname || "").toLowerCase()
207+
return username.includes(q) || firstname.includes(q) || lastname.includes(q)
208+
})
209+
}
210+
211+
function onSearchInput() {
212+
applySearchFilter()
213+
}
214+
215+
async function fetchFriends(forUserId) {
121216
try {
122-
const response = await axios.get(`${ENTRYPOINT}user_rel_users?user=/api/users/${userId}&relationType=3`, {
123-
params: {
124-
"friend.username": searchQuery.value ? searchQuery.value : undefined,
125-
},
126-
})
127-
friends.value = response.data["hydra:member"]
128-
129-
const friendIds = friends.value.map((friend) => friend.friend.id)
130-
const onlineStatusResponse = await axios.post(`/social-network/online-status`, { userIds: friendIds })
131-
const onlineStatuses = onlineStatusResponse.data
132-
133-
friends.value.forEach((friend) => {
134-
friend.friend.isOnline = onlineStatuses[friend.friend.id] || false
135-
})
217+
const safeUserId = Number(forUserId || 0)
218+
if (!safeUserId) {
219+
console.warn("Friends list: target user is not ready yet.")
220+
return
221+
}
222+
223+
const meIri = buildUserIri(safeUserId)
224+
225+
const [forward, backward] = await Promise.all([
226+
axios.get(`${ENTRYPOINT}user_rel_users`, { params: { user: meIri, relationType: 3 } }),
227+
axios.get(`${ENTRYPOINT}user_rel_users`, { params: { friend: meIri, relationType: 3 } }),
228+
])
229+
230+
const raw = [...(forward.data?.["hydra:member"] || []), ...(backward.data?.["hydra:member"] || [])]
231+
232+
const seen = new Set()
233+
const normalized = []
234+
235+
for (const rel of raw) {
236+
const fixed = normalizeFriendRelation(rel, meIri)
237+
if (!fixed) continue
238+
239+
const otherIri = fixed.friend?.["@id"]
240+
if (!otherIri || seen.has(otherIri)) continue
241+
242+
seen.add(otherIri)
243+
normalized.push(fixed)
244+
}
245+
246+
const friendIds = normalized.map((r) => r.friend?.id).filter(Boolean)
247+
if (friendIds.length) {
248+
const onlineStatusResponse = await axios.post(`/social-network/online-status`, { userIds: friendIds })
249+
const onlineStatuses = onlineStatusResponse.data || {}
250+
251+
normalized.forEach((r) => {
252+
const id = r.friend?.id
253+
if (id) r.friend.isOnline = !!onlineStatuses[id]
254+
})
255+
}
256+
257+
allFriends.value = normalized
258+
applySearchFilter()
136259
} catch (error) {
137260
console.error("Error fetching friends:", error)
138261
}
139262
}
140263
141264
const viewAll = () => {
142-
if (isCurrentUser) {
265+
const id = targetUserId.value
266+
if (isOwnWall.value) {
143267
router.push("/resources/friends")
144-
} else {
145-
router.push("/resources/friends?id=" + user.value.id)
268+
return
146269
}
147-
}
148-
watchEffect(() => {
149-
if (user.value && user.value.id) {
150-
fetchFriends(user.value.id)
270+
if (id) {
271+
router.push("/resources/friends?id=" + id)
151272
}
152-
})
273+
}
274+
275+
watch(
276+
() => targetUserId.value,
277+
(newId) => {
278+
friends.value = []
279+
allFriends.value = []
280+
searchQuery.value = ""
281+
282+
if (newId) {
283+
fetchFriends(newId)
284+
}
285+
},
286+
{ immediate: true },
287+
)
153288
</script>

0 commit comments

Comments
 (0)