Gitea was last updated at 0bb0c74a (v0.0.1). This commit is v0.0.2–v0.0.5:
QDN GIF library, in-app Q-Share updater, compact composer, and the chat/save
fixes that never left the working tree. README/CHANGELOG cover that gap;
HUB_GIF_PICKER.md is the note for Hub to copy the picker.
5.4 KiB
Message to Hub developers — chat GIF picker
This is the note for upstream Qortal Hub (Electron / desktop). Please port the GIF picker from this GO 3.0 fork so Hub and GO send the same chat payload and can display each other's GIFs.
Repo: https://gitea.qortal.link/simon/GO-3.0 (main, GO 3.0 v0.0.5)
Copy this folder as-is: src/components/Chat/gifs/
Do not invent a new GIF protocol. The library already lives on QDN
(qortal://APP/GIFs/). Chat only stores a pointer.
What a selected GIF is
QDN IMAGE resources whose identifier starts with gif-. Selecting one must
attach a pointer, not re-publish the file and not send the bytes over
Reticulum.
{
identifier, // starts with "gif-"
libraryGif: true,
mimeType: "image/gif",
name, // publisher QDN name
service: "IMAGE",
timestamp
}
Helper in this repo: toChatImagePointer() in src/components/Chat/gifs/libraryGifs.ts.
Embed (same as the GIFs Q-App copy button):
qortal://use-embed/IMAGE?name=NAME&identifier=ID&service=IMAGE&mimeType=image%2Fgif×tamp=TS
QDN endpoints (node already used by Hub)
Catalog — one fetch, then filter locally. Do not search QDN on every keystroke.
GET {node}/arbitrary/resources/search
?excludeblocked=true
&identifier=gif-
&includemetadata=true
&limit=100
&mode=ALL
&prefix=true
&reverse=true
&service=IMAGE
Moderation list:
GET {node}/arbitrary/DOCUMENT/GIFs/gifs_moderation
Bytes (queued + cached in gifMediaCache.ts; concurrency 2):
GET {node}/arbitrary/IMAGE/{name}/{identifier}
GET {node}/arbitrary/IMAGE/{name}/{identifier}?async=true // gather
getBaseApiReact() already points at the connected node. Hub already depends
on react-intersection-observer (used by GifImage for in-view lazy load).
Files to copy
src/components/Chat/gifs/libraryGifs.ts
src/components/Chat/gifs/gifMediaCache.ts
src/components/Chat/gifs/gifUsage.ts
src/components/Chat/gifs/GifImage.tsx
src/components/Chat/gifs/ChatGifPicker.tsx
src/components/Chat/gifs/EmojiGifStage.tsx
src/components/Chat/gifs/EmojiGifSwitch.tsx
One desktop tweak
EmojiGifSwitch.tsx imports keepPanelKeyboardDown from
src/components/Mobile/emojiPanelBus.ts (Android soft-keyboard). On Hub,
delete that import and the keepPanelKeyboardDown() call. Keep
onMouseDown / onPointerDown preventDefault so the composer does not
lose focus.
Wire it (desktop path — ReactionPicker, not the mobile keyboard panel)
1. src/components/ReactionPicker.tsx
- Add optional
onGifSelect?: (gif: LibraryGif) => void. - When that callback is passed, wrap
emoji-picker-react+ChatGifPickerinEmojiGifStageand putEmojiGifSwitchunder the stage. - Match this fork's
ReactionPicker.tsx.
2. src/components/Chat/TipTap.tsx
- Add
insertGif?: (gif: LibraryGif) => void. - On the desktop emoji button (the existing
ReactionPicker), passonGifSelect={insertGif}. - Do not port
MobileEmojiPanel,emojiPanelBus,useSoftKeyboardInset, or the Capacitor SoftKeyboard plugin. Those are Android-only.
3. ChatGroup.tsx and ChatDirect.tsx
pendingLibraryGifstate.insertLibraryGif: refuse if another image / attachment is already pending.- On send, prefer the pointer over a file upload:
const images = pendingLibraryGif
? [toChatImagePointer(pendingLibraryGif)]
: existingImagePath;
- Composer preview:
<GifImage eager fill gif={pendingLibraryGif} />.
4. src/components/Chat/MessageItem.tsx
- If
isLibraryGifImage(message.images[0]), render<GifImage fit="contain" gif={…} />. - Skip the normal image-download / Reticulum-resource path for these.
5. src/utils/chat.ts — buildImageEmbedLink
Treat libraryGif === true, identifier prefix gif-, or mimeType === image/gif as image%2Fgif, so copied embeds open as GIFs, not PNG.
6. Optional — ReticulumDiscussionDialog.tsx
Discussions are not the same image-pointer path. This fork fetches the blob
with fetchLibraryGifFile and attaches it as a normal file.
Do not copy (mobile-only)
src/components/Mobile/MobileEmojiPanel.tsxsrc/components/Mobile/emojiPanelBus.ts(unless you want the no-op stub)src/hooks/useSoftKeyboardInset.ts- Capacitor SoftKeyboard / AppUpdate plugins
- Android keyboard / crash-guard bits (
GifLoadCancelledinApp.tsxis nice-to-have only)
Behaviour to keep
- Fetch the catalog once (5 minute TTL). Search and tag chips are client-side
(
filterLibraryGifs,gifTagCounts). gifMediaCache: concurrency 2, memory + IndexedDB, cancel when a tile scrolls off screen, resolve cancelled work with an empty string (do not reject — that became an unhandled rejection on mobile).- Most-used row from
localStorage(gifUsage.ts, keyqortal-gif-usage-v1). - Masonry: three flex columns,
GifImagenatural(no square crop). - Never stampede the node with one fetch per visible tile.
Quick look after cloning this repo
git clone https://gitea.qortal.link/simon/GO-3.0.git
cd GO-3.0
git log --oneline -20
# Picker + send/render wiring vs upstream Hub
git diff origin/develop -- src/components/Chat/gifs src/components/ReactionPicker.tsx src/utils/chat.ts
Voice / dual-ring / presence_bridge.py notes are still in
HUB_DEV_NOTES.md and
MOBILE_VOICE_HUB_DEV_NOTE.md. Those are
separate from the GIF picker.