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

Freegle / Iznik / 11495

09 May 2026 07:35AM UTC coverage: 69.06% (-3.8%) from 72.847%
11495

Pull #408

circleci

edwh
docs(migration): mark restartproject and repaircafewales as migrated (PR #408)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pull Request #408: feat(batch): migrate check_cgas, visualise, tn_sync + dry-run improvements

9127 of 10554 branches covered (86.48%)

Branch coverage included in aggregate %.

507 of 663 new or added lines in 16 files covered. (76.47%)

11902 existing lines in 138 files now uncovered.

101630 of 149824 relevant lines covered (67.83%)

19.56 hits per line

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

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

131
const miscStore = useMiscStore()
1✔
132

133
const props = defineProps({
1✔
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
1✔
155
const {
1✔
156
  chat,
1✔
157
  chatmessage,
1✔
158
  emessage,
1✔
159
  isEmptyMessage,
1✔
160
  messageIsFromCurrentUser,
1✔
161
  chatMessageProfileImage,
1✔
162
  chatMessageProfileName,
1✔
163
  regexEmail,
1✔
164
} = useChatMessageBase(props.chatid, props.id, props.pov)
1✔
165

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

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

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

180
const postcode = computed(() => {
1✔
181
  let ret = null
4✔
182

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

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

196
  return ret
4✔
197
})
4✔
198

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

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

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

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

219
    if (locs?.length) {
×
UNCOV
220
      lat.value = locs[0].lat
×
UNCOV
221
      lng.value = locs[0].lng
×
UNCOV
222
    }
×
UNCOV
223
  }
×
224
})
4✔
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: var(--color-link);
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: var(--color-link);
240
  background-color: initial;
241
}
242

243
.chat-empty-message {
244
  font-style: italic;
245
  color: var(--color-gray-600);
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