|
|
import type { TEmoji } from '@/types' |
|
|
|
|
|
/** Canonical reaction `content` for discussion upvotes (kind 7). */ |
|
|
const DISCUSSION_UPVOTE = '+' |
|
|
/** Canonical reaction `content` for discussion downvotes (kind 7). */ |
|
|
const DISCUSSION_DOWNVOTE = '-' |
|
|
|
|
|
/** Shown in discussion UIs; legacy reaction `content` used the same characters. */ |
|
|
export const DISCUSSION_UPVOTE_DISPLAY = '⬆️' |
|
|
export const DISCUSSION_DOWNVOTE_DISPLAY = '⬇️' |
|
|
|
|
|
function emojiString(emoji: TEmoji | string | undefined | null): string | undefined { |
|
|
if (emoji === undefined || emoji === null) return undefined |
|
|
return typeof emoji === 'string' ? emoji : emoji.shortcode |
|
|
} |
|
|
|
|
|
/** |
|
|
* Relays/clients may use ⬆ (U+2B06) vs ⬆️ (U+2B06 U+FE0F); normalize before comparing. |
|
|
*/ |
|
|
function normalizedVoteToken(s: string): string { |
|
|
return s.normalize('NFC').replace(/\ufe0f/g, '').trim() |
|
|
} |
|
|
|
|
|
const UP_ARROW = '\u2b06' |
|
|
const DOWN_ARROW = '\u2b07' |
|
|
|
|
|
export function isDiscussionUpvoteEmoji(emoji: TEmoji | string | undefined | null): boolean { |
|
|
const s = emojiString(emoji) |
|
|
if (s === undefined) return false |
|
|
const n = normalizedVoteToken(s) |
|
|
return n === DISCUSSION_UPVOTE || n === UP_ARROW |
|
|
} |
|
|
|
|
|
export function isDiscussionDownvoteEmoji(emoji: TEmoji | string | undefined | null): boolean { |
|
|
const s = emojiString(emoji) |
|
|
if (s === undefined) return false |
|
|
const n = normalizedVoteToken(s) |
|
|
return n === DISCUSSION_DOWNVOTE || n === DOWN_ARROW |
|
|
} |
|
|
|
|
|
export function isDiscussionVoteEmoji(emoji: TEmoji | string | undefined | null): boolean { |
|
|
return isDiscussionUpvoteEmoji(emoji) || isDiscussionDownvoteEmoji(emoji) |
|
|
} |
|
|
|
|
|
export const DISCUSSION_VOTE_EMOJIS = [DISCUSSION_UPVOTE, DISCUSSION_DOWNVOTE] as const |
|
|
|
|
|
/** Same vote direction, including legacy ⬆️/⬇️ vs +/-. */ |
|
|
export function discussionVoteMatches( |
|
|
stored: TEmoji | string | undefined | null, |
|
|
clicked: string | TEmoji |
|
|
): boolean { |
|
|
if (stored === undefined || stored === null) return false |
|
|
const clickStr = typeof clicked === 'string' ? clicked : clicked.shortcode |
|
|
const storeStr = typeof stored === 'string' ? stored : stored.shortcode |
|
|
if (storeStr === clickStr) return true |
|
|
return ( |
|
|
(isDiscussionUpvoteEmoji(storeStr) && isDiscussionUpvoteEmoji(clickStr)) || |
|
|
(isDiscussionDownvoteEmoji(storeStr) && isDiscussionDownvoteEmoji(clickStr)) |
|
|
) |
|
|
}
|
|
|
|