• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Freegle / iznik-nuxt3 / 8b6fcaad-6d2c-497c-a213-b0e4eb987491

12 Mar 2026 01:59AM UTC coverage: 41.641% (-2.0%) from 43.67%
8b6fcaad-6d2c-497c-a213-b0e4eb987491

push

circleci

CircleCI Auto-merge
Auto-merge master to production after successful tests - Original commit: fix: Guard fetchUser against uninitialized $api

3840 of 9464 branches covered (40.57%)

Branch coverage included in aggregate %.

1744 of 3946 relevant lines covered (44.2%)

67.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/components/ChatMessageText.vue
1
<template>
2
  <div
3
    class="chatMessageWrapper"
4
    :class="{ myChatMessage: messageIsFromCurrentUser }"
5
  >
6
    <div class="chatMessageProfilePic">
7
      <ProfileImage
8
        :image="chatMessageProfileImage"
9
        :name="chatMessageProfileName"
10
        class="inline"
11
        is-thumbnail
12
        size="sm"
13
      />
14
    </div>
15
    <div
16
      class="chatMessage forcebreak chatMessage__owner"
17
      :class="{ 'chat-empty-message': isEmptyMessage }"
18
    >
19
      <div>
20
        <!-- ModTools: clickable links enabled -->
21
        <template v-if="isModTools">
×
22
          <span v-if="!highlightEmails">
23
            <span
24
              v-if="messageIsNew"
×
25
              class="prewrap font-weight-bold"
26
              v-html="linkifiedMessage"
27
            />
28
            <span v-else class="preline forcebreak" v-html="linkifiedMessage" />
×
29
            <b-img
30
              v-if="chatmessage?.image"
31
              fluid
32
              :src="chatmessage?.image.path"
×
33
              lazy
34
              rounded
35
            />
36
          </span>
37
          <span v-else>
38
            <span
39
              v-if="messageIsNew"
×
40
              class="prewrap font-weight-bold"
41
              v-html="linkifiedAndHighlightedMessage"
42
            />
43
            <span
44
              v-else
45
              class="preline forcebreak"
46
              v-html="linkifiedAndHighlightedMessage"
×
47
            />
48
            <b-img
49
              v-if="chatmessage?.image"
50
              fluid
51
              :src="chatmessage?.image.path"
×
52
              lazy
53
              rounded
54
            />
55
          </span>
56
        </template>
57
        <!-- Freegle: no clickable links for safety -->
58
        <template v-else>
59
          <span v-if="!highlightEmails">
60
            <span v-if="messageIsNew" class="prewrap font-weight-bold">{{
×
61
              emessage
62
            }}</span>
63
            <span v-else class="preline forcebreak">{{ emessage }}</span>
×
64
            <b-img
65
              v-if="chatmessage?.image"
66
              fluid
67
              :src="chatmessage?.image.path"
×
68
              lazy
69
              rounded
70
            />
71
          </span>
72
          <span v-else>
73
            <span v-if="messageIsNew" class="font-weight-bold">
×
74
              <Highlighter
75
                :text-to-highlight="emessage"
76
                :search-words="[regexEmail]"
77
                highlight-class-name="highlight"
78
                class="prewrap"
79
              />
80
            </span>
81
            <span v-else>
82
              <Highlighter
83
                :text-to-highlight="emessage"
84
                :search-words="[regexEmail]"
85
                highlight-class-name="highlight"
86
                class="preline forcebreak"
×
87
              />
88
            </span>
89
            <b-img
90
              v-if="chatmessage?.image"
91
              fluid
92
              :src="chatmessage?.image.path"
×
93
              lazy
94
              rounded
95
            />
96
          </span>
97
        </template>
98
      </div>
99
      <div v-if="lat || lng">
×
100
        <l-map
101
          ref="map"
102
          :zoom="16"
103
          :max-zoom="maxZoom"
104
          :center="[lat, lng]"
105
          :style="'width: 100%; height: 200px'"
106
        >
107
          <l-tile-layer :url="osmtile()" :attribution="attribution()" />
108
          <l-marker :lat-lng="[lat, lng]" :interactive="false" />
109
        </l-map>
110
        <div class="small text-muted">
111
          (Map shows approximate location of {{ postcode }})
112
        </div>
113
      </div>
114
    </div>
115
  </div>
116
</template>
117
<script setup>
118
import Highlighter from 'vue-highlight-words'
119
import { useChatMessageBase } from '~/composables/useChat'
120
import {
121
  linkifyText,
122
  linkifyAndHighlightEmails,
123
} from '~/composables/useLinkify'
124
import { ref, computed, onMounted } from '#imports'
125
import ProfileImage from '~/components/ProfileImage'
126
import { MAX_MAP_ZOOM, POSTCODE_REGEX } from '~/constants'
127
import { attribution, osmtile } from '~/composables/useMap'
128
import { useLocationStore } from '~/stores/location'
129
import { useMiscStore } from '~/stores/misc'
130

131
const miscStore = useMiscStore()
×
132

133
const props = defineProps({
134
  chatid: {
135
    type: Number,
136
    required: true,
137
  },
138
  id: {
139
    type: Number,
140
    required: true,
141
  },
142
  pov: {
143
    type: Number,
144
    required: false,
145
    default: null,
146
  },
147
  highlightEmails: {
148
    type: Boolean,
149
    required: false,
150
    default: false,
151
  },
152
})
153

154
// Use properties from ChatBase component via composable
155
const {
156
  chat,
157
  chatmessage,
158
  emessage,
159
  isEmptyMessage,
160
  messageIsFromCurrentUser,
161
  chatMessageProfileImage,
162
  chatMessageProfileName,
163
  regexEmail,
164
} = useChatMessageBase(props.chatid, props.id, props.pov)
165

166
// Data properties
167
const lat = ref(null)
168
const lng = ref(null)
169

170
// Computed properties
171
const maxZoom = computed(() => MAX_MAP_ZOOM)
172

173
const messageIsNew = computed(() => {
174
  return (
×
175
    chatmessage.value?.secondsago < 60 ||
×
176
    chatmessage.value?.id > chat.value?.lastmsgseen
×
177
  )
178
})
179

180
const postcode = computed(() => {
181
  let ret = null
×
182

183
  const msg = chatmessage.value?.message
×
184
  const postcodeMatch =
185
    typeof msg === 'string' ? msg.match(POSTCODE_REGEX) : null
×
186

187
  if (postcodeMatch?.length) {
×
188
    if (!postcodeMatch[0].includes(' ')) {
×
189
      // Make sure we have a space in the right place, because this helps with autocomplete
190
      ret = postcodeMatch[0].replace(/^(.*)(\d)/, '$1 $2')
191
    } else {
192
      ret = postcodeMatch[0]
193
    }
194
  }
195

196
  return ret
197
})
198

199
// In ModTools, we make URLs clickable. In Freegle, we don't for safety reasons.
200
const isModTools = computed(() => miscStore.modtools)
201

202
// Linkified message for ModTools (without email highlighting)
203
const linkifiedMessage = computed(() => {
204
  return linkifyText(emessage.value)
205
})
206

207
// Linkified message with email highlighting for ModTools chat review
208
const linkifiedAndHighlightedMessage = computed(() => {
209
  return linkifyAndHighlightEmails(emessage.value, regexEmail.value)
210
})
211

212
// Lifecycle hooks
213
onMounted(async () => {
214
  if (postcode.value) {
×
215
    // Use typeahead to find the postcode location.
216
    const locationStore = useLocationStore()
217
    const locs = await locationStore.typeahead(postcode.value)
×
218

219
    if (locs?.length) {
×
220
      lat.value = locs[0].lat
221
      lng.value = locs[0].lng
222
    }
223
  }
224
})
225
</script>
226
<style scoped lang="scss">
227
/* Chat link styling for ModTools - uses :deep() since content is rendered via v-html */
228
:deep(.chat-link) {
229
  color: $color-blue--base;
230
  text-decoration: underline;
231

232
  &:hover {
233
    text-decoration: none;
234
  }
235
}
236

237
/* Email highlight styling for ModTools - matches the Highlighter component */
238
:deep(.highlight) {
239
  color: $color-blue--base;
240
  background-color: initial;
241
}
242

243
.chat-empty-message {
244
  font-style: italic;
245
  color: $color-gray--dark;
246
}
247
</style>
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc