mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-02 15:03:01 +02:00
Add draft reactions + gifs; region resolution
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Parsing for rich-chat payloads sent by MeshCore Open clients as ordinary
|
||||
* plaintext mesh messages.
|
||||
*
|
||||
* MeshCore Open encodes some rich features into the message body with a short
|
||||
* prefix. RemoteTerm recognizes two of them for display:
|
||||
*
|
||||
* g:<gifId> Giphy GIF -> https://media.giphy.com/media/<id>/giphy.gif
|
||||
* r:<hash>:<index> Emoji reaction -> <index> picks an emoji from a fixed list
|
||||
*
|
||||
* Formats and the emoji table are ported verbatim from meshcore-open:
|
||||
* lib/helpers/gif_helper.dart
|
||||
* lib/helpers/reaction_helper.dart
|
||||
* lib/widgets/emoji_picker.dart
|
||||
* (github.com/zjs81/meshcore-open, dev branch).
|
||||
*
|
||||
* Reaction support here is intentionally "generic display only": we decode the
|
||||
* emoji from <index> and show it, but we do NOT resolve <hash> back to the
|
||||
* target message (that requires porting Dart's String.hashCode). See issue #291.
|
||||
*/
|
||||
|
||||
// --- Emoji table (order must match meshcore-open exactly for index compat) ---
|
||||
|
||||
const QUICK_EMOJIS = ['👍', '❤️', '😂', '🎉', '👏', '🔥'];
|
||||
|
||||
// prettier-ignore
|
||||
const SMILEYS = [
|
||||
'😀', '😃', '😄', '😁', '😅', '😂', '🤣', '😊', '😇', '🙂',
|
||||
'🙃', '😉', '😌', '😍', '🥰', '😘', '😗', '😙', '😚', '😋',
|
||||
'😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🥸', '🤩',
|
||||
'🥳', '😏', '😒', '😞', '😔', '😟', '😕', '🙁', '😣', '😖',
|
||||
'😫', '😩', '🥺', '😢', '😭', '😤', '😠', '😡', '🤬', '🤯',
|
||||
'😳', '🥵', '🥶', '😱', '😨', '😰', '😥', '😓', '🤗', '🤔',
|
||||
'🤭', '🤫', '🤥', '😶',
|
||||
];
|
||||
|
||||
// prettier-ignore
|
||||
const GESTURES = [
|
||||
'👍', '👎', '👊', '✊', '🤛', '🤜', '🤞', '✌️', '🤟', '🤘',
|
||||
'👌', '🤌', '🤏', '👈', '👉', '👆', '👇', '☝️', '👋', '🤚',
|
||||
'🖐️', '✋', '🖖', '👏', '🙌', '👐', '🤲', '🤝', '🙏', '✍️',
|
||||
'💅', '🤳', '💪',
|
||||
];
|
||||
|
||||
// prettier-ignore
|
||||
const HEARTS = [
|
||||
'❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔',
|
||||
'❤️🔥', '❤️🩹', '💕', '💞', '💓', '💗', '💖', '💘', '💝', '💟',
|
||||
'💌', '💢', '💥', '💫', '💦', '💨', '🕳️', '💬', '👁️🗨️', '🗨️',
|
||||
'🗯️', '💭',
|
||||
];
|
||||
|
||||
// prettier-ignore
|
||||
const OBJECTS = [
|
||||
'🎉', '🎊', '🎈', '🎁', '🎀', '🪅', '🪆', '🏆', '🥇', '🥈',
|
||||
'🥉', '⚽', '⚾', '🥎', '🏀', '🏐', '🏈', '🏉', '🎾', '🥏',
|
||||
'🎳', '🏏', '🏑', '🏒', '🥍', '🏓', '🏸', '🥊', '🥋', '🥅',
|
||||
'⛳', '🔥', '⭐', '🌟', '✨', '⚡', '💡', '🔦', '🏮', '🪔',
|
||||
'📱', '💻', '⌚', '📷', '📺', '📻', '🎵', '🎶', '🚀',
|
||||
];
|
||||
|
||||
/** Combined reaction emoji list, in the fixed index order used on the wire. */
|
||||
export const REACTION_EMOJIS: readonly string[] = [
|
||||
...QUICK_EMOJIS,
|
||||
...SMILEYS,
|
||||
...GESTURES,
|
||||
...HEARTS,
|
||||
...OBJECTS,
|
||||
];
|
||||
|
||||
// --- GIF (g:<gifId>) ---
|
||||
|
||||
const GIF_PATTERN = /^g:([A-Za-z0-9_-]+)$/;
|
||||
|
||||
/**
|
||||
* Parse a MeshCore Open GIF payload. Returns the Giphy GIF id, or null if the
|
||||
* (trimmed) text is not a `g:<id>` payload.
|
||||
*/
|
||||
export function parseGif(text: string): string | null {
|
||||
const match = GIF_PATTERN.exec(text.trim());
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/** Build the Giphy media URL for a GIF id. */
|
||||
export function giphyUrlForId(gifId: string): string {
|
||||
return `https://media.giphy.com/media/${gifId}/giphy.gif`;
|
||||
}
|
||||
|
||||
// --- Reaction (r:<hash>:<index>) ---
|
||||
|
||||
const REACTION_PATTERN = /^r:([0-9a-f]{4}):([0-9a-f]{2})$/;
|
||||
|
||||
export interface ParsedReaction {
|
||||
/** The decoded reaction emoji. */
|
||||
emoji: string;
|
||||
/** 4-hex hash identifying the target message (not resolved here). */
|
||||
targetHash: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a MeshCore Open reaction payload. Returns the decoded emoji and the
|
||||
* (unresolved) target-message hash, or null if the (trimmed) text is not a
|
||||
* valid `r:<hash>:<index>` payload or the index is out of range.
|
||||
*/
|
||||
export function parseReaction(text: string): ParsedReaction | null {
|
||||
const match = REACTION_PATTERN.exec(text.trim());
|
||||
if (!match) return null;
|
||||
const index = parseInt(match[2], 16);
|
||||
if (!Number.isInteger(index) || index < 0 || index >= REACTION_EMOJIS.length) {
|
||||
return null;
|
||||
}
|
||||
return { emoji: REACTION_EMOJIS[index], targetHash: match[1] };
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Browser-local preference for rendering MeshCore Open rich-chat payloads
|
||||
// (Giphy GIFs and emoji reactions) instead of their raw encoded text. This is
|
||||
// a pure display tweak, stored per-browser in localStorage. GIF rendering
|
||||
// fetches images from media.giphy.com, so it is off by default.
|
||||
|
||||
export const RENDER_RICH_PAYLOADS_KEY = 'remoteterm-render-rich-payloads';
|
||||
|
||||
export function getSavedRenderRichPayloads(): boolean {
|
||||
try {
|
||||
return localStorage.getItem(RENDER_RICH_PAYLOADS_KEY) === 'true';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function setSavedRenderRichPayloads(enabled: boolean): void {
|
||||
try {
|
||||
if (enabled) {
|
||||
localStorage.setItem(RENDER_RICH_PAYLOADS_KEY, 'true');
|
||||
} else {
|
||||
localStorage.removeItem(RENDER_RICH_PAYLOADS_KEY);
|
||||
}
|
||||
} catch {
|
||||
// localStorage may be unavailable
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user