|
|
@ -77,12 +77,66 @@ |
|
|
|
</a> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- Description --> |
|
|
|
<p class="font-medium overflow-hidden"> |
|
|
|
<a |
|
|
|
class="block cursor-pointer overflow-hidden text-ellipsis" |
|
|
|
@click="emitLoadClaim(record.jwtId)" |
|
|
|
<!-- Emoji Section --> |
|
|
|
<div |
|
|
|
class="float-right ml-3 mb-1 bg-white rounded border border-slate-300 px-1.5 py-0.5 max-w-[200px]" |
|
|
|
> |
|
|
|
<div class="flex items-center justify-between gap-1"> |
|
|
|
<!-- Existing Emojis Display --> |
|
|
|
<div v-if="hasEmojis" class="flex flex-wrap gap-1 mr-2"> |
|
|
|
<button |
|
|
|
v-for="(count, emoji) in record.emojiCount" |
|
|
|
:key="emoji" |
|
|
|
class="inline-flex items-center gap-0.5 px-1 py-0.5 text-xs bg-slate-50 hover:bg-slate-100 rounded border border-slate-200 transition-colors cursor-pointer" |
|
|
|
:class="{ 'bg-blue-50 border-blue-200': isUserEmoji(emoji) }" |
|
|
|
:title=" |
|
|
|
isUserEmoji(emoji) |
|
|
|
? 'Click to remove your emoji' |
|
|
|
: 'Click to add this emoji' |
|
|
|
" |
|
|
|
@click="toggleEmoji(emoji)" |
|
|
|
> |
|
|
|
<span class="text-sm leading-none">{{ emoji }}</span> |
|
|
|
<span class="text-xs text-slate-600 font-medium leading-none">{{ |
|
|
|
count |
|
|
|
}}</span> |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- Add Emoji Button --> |
|
|
|
<button |
|
|
|
class="inline-flex px-1 py-0.5 text-xs bg-slate-100 hover:bg-slate-200 rounded border border-slate-300 transition-colors items-center justify-center ml-auto" |
|
|
|
:title="showEmojiPicker ? 'Close emoji picker' : 'Add emoji'" |
|
|
|
@click="showEmojiPicker = !showEmojiPicker" |
|
|
|
> |
|
|
|
<span class="text-sm leading-none">{{ |
|
|
|
showEmojiPicker ? "x" : "😊" |
|
|
|
}}</span> |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- Emoji Picker (placeholder for now) --> |
|
|
|
<div |
|
|
|
v-if="showEmojiPicker" |
|
|
|
class="mt-1 p-1.5 bg-slate-50 rounded border border-slate-300" |
|
|
|
> |
|
|
|
<!-- Temporary emoji buttons for testing --> |
|
|
|
<div class="flex flex-wrap gap-1 mt-1"> |
|
|
|
<button |
|
|
|
v-for="emoji in QUICK_EMOJIS" |
|
|
|
:key="emoji" |
|
|
|
class="p-0.5 hover:bg-slate-200 rounded text-base" |
|
|
|
@click="selectEmoji(emoji)" |
|
|
|
> |
|
|
|
{{ emoji }} |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- Description --> |
|
|
|
<p class="font-medium"> |
|
|
|
<a class="block cursor-pointer" @click="emitLoadClaim(record.jwtId)"> |
|
|
|
<vue-markdown |
|
|
|
:source="truncatedDescription" |
|
|
|
class="markdown-content" |
|
|
@ -91,7 +145,7 @@ |
|
|
|
</p> |
|
|
|
|
|
|
|
<div |
|
|
|
class="relative flex justify-between gap-4 max-w-[40rem] mx-auto mt-4" |
|
|
|
class="clear-right relative flex justify-between gap-4 max-w-[40rem] mx-auto mt-4" |
|
|
|
> |
|
|
|
<!-- Source --> |
|
|
|
<div |
|
|
@ -254,17 +308,23 @@ |
|
|
|
|
|
|
|
<script lang="ts"> |
|
|
|
import { Component, Prop, Vue, Emit } from "vue-facing-decorator"; |
|
|
|
import { GiveRecordWithContactInfo } from "@/interfaces/give"; |
|
|
|
import VueMarkdown from "vue-markdown-render"; |
|
|
|
|
|
|
|
import { logger } from "../utils/logger"; |
|
|
|
import { |
|
|
|
createAndSubmitClaim, |
|
|
|
getHeaders, |
|
|
|
isHiddenDid, |
|
|
|
} from "../libs/endorserServer"; |
|
|
|
import EntityIcon from "./EntityIcon.vue"; |
|
|
|
import { isHiddenDid } from "../libs/endorserServer"; |
|
|
|
import ProjectIcon from "./ProjectIcon.vue"; |
|
|
|
import { createNotifyHelpers, NotifyFunction } from "@/utils/notify"; |
|
|
|
import { createNotifyHelpers, NotifyFunction, TIMEOUTS } from "@/utils/notify"; |
|
|
|
import { |
|
|
|
NOTIFY_PERSON_HIDDEN, |
|
|
|
NOTIFY_UNKNOWN_PERSON, |
|
|
|
} from "@/constants/notifications"; |
|
|
|
import { TIMEOUTS } from "@/utils/notify"; |
|
|
|
import VueMarkdown from "vue-markdown-render"; |
|
|
|
import { GenericVerifiableCredential } from "@/interfaces"; |
|
|
|
import { GiveRecordWithContactInfo } from "@/interfaces/give"; |
|
|
|
|
|
|
|
@Component({ |
|
|
|
components: { |
|
|
@ -274,15 +334,23 @@ import VueMarkdown from "vue-markdown-render"; |
|
|
|
}, |
|
|
|
}) |
|
|
|
export default class ActivityListItem extends Vue { |
|
|
|
readonly QUICK_EMOJIS = ["👍", "👏", "❤️", "🎉", "😊", "😆", "🔥"]; |
|
|
|
|
|
|
|
@Prop() record!: GiveRecordWithContactInfo; |
|
|
|
@Prop() lastViewedClaimId?: string; |
|
|
|
@Prop() isRegistered!: boolean; |
|
|
|
@Prop() activeDid!: string; |
|
|
|
@Prop() apiServer!: string; |
|
|
|
|
|
|
|
isHiddenDid = isHiddenDid; |
|
|
|
notify!: ReturnType<typeof createNotifyHelpers>; |
|
|
|
$notify!: NotifyFunction; |
|
|
|
|
|
|
|
// Emoji-related data |
|
|
|
showEmojiPicker = false; |
|
|
|
|
|
|
|
userEmojis: string[] | null = null; // load this only when needed |
|
|
|
|
|
|
|
created() { |
|
|
|
this.notify = createNotifyHelpers(this.$notify); |
|
|
|
} |
|
|
@ -346,5 +414,119 @@ export default class ActivityListItem extends Vue { |
|
|
|
day: "numeric", |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// Emoji-related computed properties and methods |
|
|
|
get hasEmojis(): boolean { |
|
|
|
return Object.keys(this.record.emojiCount).length > 0; |
|
|
|
} |
|
|
|
|
|
|
|
async loadUserEmojis(): Promise<void> { |
|
|
|
try { |
|
|
|
const response = await this.axios.get( |
|
|
|
`${this.apiServer}/api/v2/emoji/userEmojis?parentHandleId=${this.record.jwtId}`, |
|
|
|
{ headers: await getHeaders(this.activeDid) }, |
|
|
|
); |
|
|
|
this.userEmojis = response.data; |
|
|
|
} catch (error) { |
|
|
|
logger.error( |
|
|
|
"Error loading all emojis for parent handle id:", |
|
|
|
this.record.jwtId, |
|
|
|
error, |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
async getUserEmojis(): Promise<string[]> { |
|
|
|
if (!this.userEmojis) { |
|
|
|
await this.loadUserEmojis(); |
|
|
|
} |
|
|
|
return this.userEmojis || []; |
|
|
|
} |
|
|
|
|
|
|
|
selectEmoji(emoji: string) { |
|
|
|
this.showEmojiPicker = false; |
|
|
|
this.submitEmoji(emoji); |
|
|
|
} |
|
|
|
|
|
|
|
isUserEmoji(emoji: string): boolean { |
|
|
|
return this.userEmojis?.includes(emoji) || false; |
|
|
|
} |
|
|
|
|
|
|
|
toggleEmoji(emoji: string) { |
|
|
|
if (this.isUserEmoji(emoji)) { |
|
|
|
this.removeEmoji(emoji); |
|
|
|
} else { |
|
|
|
this.submitEmoji(emoji); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
async submitEmoji(emoji: string) { |
|
|
|
try { |
|
|
|
// Temporarily add to user emojis for UI feedback |
|
|
|
if (!this.isUserEmoji(emoji)) { |
|
|
|
this.record.emojiCount[emoji] = 0; |
|
|
|
} |
|
|
|
// Create an Emoji claim and send to the server |
|
|
|
const emojiClaim: GenericVerifiableCredential = { |
|
|
|
"@type": "Emoji", |
|
|
|
text: emoji, |
|
|
|
parentItem: { lastClaimId: this.record.jwtId }, |
|
|
|
}; |
|
|
|
const claim = await createAndSubmitClaim( |
|
|
|
emojiClaim, |
|
|
|
this.record.issuerDid, |
|
|
|
this.apiServer, |
|
|
|
this.axios, |
|
|
|
); |
|
|
|
if ( |
|
|
|
claim.success && |
|
|
|
!(claim.success as { embeddedRecordError?: string }).embeddedRecordError |
|
|
|
) { |
|
|
|
this.record.emojiCount[emoji] = |
|
|
|
(this.record.emojiCount[emoji] || 0) + 1; |
|
|
|
this.userEmojis = [...(this.userEmojis || []), emoji]; |
|
|
|
} else { |
|
|
|
this.notify.error("Failed to add emoji.", TIMEOUTS.STANDARD); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
logger.error("Error submitting emoji:", error); |
|
|
|
this.notify.error("Got error adding emoji.", TIMEOUTS.STANDARD); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
async removeEmoji(emoji: string) { |
|
|
|
try { |
|
|
|
// Create an Emoji claim and send to the server |
|
|
|
const emojiClaim: GenericVerifiableCredential = { |
|
|
|
"@type": "Emoji", |
|
|
|
text: emoji, |
|
|
|
parentItem: { lastClaimId: this.record.jwtId }, |
|
|
|
}; |
|
|
|
const claim = await createAndSubmitClaim( |
|
|
|
emojiClaim, |
|
|
|
this.record.issuerDid, |
|
|
|
this.apiServer, |
|
|
|
this.axios, |
|
|
|
); |
|
|
|
if (claim.success) { |
|
|
|
this.record.emojiCount[emoji] = |
|
|
|
(this.record.emojiCount[emoji] || 0) - 1; |
|
|
|
|
|
|
|
// Update local emoji count for immediate UI feedback |
|
|
|
const newCount = Math.max(0, this.record.emojiCount[emoji]); |
|
|
|
if (newCount === 0) { |
|
|
|
delete this.record.emojiCount[emoji]; |
|
|
|
} else { |
|
|
|
this.record.emojiCount[emoji] = newCount; |
|
|
|
} |
|
|
|
this.userEmojis = this.userEmojis?.filter(e => e !== emoji) || []; |
|
|
|
} else { |
|
|
|
this.notify.error("Failed to remove emoji.", TIMEOUTS.STANDARD); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
logger.error("Error removing emoji:", error); |
|
|
|
this.notify.error("Got error removing emoji.", TIMEOUTS.STANDARD); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
</script> |
|
|
|