@streamplace/components 0.7.2 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/chat/chat-box.js +212 -24
- package/dist/components/chat/chat-message.js +5 -5
- package/dist/components/chat/chat.js +83 -5
- package/dist/components/chat/emoji-suggestions.js +35 -0
- package/dist/components/chat/mod-view.js +59 -8
- package/dist/components/chat/system-message.js +19 -0
- package/dist/components/icons/bluesky-icon.js +9 -0
- package/dist/components/keep-awake.js +7 -0
- package/dist/components/keep-awake.native.js +16 -0
- package/dist/components/mobile-player/fullscreen.js +2 -1
- package/dist/components/mobile-player/fullscreen.native.js +3 -3
- package/dist/components/mobile-player/player.js +15 -30
- package/dist/components/mobile-player/ui/index.js +2 -0
- package/dist/components/mobile-player/ui/report-modal.js +90 -0
- package/dist/components/mobile-player/ui/streamer-loading-overlay.js +104 -0
- package/dist/components/mobile-player/ui/viewer-context-menu.js +20 -1
- package/dist/components/mobile-player/ui/viewer-loading-overlay.js +49 -0
- package/dist/components/mobile-player/use-webrtc.js +7 -1
- package/dist/components/mobile-player/video-retry.js +29 -0
- package/dist/components/mobile-player/video.js +84 -9
- package/dist/components/mobile-player/video.native.js +24 -10
- package/dist/components/share/sharesheet.js +91 -0
- package/dist/components/ui/dialog.js +1 -1
- package/dist/components/ui/dropdown.js +6 -6
- package/dist/components/ui/index.js +2 -0
- package/dist/components/ui/primitives/modal.js +0 -1
- package/dist/components/ui/resizeable.js +20 -11
- package/dist/components/ui/slider.js +5 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/usePointerDevice.js +71 -0
- package/dist/index.js +10 -3
- package/dist/lib/system-messages.js +101 -0
- package/dist/livestream-store/chat.js +111 -18
- package/dist/livestream-store/livestream-store.js +3 -0
- package/dist/livestream-store/problems.js +76 -0
- package/dist/livestream-store/websocket-consumer.js +39 -4
- package/dist/player-store/player-store.js +33 -4
- package/dist/streamplace-store/block.js +51 -12
- package/dist/streamplace-store/stream.js +44 -23
- package/dist/ui/index.js +79 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/56540125 +0 -0
- package/node-compile-cache/{v22.15.0-x64-92db9086-0 → v22.15.0-x64-efe9a9df-0}/67b1eb60 +0 -0
- package/node-compile-cache/{v22.15.0-x64-92db9086-0 → v22.15.0-x64-efe9a9df-0}/7c275f90 +0 -0
- package/package.json +6 -2
- package/src/components/chat/chat-box.tsx +295 -25
- package/src/components/chat/chat-message.tsx +6 -7
- package/src/components/chat/chat.tsx +192 -41
- package/src/components/chat/emoji-suggestions.tsx +94 -0
- package/src/components/chat/mod-view.tsx +119 -40
- package/src/components/chat/system-message.tsx +38 -0
- package/src/components/icons/bluesky-icon.tsx +9 -0
- package/src/components/keep-awake.native.tsx +13 -0
- package/src/components/keep-awake.tsx +3 -0
- package/src/components/mobile-player/fullscreen.native.tsx +12 -3
- package/src/components/mobile-player/fullscreen.tsx +10 -3
- package/src/components/mobile-player/player.tsx +28 -36
- package/src/components/mobile-player/props.tsx +1 -0
- package/src/components/mobile-player/ui/index.ts +2 -0
- package/src/components/mobile-player/ui/report-modal.tsx +195 -0
- package/src/components/mobile-player/ui/streamer-loading-overlay.tsx +154 -0
- package/src/components/mobile-player/ui/viewer-context-menu.tsx +31 -3
- package/src/components/mobile-player/ui/viewer-loading-overlay.tsx +66 -0
- package/src/components/mobile-player/use-webrtc.tsx +10 -2
- package/src/components/mobile-player/video-retry.tsx +28 -0
- package/src/components/mobile-player/video.native.tsx +24 -10
- package/src/components/mobile-player/video.tsx +100 -21
- package/src/components/share/sharesheet.tsx +185 -0
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/dropdown.tsx +13 -13
- package/src/components/ui/index.ts +2 -0
- package/src/components/ui/primitives/modal.tsx +0 -1
- package/src/components/ui/resizeable.tsx +26 -15
- package/src/components/ui/slider.tsx +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/usePointerDevice.ts +89 -0
- package/src/index.tsx +11 -2
- package/src/lib/system-messages.ts +135 -0
- package/src/livestream-store/chat.tsx +145 -17
- package/src/livestream-store/livestream-state.tsx +10 -0
- package/src/livestream-store/livestream-store.tsx +3 -0
- package/src/livestream-store/problems.tsx +96 -0
- package/src/livestream-store/websocket-consumer.tsx +44 -4
- package/src/player-store/player-state.tsx +25 -4
- package/src/player-store/player-store.tsx +43 -5
- package/src/streamplace-store/block.tsx +55 -13
- package/src/streamplace-store/stream.tsx +66 -35
- package/src/ui/index.ts +86 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/node-compile-cache/v22.15.0-x64-92db9086-0/37be0eec +0 -0
- package/node-compile-cache/v22.15.0-x64-92db9086-0/56540125 +0 -0
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @streamplace/components/ui - Streamplace ZeroCSS
|
|
4
|
+
*
|
|
5
|
+
* Clean export path for ZeroCSS styling utilities, design tokens, and atomic styles.
|
|
6
|
+
* ZeroCSS provides a zero-config, atomic styling system optimized for React Native.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.atomsNS = exports.theme = exports.useTheme = exports.usePlatformTypography = exports.lightTheme = exports.darkTheme = exports.createThemedStyles = exports.createThemeStyles = exports.createThemeIcons = exports.createThemeColors = exports.ThemeProvider = exports.responsiveValue = exports.platformStyle = exports.mergeStyles = exports.debounce = exports.typography = exports.spacing = exports.shadows = exports.colors = exports.breakpoints = exports.borderRadius = exports.w = exports.top = exports.text = exports.right = exports.r = exports.py = exports.px = exports.pt = exports.pr = exports.position = exports.pl = exports.pb = exports.p = exports.my = exports.mx = exports.mt = exports.mr = exports.ml = exports.mb = exports.m = exports.left = exports.layout = exports.h = exports.gap = exports.flex = exports.bottom = exports.borders = exports.bg = exports.atoms = void 0;
|
|
10
|
+
exports.utils = exports.tokens = void 0;
|
|
11
|
+
const tslib_1 = require("tslib");
|
|
12
|
+
// Export the most commonly used ZeroCSS utilities
|
|
13
|
+
var atoms_1 = require("../lib/theme/atoms");
|
|
14
|
+
// Core atoms object
|
|
15
|
+
Object.defineProperty(exports, "atoms", { enumerable: true, get: function () { return atoms_1.atoms; } });
|
|
16
|
+
// Common shorthand utilities
|
|
17
|
+
Object.defineProperty(exports, "bg", { enumerable: true, get: function () { return atoms_1.bg; } });
|
|
18
|
+
// Border utilities
|
|
19
|
+
Object.defineProperty(exports, "borders", { enumerable: true, get: function () { return atoms_1.borders; } });
|
|
20
|
+
Object.defineProperty(exports, "bottom", { enumerable: true, get: function () { return atoms_1.bottom; } });
|
|
21
|
+
// Flex utilities
|
|
22
|
+
Object.defineProperty(exports, "flex", { enumerable: true, get: function () { return atoms_1.flex; } });
|
|
23
|
+
// Gap utilities (React Native 0.71+)
|
|
24
|
+
Object.defineProperty(exports, "gap", { enumerable: true, get: function () { return atoms_1.gap; } });
|
|
25
|
+
Object.defineProperty(exports, "h", { enumerable: true, get: function () { return atoms_1.h; } });
|
|
26
|
+
// Layout utilities
|
|
27
|
+
Object.defineProperty(exports, "layout", { enumerable: true, get: function () { return atoms_1.layout; } });
|
|
28
|
+
Object.defineProperty(exports, "left", { enumerable: true, get: function () { return atoms_1.left; } });
|
|
29
|
+
Object.defineProperty(exports, "m", { enumerable: true, get: function () { return atoms_1.m; } });
|
|
30
|
+
Object.defineProperty(exports, "mb", { enumerable: true, get: function () { return atoms_1.mb; } });
|
|
31
|
+
Object.defineProperty(exports, "ml", { enumerable: true, get: function () { return atoms_1.ml; } });
|
|
32
|
+
Object.defineProperty(exports, "mr", { enumerable: true, get: function () { return atoms_1.mr; } });
|
|
33
|
+
Object.defineProperty(exports, "mt", { enumerable: true, get: function () { return atoms_1.mt; } });
|
|
34
|
+
Object.defineProperty(exports, "mx", { enumerable: true, get: function () { return atoms_1.mx; } });
|
|
35
|
+
Object.defineProperty(exports, "my", { enumerable: true, get: function () { return atoms_1.my; } });
|
|
36
|
+
Object.defineProperty(exports, "p", { enumerable: true, get: function () { return atoms_1.p; } });
|
|
37
|
+
Object.defineProperty(exports, "pb", { enumerable: true, get: function () { return atoms_1.pb; } });
|
|
38
|
+
Object.defineProperty(exports, "pl", { enumerable: true, get: function () { return atoms_1.pl; } });
|
|
39
|
+
// Position utilities
|
|
40
|
+
Object.defineProperty(exports, "position", { enumerable: true, get: function () { return atoms_1.position; } });
|
|
41
|
+
Object.defineProperty(exports, "pr", { enumerable: true, get: function () { return atoms_1.pr; } });
|
|
42
|
+
Object.defineProperty(exports, "pt", { enumerable: true, get: function () { return atoms_1.pt; } });
|
|
43
|
+
Object.defineProperty(exports, "px", { enumerable: true, get: function () { return atoms_1.px; } });
|
|
44
|
+
Object.defineProperty(exports, "py", { enumerable: true, get: function () { return atoms_1.py; } });
|
|
45
|
+
Object.defineProperty(exports, "r", { enumerable: true, get: function () { return atoms_1.r; } });
|
|
46
|
+
Object.defineProperty(exports, "right", { enumerable: true, get: function () { return atoms_1.right; } });
|
|
47
|
+
Object.defineProperty(exports, "text", { enumerable: true, get: function () { return atoms_1.text; } });
|
|
48
|
+
Object.defineProperty(exports, "top", { enumerable: true, get: function () { return atoms_1.top; } });
|
|
49
|
+
Object.defineProperty(exports, "w", { enumerable: true, get: function () { return atoms_1.w; } });
|
|
50
|
+
// Export ZeroCSS design tokens
|
|
51
|
+
var tokens_1 = require("../lib/theme/tokens");
|
|
52
|
+
Object.defineProperty(exports, "borderRadius", { enumerable: true, get: function () { return tokens_1.borderRadius; } });
|
|
53
|
+
Object.defineProperty(exports, "breakpoints", { enumerable: true, get: function () { return tokens_1.breakpoints; } });
|
|
54
|
+
Object.defineProperty(exports, "colors", { enumerable: true, get: function () { return tokens_1.colors; } });
|
|
55
|
+
Object.defineProperty(exports, "shadows", { enumerable: true, get: function () { return tokens_1.shadows; } });
|
|
56
|
+
Object.defineProperty(exports, "spacing", { enumerable: true, get: function () { return tokens_1.spacing; } });
|
|
57
|
+
Object.defineProperty(exports, "typography", { enumerable: true, get: function () { return tokens_1.typography; } });
|
|
58
|
+
// Export ZeroCSS utility functions
|
|
59
|
+
var utils_1 = require("../lib/utils");
|
|
60
|
+
Object.defineProperty(exports, "debounce", { enumerable: true, get: function () { return utils_1.debounce; } });
|
|
61
|
+
Object.defineProperty(exports, "mergeStyles", { enumerable: true, get: function () { return utils_1.mergeStyles; } });
|
|
62
|
+
Object.defineProperty(exports, "platformStyle", { enumerable: true, get: function () { return utils_1.platformStyle; } });
|
|
63
|
+
Object.defineProperty(exports, "responsiveValue", { enumerable: true, get: function () { return utils_1.responsiveValue; } });
|
|
64
|
+
// Export ZeroCSS theme system
|
|
65
|
+
var theme_1 = require("../lib/theme/theme");
|
|
66
|
+
Object.defineProperty(exports, "ThemeProvider", { enumerable: true, get: function () { return theme_1.ThemeProvider; } });
|
|
67
|
+
Object.defineProperty(exports, "createThemeColors", { enumerable: true, get: function () { return theme_1.createThemeColors; } });
|
|
68
|
+
Object.defineProperty(exports, "createThemeIcons", { enumerable: true, get: function () { return theme_1.createThemeIcons; } });
|
|
69
|
+
Object.defineProperty(exports, "createThemeStyles", { enumerable: true, get: function () { return theme_1.createThemeStyles; } });
|
|
70
|
+
Object.defineProperty(exports, "createThemedStyles", { enumerable: true, get: function () { return theme_1.createThemedStyles; } });
|
|
71
|
+
Object.defineProperty(exports, "darkTheme", { enumerable: true, get: function () { return theme_1.darkTheme; } });
|
|
72
|
+
Object.defineProperty(exports, "lightTheme", { enumerable: true, get: function () { return theme_1.lightTheme; } });
|
|
73
|
+
Object.defineProperty(exports, "usePlatformTypography", { enumerable: true, get: function () { return theme_1.usePlatformTypography; } });
|
|
74
|
+
Object.defineProperty(exports, "useTheme", { enumerable: true, get: function () { return theme_1.useTheme; } });
|
|
75
|
+
// Namespace exports for power users
|
|
76
|
+
exports.theme = tslib_1.__importStar(require("../lib/theme"));
|
|
77
|
+
exports.atomsNS = tslib_1.__importStar(require("../lib/theme/atoms"));
|
|
78
|
+
exports.tokens = tslib_1.__importStar(require("../lib/theme/tokens"));
|
|
79
|
+
exports.utils = tslib_1.__importStar(require("../lib/utils"));
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamplace/components",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7",
|
|
4
4
|
"description": "Streamplace React (Native) Components",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "src/index.tsx",
|
|
@@ -28,11 +28,14 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@atproto/api": "^0.15.7",
|
|
30
30
|
"@atproto/crypto": "^0.4.4",
|
|
31
|
+
"@emoji-mart/react": "^1.1.1",
|
|
31
32
|
"@gorhom/bottom-sheet": "^5.1.6",
|
|
32
33
|
"@react-navigation/native": "^6.1.18",
|
|
33
34
|
"@rn-primitives/dropdown-menu": "^1.2.0",
|
|
34
35
|
"@rn-primitives/portal": "^1.3.0",
|
|
36
|
+
"@rn-primitives/slider": "^1.2.0",
|
|
35
37
|
"class-variance-authority": "^0.6.1",
|
|
38
|
+
"expo-keep-awake": "~14.1.4",
|
|
36
39
|
"expo-video": "~2.2.1",
|
|
37
40
|
"hls.js": "^1.5.17",
|
|
38
41
|
"lucide-react-native": "^0.514.0",
|
|
@@ -41,6 +44,7 @@
|
|
|
41
44
|
"react-native-gesture-handler": "~2.26.0",
|
|
42
45
|
"react-native-reanimated": "~3.18.0",
|
|
43
46
|
"react-native-safe-area-context": "5.4.1",
|
|
47
|
+
"react-native-svg": "15.12.0",
|
|
44
48
|
"react-native-webrtc": "git+https://github.com/streamplace/react-native-webrtc.git#6b8472a771ac47f89217d327058a8a4124a6ae56",
|
|
45
49
|
"react-use-websocket": "^4.13.0",
|
|
46
50
|
"streamplace": "0.7.2",
|
|
@@ -50,5 +54,5 @@
|
|
|
50
54
|
"peerDependencies": {
|
|
51
55
|
"react": "*"
|
|
52
56
|
},
|
|
53
|
-
"gitHead": "
|
|
57
|
+
"gitHead": "8c2cad31c840efeda8aa7f797f91dddbedb95397"
|
|
54
58
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Picker from "@emoji-mart/react";
|
|
2
|
+
import { AtSignIcon, ExternalLink, X } from "lucide-react-native";
|
|
2
3
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
-
import { Pressable, TextInput } from "react-native";
|
|
4
|
+
import { Platform, Pressable, TextInput } from "react-native";
|
|
4
5
|
import { ChatMessageViewHydrated } from "streamplace";
|
|
5
6
|
import {
|
|
6
7
|
Button,
|
|
@@ -8,30 +9,60 @@ import {
|
|
|
8
9
|
Text,
|
|
9
10
|
useChat,
|
|
10
11
|
useCreateChatMessage,
|
|
12
|
+
useLivestream,
|
|
11
13
|
useReplyToMessage,
|
|
12
14
|
useSetReplyToMessage,
|
|
13
15
|
View,
|
|
14
16
|
} from "../../";
|
|
15
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
bg,
|
|
19
|
+
flex,
|
|
20
|
+
gap,
|
|
21
|
+
h,
|
|
22
|
+
layout,
|
|
23
|
+
mb,
|
|
24
|
+
mr,
|
|
25
|
+
pl,
|
|
26
|
+
pr,
|
|
27
|
+
py,
|
|
28
|
+
w,
|
|
29
|
+
} from "../../lib/theme/atoms";
|
|
16
30
|
import { usePDSAgent } from "../../streamplace-store/xrpc";
|
|
17
31
|
import { Textarea } from "../ui/textarea";
|
|
18
32
|
import { RenderChatMessage } from "./chat-message";
|
|
33
|
+
import { EmojiData, EmojiSuggestions } from "./emoji-suggestions";
|
|
19
34
|
import { MentionSuggestions } from "./mention-suggestions";
|
|
20
35
|
|
|
36
|
+
const COOL_EMOJI_LIST = [
|
|
37
|
+
..."😀🥸😍😘😁🥸😆🥸😜🥸😂😅🥸🙂🤫😱🥸🤣😗😄🥸😎🤓😲😯😰🥸😥🥸😣🥸😞😓🥸😩😩🥸😤🥱",
|
|
38
|
+
];
|
|
39
|
+
|
|
21
40
|
export function ChatBox({
|
|
22
41
|
isPopout,
|
|
23
42
|
chatBoxStyle,
|
|
43
|
+
emojiData,
|
|
44
|
+
setIsChatVisible,
|
|
24
45
|
}: {
|
|
25
46
|
isPopout?: boolean;
|
|
26
47
|
chatBoxStyle?: any;
|
|
48
|
+
emojiData: EmojiData;
|
|
49
|
+
setIsChatVisible?: (visible: boolean) => void;
|
|
27
50
|
}) {
|
|
28
51
|
const [submitting, setSubmitting] = useState(false);
|
|
29
52
|
const [message, setMessage] = useState("");
|
|
30
53
|
const [showSuggestions, setShowSuggestions] = useState(false);
|
|
54
|
+
const [showEmojiSuggestions, setShowEmojiSuggestions] = useState(false);
|
|
55
|
+
const [showEmojiSelector, setShowEmojiSelector] = useState(false);
|
|
56
|
+
const [emojiIconIndex, setEmojiIconIndex] = useState(
|
|
57
|
+
Math.floor(Math.random() * COOL_EMOJI_LIST.length),
|
|
58
|
+
);
|
|
31
59
|
const [highlightedIndex, setHighlightedIndex] = useState(0);
|
|
32
60
|
const [filteredAuthors, setFilteredAuthors] = useState<Map<string, any>>(
|
|
33
61
|
new Map(),
|
|
34
62
|
);
|
|
63
|
+
const [filteredEmojis, setFilteredEmojis] = useState<any[]>([]);
|
|
64
|
+
|
|
65
|
+
let linfo = useLivestream();
|
|
35
66
|
|
|
36
67
|
const chat = useChat();
|
|
37
68
|
const createChatMessage = useCreateChatMessage();
|
|
@@ -63,25 +94,145 @@ export function ChatBox({
|
|
|
63
94
|
setShowSuggestions(false);
|
|
64
95
|
};
|
|
65
96
|
|
|
97
|
+
const handleEmojiSelect = (emoji: any) => {
|
|
98
|
+
const beforeColon = message.slice(0, message.lastIndexOf(":"));
|
|
99
|
+
setMessage(`${beforeColon}${emoji.skins[0]?.native} `);
|
|
100
|
+
setShowEmojiSuggestions(false);
|
|
101
|
+
};
|
|
102
|
+
|
|
66
103
|
const updateSuggestions = (text: string) => {
|
|
104
|
+
// Handle mentions
|
|
67
105
|
const atIndex = text.lastIndexOf("@");
|
|
68
|
-
if (atIndex
|
|
106
|
+
if (atIndex !== -1 && authors) {
|
|
107
|
+
const searchText = text.slice(atIndex + 1).toLowerCase();
|
|
108
|
+
const filteredAuthorsMap = new Map(
|
|
109
|
+
Array.from(authors.entries()).filter(([handle]) =>
|
|
110
|
+
handle.toLowerCase().includes(searchText),
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
setFilteredAuthors(filteredAuthorsMap);
|
|
114
|
+
setHighlightedIndex(0);
|
|
115
|
+
setShowSuggestions(filteredAuthorsMap.size > 0);
|
|
116
|
+
setShowEmojiSuggestions(false);
|
|
117
|
+
} else {
|
|
69
118
|
setShowSuggestions(false);
|
|
70
|
-
return;
|
|
71
119
|
}
|
|
72
120
|
|
|
73
|
-
const
|
|
121
|
+
const colonIndex = text.lastIndexOf(":");
|
|
122
|
+
if (colonIndex !== -1) {
|
|
123
|
+
const searchText = text.slice(colonIndex + 1).toLowerCase();
|
|
124
|
+
if (searchText.length > 0) {
|
|
125
|
+
const aliasMatches = Object.entries(emojiData.aliases)
|
|
126
|
+
.map(([alias, emojiId]) => {
|
|
127
|
+
const aliasLower = alias.toLowerCase();
|
|
128
|
+
if (aliasLower === searchText) {
|
|
129
|
+
return { emojiId, alias, matchType: 0, index: 0 };
|
|
130
|
+
} else if (aliasLower.startsWith(searchText)) {
|
|
131
|
+
return { emojiId, alias, matchType: 1, index: 0 };
|
|
132
|
+
} else if (aliasLower.includes(searchText)) {
|
|
133
|
+
return {
|
|
134
|
+
emojiId,
|
|
135
|
+
alias,
|
|
136
|
+
matchType: 2,
|
|
137
|
+
index: aliasLower.indexOf(searchText),
|
|
138
|
+
}; // includes
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
})
|
|
142
|
+
.filter(Boolean);
|
|
143
|
+
|
|
144
|
+
// Map emojiId to best alias match info
|
|
145
|
+
const bestAliasMatch: Record<
|
|
146
|
+
string,
|
|
147
|
+
{ matchType: number; index: number; alias: string }
|
|
148
|
+
> = {};
|
|
149
|
+
for (const match of aliasMatches) {
|
|
150
|
+
if (!match) continue;
|
|
151
|
+
const prev = bestAliasMatch[match.emojiId];
|
|
152
|
+
if (
|
|
153
|
+
!prev ||
|
|
154
|
+
match?.matchType < prev.matchType ||
|
|
155
|
+
(match.matchType === prev.matchType && match.index < prev.index)
|
|
156
|
+
) {
|
|
157
|
+
bestAliasMatch[match.emojiId] = match;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
74
160
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
161
|
+
// Collect all matching emojis by id, name, keywords, or alias
|
|
162
|
+
const allEmojis = Object.values(emojiData.emojis);
|
|
163
|
+
const filtered = allEmojis
|
|
164
|
+
.map((emoji: any) => {
|
|
165
|
+
// Check alias match
|
|
166
|
+
const aliasMatch = bestAliasMatch[emoji.id];
|
|
167
|
+
if (aliasMatch) {
|
|
168
|
+
return {
|
|
169
|
+
emoji,
|
|
170
|
+
sort: [aliasMatch.matchType, aliasMatch.index, 0],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// Check id, name, keywords
|
|
174
|
+
if (emoji.id.toLowerCase() === searchText) {
|
|
175
|
+
return { emoji, sort: [3, 0, 0] }; // exact id
|
|
176
|
+
}
|
|
177
|
+
if (emoji.id.toLowerCase().startsWith(searchText)) {
|
|
178
|
+
return { emoji, sort: [4, 0, 0] }; // startsWith id
|
|
179
|
+
}
|
|
180
|
+
if (emoji.id.toLowerCase().includes(searchText)) {
|
|
181
|
+
return {
|
|
182
|
+
emoji,
|
|
183
|
+
sort: [5, emoji.id.toLowerCase().indexOf(searchText), 0],
|
|
184
|
+
}; // includes id
|
|
185
|
+
}
|
|
186
|
+
if (emoji.name.toLowerCase().includes(searchText)) {
|
|
187
|
+
return {
|
|
188
|
+
emoji,
|
|
189
|
+
sort: [6, emoji.name.toLowerCase().indexOf(searchText), 0],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if (
|
|
193
|
+
emoji.keywords &&
|
|
194
|
+
emoji.keywords.some((keyword: string) =>
|
|
195
|
+
keyword.toLowerCase().includes(searchText),
|
|
196
|
+
)
|
|
197
|
+
) {
|
|
198
|
+
return { emoji, sort: [7, 0, 0] };
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
})
|
|
202
|
+
.filter(Boolean)
|
|
203
|
+
// Remove duplicates by emoji id (keep best match)
|
|
204
|
+
.reduce((acc: any[], curr: any) => {
|
|
205
|
+
if (!acc.find((e) => e.emoji.id === curr.emoji.id)) {
|
|
206
|
+
acc.push(curr);
|
|
207
|
+
}
|
|
208
|
+
return acc;
|
|
209
|
+
}, [])
|
|
210
|
+
// Sort by alias match type, then position, then fallback
|
|
211
|
+
.sort((a, b) => {
|
|
212
|
+
for (let i = 0; i < a.sort.length; ++i) {
|
|
213
|
+
if (a.sort[i] !== b.sort[i]) return a.sort[i] - b.sort[i];
|
|
214
|
+
}
|
|
215
|
+
return 0;
|
|
216
|
+
})
|
|
217
|
+
.slice(0, 10) // Limit to 10 results
|
|
218
|
+
.map((entry) => entry.emoji);
|
|
80
219
|
|
|
81
|
-
|
|
220
|
+
setFilteredEmojis(filtered);
|
|
221
|
+
setHighlightedIndex(0);
|
|
222
|
+
setShowEmojiSuggestions(filtered.length > 0);
|
|
223
|
+
setShowSuggestions(false);
|
|
224
|
+
} else {
|
|
225
|
+
setShowEmojiSuggestions(false);
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
setShowEmojiSuggestions(false);
|
|
229
|
+
}
|
|
82
230
|
|
|
83
|
-
|
|
84
|
-
|
|
231
|
+
// If neither mention nor emoji, hide all suggestions
|
|
232
|
+
if (atIndex === -1 && colonIndex === -1) {
|
|
233
|
+
setShowSuggestions(false);
|
|
234
|
+
setShowEmojiSuggestions(false);
|
|
235
|
+
}
|
|
85
236
|
};
|
|
86
237
|
|
|
87
238
|
const submit = () => {
|
|
@@ -110,10 +261,11 @@ export function ChatBox({
|
|
|
110
261
|
layout.flex.row,
|
|
111
262
|
layout.flex.alignCenter,
|
|
112
263
|
layout.flex.spaceBetween,
|
|
113
|
-
h[12],
|
|
114
264
|
pl[2],
|
|
115
|
-
pr[
|
|
265
|
+
pr[6],
|
|
266
|
+
mr[6],
|
|
116
267
|
mb[2],
|
|
268
|
+
py[1],
|
|
117
269
|
bg.gray[800],
|
|
118
270
|
{ borderRadius: 16 },
|
|
119
271
|
]}
|
|
@@ -140,6 +292,35 @@ export function ChatBox({
|
|
|
140
292
|
</Pressable>
|
|
141
293
|
</View>
|
|
142
294
|
)}
|
|
295
|
+
{showEmojiSelector && (
|
|
296
|
+
<>
|
|
297
|
+
{/* Overlay to catch outside clicks */}
|
|
298
|
+
<Pressable
|
|
299
|
+
style={{
|
|
300
|
+
position: "absolute",
|
|
301
|
+
top: 0,
|
|
302
|
+
left: 0,
|
|
303
|
+
right: 0,
|
|
304
|
+
bottom: 0,
|
|
305
|
+
zIndex: 200,
|
|
306
|
+
}}
|
|
307
|
+
onPress={() => setShowEmojiSelector(false)}
|
|
308
|
+
/>
|
|
309
|
+
<View
|
|
310
|
+
style={{
|
|
311
|
+
position: "absolute",
|
|
312
|
+
bottom: "100%",
|
|
313
|
+
left: 0,
|
|
314
|
+
zIndex: 2001,
|
|
315
|
+
}}
|
|
316
|
+
>
|
|
317
|
+
<Picker
|
|
318
|
+
data={emojiData}
|
|
319
|
+
onEmojiSelect={(e) => setMessage(message + e.native)}
|
|
320
|
+
/>
|
|
321
|
+
</View>
|
|
322
|
+
</>
|
|
323
|
+
)}
|
|
143
324
|
<View style={[layout.flex.row, layout.flex.alignCenter, gap.all[2]]}>
|
|
144
325
|
<Textarea
|
|
145
326
|
ref={textAreaRef}
|
|
@@ -160,16 +341,40 @@ export function ChatBox({
|
|
|
160
341
|
if (handles.length > 0) {
|
|
161
342
|
handleMentionSelect(handles[highlightedIndex]);
|
|
162
343
|
}
|
|
163
|
-
} else
|
|
344
|
+
} else if (showEmojiSuggestions) {
|
|
345
|
+
k.preventDefault();
|
|
346
|
+
if (filteredEmojis.length > 0) {
|
|
347
|
+
handleEmojiSelect(filteredEmojis[highlightedIndex]);
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
submit();
|
|
351
|
+
}
|
|
164
352
|
} else if (k.nativeEvent.key === "ArrowUp") {
|
|
165
|
-
|
|
353
|
+
if (showSuggestions || showEmojiSuggestions) {
|
|
354
|
+
k.preventDefault();
|
|
355
|
+
setHighlightedIndex((prev) => Math.max(prev - 1, 0));
|
|
356
|
+
}
|
|
166
357
|
} else if (k.nativeEvent.key === "ArrowDown") {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
358
|
+
if (showSuggestions) {
|
|
359
|
+
k.preventDefault();
|
|
360
|
+
setHighlightedIndex((prev) =>
|
|
361
|
+
Math.min(
|
|
362
|
+
prev + 1,
|
|
363
|
+
Array.from(filteredAuthors.keys()).length - 1,
|
|
364
|
+
),
|
|
365
|
+
);
|
|
366
|
+
} else if (showEmojiSuggestions) {
|
|
367
|
+
k.preventDefault();
|
|
368
|
+
setHighlightedIndex((prev) =>
|
|
369
|
+
Math.min(prev + 1, filteredEmojis.length - 1),
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
} else if (k.nativeEvent.key === "Escape") {
|
|
373
|
+
if (showSuggestions || showEmojiSuggestions) {
|
|
374
|
+
k.preventDefault();
|
|
375
|
+
setShowSuggestions(false);
|
|
376
|
+
setShowEmojiSuggestions(false);
|
|
377
|
+
}
|
|
173
378
|
}
|
|
174
379
|
}}
|
|
175
380
|
style={[chatBoxStyle]}
|
|
@@ -185,11 +390,76 @@ export function ChatBox({
|
|
|
185
390
|
</View>
|
|
186
391
|
{showSuggestions && (
|
|
187
392
|
<MentionSuggestions
|
|
188
|
-
authors={filteredAuthors ||
|
|
393
|
+
authors={filteredAuthors || new Map()}
|
|
189
394
|
highlightedIndex={highlightedIndex}
|
|
190
395
|
onSelect={handleMentionSelect}
|
|
191
396
|
/>
|
|
192
397
|
)}
|
|
398
|
+
{showEmojiSuggestions && (
|
|
399
|
+
<EmojiSuggestions
|
|
400
|
+
emojis={filteredEmojis}
|
|
401
|
+
highlightedIndex={highlightedIndex}
|
|
402
|
+
onSelect={handleEmojiSelect}
|
|
403
|
+
/>
|
|
404
|
+
)}
|
|
405
|
+
{Platform.OS === "web" && (
|
|
406
|
+
<View
|
|
407
|
+
style={[
|
|
408
|
+
layout.flex.row,
|
|
409
|
+
mb[2],
|
|
410
|
+
gap.all[2],
|
|
411
|
+
{ justifyContent: "flex-end" },
|
|
412
|
+
]}
|
|
413
|
+
>
|
|
414
|
+
<Button
|
|
415
|
+
variant="secondary"
|
|
416
|
+
style={{ borderRadius: 16, height: 36, maxWidth: 36 }}
|
|
417
|
+
onPress={() => {
|
|
418
|
+
// if the last character is not @, add it
|
|
419
|
+
!message.endsWith("@") && setMessage(message + "@");
|
|
420
|
+
// get all the text after the last @
|
|
421
|
+
const atIndex = message.lastIndexOf("@");
|
|
422
|
+
const searchText = message.slice(atIndex + 1).toLowerCase();
|
|
423
|
+
updateSuggestions(searchText);
|
|
424
|
+
setShowSuggestions(true);
|
|
425
|
+
// focus the textarea
|
|
426
|
+
textAreaRef.current?.focus();
|
|
427
|
+
}}
|
|
428
|
+
>
|
|
429
|
+
<AtSignIcon size={20} color="white" />
|
|
430
|
+
</Button>
|
|
431
|
+
<Pressable
|
|
432
|
+
onHoverOut={() => {
|
|
433
|
+
setEmojiIconIndex(
|
|
434
|
+
Math.floor(Math.random() * COOL_EMOJI_LIST.length),
|
|
435
|
+
);
|
|
436
|
+
}}
|
|
437
|
+
>
|
|
438
|
+
<Button
|
|
439
|
+
variant="secondary"
|
|
440
|
+
style={{ borderRadius: 16, height: 36, maxWidth: 36 }}
|
|
441
|
+
onPress={() => setShowEmojiSelector(!showEmojiSelector)}
|
|
442
|
+
>
|
|
443
|
+
<Text>{COOL_EMOJI_LIST[emojiIconIndex]}</Text>
|
|
444
|
+
</Button>
|
|
445
|
+
</Pressable>
|
|
446
|
+
{!isPopout && (
|
|
447
|
+
<Button
|
|
448
|
+
variant="secondary"
|
|
449
|
+
style={{ borderRadius: 16, height: 36, maxWidth: 36 }}
|
|
450
|
+
onPress={() => {
|
|
451
|
+
if (!linfo) return;
|
|
452
|
+
const u = new URL(window.location.href);
|
|
453
|
+
u.pathname = `/chat-popout/${linfo?.author?.did}`;
|
|
454
|
+
window.open(u.toString(), "_blank", "popup=true");
|
|
455
|
+
setIsChatVisible?.(false);
|
|
456
|
+
}}
|
|
457
|
+
>
|
|
458
|
+
<ExternalLink size={16} />
|
|
459
|
+
</Button>
|
|
460
|
+
)}
|
|
461
|
+
</View>
|
|
462
|
+
)}
|
|
193
463
|
</View>
|
|
194
464
|
);
|
|
195
465
|
}
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
pl,
|
|
18
18
|
w,
|
|
19
19
|
} from "../../lib/theme/atoms";
|
|
20
|
-
import { atoms, layout } from "../ui";
|
|
20
|
+
import { atoms, colors, layout } from "../ui";
|
|
21
21
|
|
|
22
22
|
interface Facet {
|
|
23
23
|
index: {
|
|
@@ -35,7 +35,7 @@ import { useLivestreamStore } from "../../livestream-store";
|
|
|
35
35
|
import { Text } from "../ui/text";
|
|
36
36
|
|
|
37
37
|
const getRgbColor = (color?: { red: number; green: number; blue: number }) =>
|
|
38
|
-
color ? `rgb(${color.red}, ${color.green}, ${color.blue})` :
|
|
38
|
+
color ? `rgb(${color.red}, ${color.green}, ${color.blue})` : colors.gray[500];
|
|
39
39
|
|
|
40
40
|
const segmentedObject = (
|
|
41
41
|
obj: RichtextSegment,
|
|
@@ -59,7 +59,6 @@ const segmentedObject = (
|
|
|
59
59
|
} else if (ftr.$type === "app.bsky.richtext.facet#mention") {
|
|
60
60
|
let mtnftr = ftr as $Typed<Mention>;
|
|
61
61
|
const profile = userCache?.[mtnftr.did];
|
|
62
|
-
console.log(profile, mtnftr.did, userCache);
|
|
63
62
|
return (
|
|
64
63
|
<Text
|
|
65
64
|
key={`mention-${index}`}
|
|
@@ -115,7 +114,6 @@ export const RenderChatMessage = memo(
|
|
|
115
114
|
hour12: false,
|
|
116
115
|
});
|
|
117
116
|
}, []);
|
|
118
|
-
|
|
119
117
|
return (
|
|
120
118
|
<>
|
|
121
119
|
{item.replyTo && showReply && (
|
|
@@ -142,7 +140,7 @@ export const RenderChatMessage = memo(
|
|
|
142
140
|
</Text>{" "}
|
|
143
141
|
<Text
|
|
144
142
|
style={{
|
|
145
|
-
color:
|
|
143
|
+
color: colors.gray[300],
|
|
146
144
|
fontStyle: "italic",
|
|
147
145
|
}}
|
|
148
146
|
>
|
|
@@ -156,7 +154,7 @@ export const RenderChatMessage = memo(
|
|
|
156
154
|
<Text
|
|
157
155
|
style={{
|
|
158
156
|
fontVariant: ["tabular-nums"],
|
|
159
|
-
color:
|
|
157
|
+
color: colors.gray[400],
|
|
160
158
|
}}
|
|
161
159
|
>
|
|
162
160
|
{formatTime(item.record.createdAt)}
|
|
@@ -186,7 +184,8 @@ export const RenderChatMessage = memo(
|
|
|
186
184
|
(prevProps, nextProps) => {
|
|
187
185
|
return (
|
|
188
186
|
prevProps.item.author.handle === nextProps.item.author.handle &&
|
|
189
|
-
prevProps.item.record.text === nextProps.item.record.text
|
|
187
|
+
prevProps.item.record.text === nextProps.item.record.text &&
|
|
188
|
+
prevProps.item.uri === nextProps.item.uri
|
|
190
189
|
);
|
|
191
190
|
},
|
|
192
191
|
);
|