@droppii-org/chat-mobile 0.2.106 → 0.2.107
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/lib/module/components/messages/ExpandableMessageText.js +112 -0
- package/lib/module/components/messages/ExpandableMessageText.js.map +1 -0
- package/lib/module/components/messages/PreviewScrollableText.js +80 -0
- package/lib/module/components/messages/PreviewScrollableText.js.map +1 -0
- package/lib/module/components/messages/linkMessage/index.js +21 -12
- package/lib/module/components/messages/linkMessage/index.js.map +1 -1
- package/lib/module/components/messages/quotedMessage/index.js +12 -2
- package/lib/module/components/messages/quotedMessage/index.js.map +1 -1
- package/lib/module/components/messages/textMessage/index.js +7 -1
- package/lib/module/components/messages/textMessage/index.js.map +1 -1
- package/lib/module/components/messages/types.js.map +1 -1
- package/lib/module/hooks/useExpandableText.js +120 -0
- package/lib/module/hooks/useExpandableText.js.map +1 -0
- package/lib/module/screens/chat-detail/ChatDetail.js +11 -0
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/PinnedMessagesSheet.js +7 -0
- package/lib/module/screens/chat-detail/PinnedMessagesSheet.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +10 -3
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +11 -2
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -1
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js +79 -6
- package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js.map +1 -1
- package/lib/module/screens/chat-detail/messageToolbox/useClonePreview.js +30 -0
- package/lib/module/screens/chat-detail/messageToolbox/useClonePreview.js.map +1 -0
- package/lib/module/store/expandedMessage.js +28 -0
- package/lib/module/store/expandedMessage.js.map +1 -0
- package/lib/module/utils/expandableText.js +41 -0
- package/lib/module/utils/expandableText.js.map +1 -0
- package/lib/module/utils/inlineTruncation.js +51 -0
- package/lib/module/utils/inlineTruncation.js.map +1 -0
- package/lib/module/utils/truncateTextChildren.js +38 -0
- package/lib/module/utils/truncateTextChildren.js.map +1 -0
- package/lib/typescript/src/components/messages/ExpandableMessageText.d.ts +25 -0
- package/lib/typescript/src/components/messages/ExpandableMessageText.d.ts.map +1 -0
- package/lib/typescript/src/components/messages/PreviewScrollableText.d.ts +29 -0
- package/lib/typescript/src/components/messages/PreviewScrollableText.d.ts.map +1 -0
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts +1 -1
- package/lib/typescript/src/components/messages/linkMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/quotedMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/textMessage/index.d.ts +1 -1
- package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
- package/lib/typescript/src/components/messages/types.d.ts +18 -0
- package/lib/typescript/src/components/messages/types.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useExpandableText.d.ts +13 -0
- package/lib/typescript/src/hooks/useExpandableText.d.ts.map +1 -0
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/PinnedMessagesSheet.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +11 -1
- package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.d.ts +14 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/messageToolbox/useClonePreview.d.ts +18 -0
- package/lib/typescript/src/screens/chat-detail/messageToolbox/useClonePreview.d.ts.map +1 -0
- package/lib/typescript/src/store/expandedMessage.d.ts +15 -0
- package/lib/typescript/src/store/expandedMessage.d.ts.map +1 -0
- package/lib/typescript/src/utils/expandableText.d.ts +18 -0
- package/lib/typescript/src/utils/expandableText.d.ts.map +1 -0
- package/lib/typescript/src/utils/inlineTruncation.d.ts +24 -0
- package/lib/typescript/src/utils/inlineTruncation.d.ts.map +1 -0
- package/lib/typescript/src/utils/truncateTextChildren.d.ts +3 -0
- package/lib/typescript/src/utils/truncateTextChildren.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/messages/ExpandableMessageText.tsx +145 -0
- package/src/components/messages/PreviewScrollableText.tsx +98 -0
- package/src/components/messages/linkMessage/index.tsx +30 -21
- package/src/components/messages/quotedMessage/index.tsx +14 -2
- package/src/components/messages/textMessage/index.tsx +11 -2
- package/src/components/messages/types.ts +18 -0
- package/src/hooks/useExpandableText.ts +139 -0
- package/src/screens/chat-detail/ChatDetail.tsx +11 -0
- package/src/screens/chat-detail/PinnedMessagesSheet.tsx +8 -0
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +22 -2
- package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +20 -1
- package/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.tsx +111 -5
- package/src/screens/chat-detail/messageToolbox/useClonePreview.ts +34 -0
- package/src/store/expandedMessage.ts +42 -0
- package/src/utils/expandableText.ts +51 -0
- package/src/utils/inlineTruncation.ts +65 -0
- package/src/utils/truncateTextChildren.ts +46 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cut-point arithmetic for the inline `... Xem thêm` preview. Everything here
|
|
5
|
+
* works on the lines RN reports from `onTextLayout`, whose `text` includes the
|
|
6
|
+
* line's trailing break — so joining them reproduces the rendered string and a
|
|
7
|
+
* running length is a valid character offset into it.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// How far back a shrink may look for a word boundary before accepting a
|
|
11
|
+
// mid-word cut. One long unbroken token (a URL) must still be cuttable.
|
|
12
|
+
const WORD_SNAP_WINDOW = 20;
|
|
13
|
+
const isSpace = char => !!char && /\s/.test(char);
|
|
14
|
+
const isHighSurrogate = code => code >= 0xd800 && code <= 0xdbff;
|
|
15
|
+
export const joinLines = lines => lines.map(line => line.text).join('');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Backs `cut` off trailing whitespace — a kept `\n` would push the suffix onto
|
|
19
|
+
* a line of its own — and off half an emoji, which would render as a tofu box.
|
|
20
|
+
*/
|
|
21
|
+
export const settleCut = (full, cut) => {
|
|
22
|
+
let next = Math.max(0, Math.min(cut, full.length));
|
|
23
|
+
if (next > 0 && isHighSurrogate(full.charCodeAt(next - 1))) next -= 1;
|
|
24
|
+
while (next > 0 && isSpace(full[next - 1])) next -= 1;
|
|
25
|
+
return next;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** First guess: everything on the first `maxLines` lines. */
|
|
29
|
+
export const initialCut = (lines, maxLines) => {
|
|
30
|
+
const full = joinLines(lines);
|
|
31
|
+
return settleCut(full, joinLines(lines.slice(0, maxLines)).length);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The suffix did not fit and wrapped past `maxLines`. Drop at least as many
|
|
36
|
+
* characters as spilled over — the spill includes the suffix itself, so this
|
|
37
|
+
* converges in one or two passes — then prefer ending on a word.
|
|
38
|
+
*/
|
|
39
|
+
export const shrinkCut = (full, cut, fittedLines, maxLines) => {
|
|
40
|
+
const overflow = joinLines(fittedLines.slice(maxLines)).length;
|
|
41
|
+
let next = Math.max(0, cut - Math.max(overflow, 1));
|
|
42
|
+
const floor = Math.max(0, next - WORD_SNAP_WINDOW);
|
|
43
|
+
for (let i = next; i > floor; i -= 1) {
|
|
44
|
+
if (isSpace(full[i - 1])) {
|
|
45
|
+
next = i;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return settleCut(full, next);
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=inlineTruncation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["WORD_SNAP_WINDOW","isSpace","char","test","isHighSurrogate","code","joinLines","lines","map","line","text","join","settleCut","full","cut","next","Math","max","min","length","charCodeAt","initialCut","maxLines","slice","shrinkCut","fittedLines","overflow","floor","i"],"sourceRoot":"../../../src","sources":["utils/inlineTruncation.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;;AAKA;AACA;AACA,MAAMA,gBAAgB,GAAG,EAAE;AAE3B,MAAMC,OAAO,GAAIC,IAAwB,IAAK,CAAC,CAACA,IAAI,IAAI,IAAI,CAACC,IAAI,CAACD,IAAI,CAAC;AAEvE,MAAME,eAAe,GAAIC,IAAY,IAAKA,IAAI,IAAI,MAAM,IAAIA,IAAI,IAAI,MAAM;AAE1E,OAAO,MAAMC,SAAS,GAAIC,KAA8B,IACtDA,KAAK,CAACC,GAAG,CAAEC,IAAI,IAAKA,IAAI,CAACC,IAAI,CAAC,CAACC,IAAI,CAAC,EAAE,CAAC;;AAEzC;AACA;AACA;AACA;AACA,OAAO,MAAMC,SAAS,GAAGA,CAACC,IAAY,EAAEC,GAAW,KAAK;EACtD,IAAIC,IAAI,GAAGC,IAAI,CAACC,GAAG,CAAC,CAAC,EAAED,IAAI,CAACE,GAAG,CAACJ,GAAG,EAAED,IAAI,CAACM,MAAM,CAAC,CAAC;EAClD,IAAIJ,IAAI,GAAG,CAAC,IAAIX,eAAe,CAACS,IAAI,CAACO,UAAU,CAACL,IAAI,GAAG,CAAC,CAAC,CAAC,EAAEA,IAAI,IAAI,CAAC;EACrE,OAAOA,IAAI,GAAG,CAAC,IAAId,OAAO,CAACY,IAAI,CAACE,IAAI,GAAG,CAAC,CAAC,CAAC,EAAEA,IAAI,IAAI,CAAC;EACrD,OAAOA,IAAI;AACb,CAAC;;AAED;AACA,OAAO,MAAMM,UAAU,GAAGA,CACxBd,KAA8B,EAC9Be,QAAgB,KACb;EACH,MAAMT,IAAI,GAAGP,SAAS,CAACC,KAAK,CAAC;EAC7B,OAAOK,SAAS,CAACC,IAAI,EAAEP,SAAS,CAACC,KAAK,CAACgB,KAAK,CAAC,CAAC,EAAED,QAAQ,CAAC,CAAC,CAACH,MAAM,CAAC;AACpE,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMK,SAAS,GAAGA,CACvBX,IAAY,EACZC,GAAW,EACXW,WAAoC,EACpCH,QAAgB,KACb;EACH,MAAMI,QAAQ,GAAGpB,SAAS,CAACmB,WAAW,CAACF,KAAK,CAACD,QAAQ,CAAC,CAAC,CAACH,MAAM;EAC9D,IAAIJ,IAAI,GAAGC,IAAI,CAACC,GAAG,CAAC,CAAC,EAAEH,GAAG,GAAGE,IAAI,CAACC,GAAG,CAACS,QAAQ,EAAE,CAAC,CAAC,CAAC;EAEnD,MAAMC,KAAK,GAAGX,IAAI,CAACC,GAAG,CAAC,CAAC,EAAEF,IAAI,GAAGf,gBAAgB,CAAC;EAClD,KAAK,IAAI4B,CAAC,GAAGb,IAAI,EAAEa,CAAC,GAAGD,KAAK,EAAEC,CAAC,IAAI,CAAC,EAAE;IACpC,IAAI3B,OAAO,CAACY,IAAI,CAACe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;MACxBb,IAAI,GAAGa,CAAC;MACR;IACF;EACF;EAEA,OAAOhB,SAAS,CAACC,IAAI,EAAEE,IAAI,CAAC;AAC9B,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { cloneElement, isValidElement } from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* Walks a `<Text>` children tree — plain strings, mention runs, linkified
|
|
6
|
+
* runs, nested `<Text>` — and keeps the first `budget` characters of rendered
|
|
7
|
+
* text, preserving every element's styling and press handler up to the cut.
|
|
8
|
+
* Character counts line up with `onTextLayout` because RN renders exactly the
|
|
9
|
+
* concatenation of the string leaves.
|
|
10
|
+
*/
|
|
11
|
+
const take = (node, budget) => {
|
|
12
|
+
if (budget <= 0 || node == null || typeof node === 'boolean') {
|
|
13
|
+
return [null, 0];
|
|
14
|
+
}
|
|
15
|
+
if (typeof node === 'string' || typeof node === 'number') {
|
|
16
|
+
const text = String(node);
|
|
17
|
+
return text.length <= budget ? [node, text.length] : [text.slice(0, budget), budget];
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(node)) {
|
|
20
|
+
const kept = [];
|
|
21
|
+
let used = 0;
|
|
22
|
+
for (const child of node) {
|
|
23
|
+
if (used >= budget) break;
|
|
24
|
+
const [part, count] = take(child, budget - used);
|
|
25
|
+
kept.push(part);
|
|
26
|
+
used += count;
|
|
27
|
+
}
|
|
28
|
+
return [kept, used];
|
|
29
|
+
}
|
|
30
|
+
if (/*#__PURE__*/isValidElement(node)) {
|
|
31
|
+
if (node.props.children === undefined) return [node, 0];
|
|
32
|
+
const [children, used] = take(node.props.children, budget);
|
|
33
|
+
return [/*#__PURE__*/cloneElement(node, undefined, children), used];
|
|
34
|
+
}
|
|
35
|
+
return [node, 0];
|
|
36
|
+
};
|
|
37
|
+
export const truncateTextChildren = (node, limit) => take(node, limit)[0];
|
|
38
|
+
//# sourceMappingURL=truncateTextChildren.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["cloneElement","isValidElement","take","node","budget","text","String","length","slice","Array","isArray","kept","used","child","part","count","push","props","children","undefined","truncateTextChildren","limit"],"sourceRoot":"../../../src","sources":["utils/truncateTextChildren.ts"],"mappings":";;AAAA,SAASA,YAAY,EAAEC,cAAc,QAAwB,OAAO;AAIpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMC,IAAI,GAAGA,CAACC,IAAe,EAAEC,MAAc,KAAY;EACvD,IAAIA,MAAM,IAAI,CAAC,IAAID,IAAI,IAAI,IAAI,IAAI,OAAOA,IAAI,KAAK,SAAS,EAAE;IAC5D,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;EAClB;EAEA,IAAI,OAAOA,IAAI,KAAK,QAAQ,IAAI,OAAOA,IAAI,KAAK,QAAQ,EAAE;IACxD,MAAME,IAAI,GAAGC,MAAM,CAACH,IAAI,CAAC;IACzB,OAAOE,IAAI,CAACE,MAAM,IAAIH,MAAM,GACxB,CAACD,IAAI,EAAEE,IAAI,CAACE,MAAM,CAAC,GACnB,CAACF,IAAI,CAACG,KAAK,CAAC,CAAC,EAAEJ,MAAM,CAAC,EAAEA,MAAM,CAAC;EACrC;EAEA,IAAIK,KAAK,CAACC,OAAO,CAACP,IAAI,CAAC,EAAE;IACvB,MAAMQ,IAAiB,GAAG,EAAE;IAC5B,IAAIC,IAAI,GAAG,CAAC;IACZ,KAAK,MAAMC,KAAK,IAAIV,IAAI,EAAE;MACxB,IAAIS,IAAI,IAAIR,MAAM,EAAE;MACpB,MAAM,CAACU,IAAI,EAAEC,KAAK,CAAC,GAAGb,IAAI,CAACW,KAAK,EAAET,MAAM,GAAGQ,IAAI,CAAC;MAChDD,IAAI,CAACK,IAAI,CAACF,IAAI,CAAC;MACfF,IAAI,IAAIG,KAAK;IACf;IACA,OAAO,CAACJ,IAAI,EAAEC,IAAI,CAAC;EACrB;EAEA,iBAAIX,cAAc,CAA2BE,IAAI,CAAC,EAAE;IAClD,IAAIA,IAAI,CAACc,KAAK,CAACC,QAAQ,KAAKC,SAAS,EAAE,OAAO,CAAChB,IAAI,EAAE,CAAC,CAAC;IACvD,MAAM,CAACe,QAAQ,EAAEN,IAAI,CAAC,GAAGV,IAAI,CAACC,IAAI,CAACc,KAAK,CAACC,QAAQ,EAAEd,MAAM,CAAC;IAC1D,OAAO,cAACJ,YAAY,CAACG,IAAI,EAAEgB,SAAS,EAAED,QAAQ,CAAC,EAAEN,IAAI,CAAC;EACxD;EAEA,OAAO,CAACT,IAAI,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,OAAO,MAAMiB,oBAAoB,GAAGA,CAACjB,IAAe,EAAEkB,KAAa,KACjEnB,IAAI,CAACC,IAAI,EAAEkB,KAAK,CAAC,CAAC,CAAC,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
|
+
import { KLabel } from '@droppii/libs';
|
|
3
|
+
import type { MessageSurface } from '../../store/expandedMessage';
|
|
4
|
+
type TypoType = ComponentProps<typeof KLabel.Text>['typo'];
|
|
5
|
+
interface ExpandableMessageTextProps {
|
|
6
|
+
messageId: string;
|
|
7
|
+
surface: MessageSurface;
|
|
8
|
+
/** Raw body text — drives the length/newline pre-check only. */
|
|
9
|
+
text: string;
|
|
10
|
+
/**
|
|
11
|
+
* The already-rendered body. Taken as children rather than re-derived from
|
|
12
|
+
* `text` because the three call sites build structurally different bodies
|
|
13
|
+
* (plain run, mention segments, linkified runs) and re-tokenising them here
|
|
14
|
+
* would duplicate three parsers.
|
|
15
|
+
*/
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
typo?: TypoType;
|
|
18
|
+
color?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const ExpandableMessageText: {
|
|
21
|
+
({ messageId, surface, text, children, typo, color, }: ExpandableMessageTextProps): import("react").JSX.Element;
|
|
22
|
+
displayName: string;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=ExpandableMessageText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpandableMessageText.d.ts","sourceRoot":"","sources":["../../../../../src/components/messages/ExpandableMessageText.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvD,OAAO,EAAuB,MAAM,EAAE,MAAM,eAAe,CAAC;AAQ5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,KAAK,QAAQ,GAAG,cAAc,CAAC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;AAe3D,UAAU,0BAA0B;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,cAAc,CAAC;IACxB,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,qBAAqB;2DAO/B,0BAA0B;;CA0E5B,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Set by the toolbox overlay around its clone. `maxHeight` is the space the
|
|
4
|
+
* clone may take; `contentHeight` is how tall the clone currently is.
|
|
5
|
+
* `onScrollableChange` reports whether the text is capped (and so scrolls),
|
|
6
|
+
* because the overlay's tap-to-dismiss would otherwise block that scroll.
|
|
7
|
+
*/
|
|
8
|
+
export interface ClonePreviewBudget {
|
|
9
|
+
maxHeight: number;
|
|
10
|
+
contentHeight: number;
|
|
11
|
+
onScrollableChange: (scrollable: boolean) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare const ClonePreviewBudgetContext: import("react").Context<ClonePreviewBudget>;
|
|
14
|
+
/**
|
|
15
|
+
* An expanded message can be taller than the overlay. Scrolling the whole
|
|
16
|
+
* clone clips the bubble along a straight edge — square corners — so inside
|
|
17
|
+
* the overlay the text region scrolls instead and the bubble keeps its shape.
|
|
18
|
+
*
|
|
19
|
+
* Budget: whatever the rest of the clone (padding, quote strip, link card,
|
|
20
|
+
* time row) does not use. `contentHeight` and this box's height arrive from
|
|
21
|
+
* native at different moments, and mixing a fresh one with a stale one makes
|
|
22
|
+
* the cap swing between two sizes indefinitely. So the rest is read only from
|
|
23
|
+
* a consistent pair — while uncapped, when the box equals the natural height —
|
|
24
|
+
* and frozen once a cap is applied, which breaks the feedback loop.
|
|
25
|
+
*/
|
|
26
|
+
export declare const PreviewScrollableText: ({ children, }: {
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
}) => import("react").JSX.Element;
|
|
29
|
+
//# sourceMappingURL=PreviewScrollableText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PreviewScrollableText.d.ts","sourceRoot":"","sources":["../../../../../src/components/messages/PreviewScrollableText.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;CACnD;AAED,eAAO,MAAM,yBAAyB,6CACU,CAAC;AAKjD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,GAAI,eAEnC;IACD,QAAQ,EAAE,SAAS,CAAC;CACrB,gCAqDA,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type BaseLegendMessageProps } from '../types';
|
|
2
|
-
export declare const LinkMessage: import("react").MemoExoticComponent<({ message, isOutgoing, createdAtTime, onPressUrl, isHighlighted, isTimeRevealed, groupPosition, }: BaseLegendMessageProps & {
|
|
2
|
+
export declare const LinkMessage: import("react").MemoExoticComponent<({ message, messageId, surface, isOutgoing, createdAtTime, onPressUrl, isHighlighted, isTimeRevealed, groupPosition, }: BaseLegendMessageProps & {
|
|
3
3
|
onPressUrl?: (url: string) => void;
|
|
4
4
|
}) => import("react").JSX.Element>;
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/components/messages/linkMessage/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAmC1E,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/components/messages/linkMessage/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAmC1E,eAAO,MAAM,WAAW,8JAWnB,sBAAsB,GAAG;IAAE,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,iCAuDnE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/components/messages/quotedMessage/index.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/components/messages/quotedMessage/index.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAyB1E,UAAU,kBAAmB,SAAQ,sBAAsB;IACzD,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,QAAA,MAAM,aAAa,8CAAgB,kBAAkB,iCA6JnD,CAAC;AAIH,eAAe,aAAa,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type BaseLegendMessageProps } from '../types';
|
|
2
|
-
export declare const TextMessage: import("react").MemoExoticComponent<({ message, isOutgoing, createdAtTime, isHighlighted, isTimeRevealed, onPressUrl, groupPosition, }: BaseLegendMessageProps & {
|
|
2
|
+
export declare const TextMessage: import("react").MemoExoticComponent<({ message, messageId, surface, isOutgoing, createdAtTime, isHighlighted, isTimeRevealed, onPressUrl, groupPosition, }: BaseLegendMessageProps & {
|
|
3
3
|
onPressUrl?: (url: string) => void;
|
|
4
4
|
}) => import("react").JSX.Element>;
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/components/messages/textMessage/index.tsx"],"names":[],"mappings":"AASA,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/components/messages/textMessage/index.tsx"],"names":[],"mappings":"AASA,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAmB1E,eAAO,MAAM,WAAW,8JAWnB,sBAAsB,GAAG;IAAE,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,iCAmInE,CAAC"}
|
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
import type { MediaItem } from '../../hooks/useMediaViewer';
|
|
2
2
|
import type { MessageItem } from '@droppii/openim-rn-client-sdk';
|
|
3
3
|
import type { GroupPosition } from '../../utils/legendListMessage';
|
|
4
|
+
import type { MessageSurface } from '../../store/expandedMessage';
|
|
4
5
|
export interface BaseLegendMessageProps {
|
|
5
6
|
message: MessageItem;
|
|
7
|
+
/**
|
|
8
|
+
* Resolved once by the caller and threaded down — never re-derived here.
|
|
9
|
+
* The timeline, the pin sheet and the toolbox overlay each compute a
|
|
10
|
+
* message's id differently, and a mismatch silently splits its expansion
|
|
11
|
+
* state across surfaces.
|
|
12
|
+
*/
|
|
13
|
+
messageId: string;
|
|
14
|
+
/** Expansion is scoped per surface (BR-003): the same message collapsed in
|
|
15
|
+
* Pin Management while expanded in the timeline, and vice versa. */
|
|
16
|
+
surface: MessageSurface;
|
|
17
|
+
/**
|
|
18
|
+
* Rendered as the toolbox overlay's clone. The clone is touchable so that
|
|
19
|
+
* `Xem thêm` works there (D1), which makes every other press target in the
|
|
20
|
+
* bubble live too — components with a self-contained handler must disable
|
|
21
|
+
* it when this is set.
|
|
22
|
+
*/
|
|
23
|
+
isPreview?: boolean;
|
|
6
24
|
isOutgoing: boolean;
|
|
7
25
|
createdAtTime: number;
|
|
8
26
|
isHighlighted?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/components/messages/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/components/messages/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,WAAW,CAAC;IACrB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;wEACoE;IACpE,OAAO,EAAE,cAAc,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wEAAwE;IACxE,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7D;AAED,eAAO,MAAM,iBAAiB,GAAI,WAAW,MAAM,WAWlD,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type TextLayoutEvent } from 'react-native';
|
|
2
|
+
import { COLLAPSED_LINES, type MessageSurface } from '../utils/expandableText';
|
|
3
|
+
export { COLLAPSED_LINES };
|
|
4
|
+
export declare const clearExpandableTextCache: () => void;
|
|
5
|
+
export type ExpandableTextMode = 'plain' | 'measuring' | 'fitting' | 'collapsed';
|
|
6
|
+
export declare function useExpandableText(messageId: string, text: string, surface: MessageSurface): {
|
|
7
|
+
mode: ExpandableTextMode;
|
|
8
|
+
cut: number;
|
|
9
|
+
expand: () => void;
|
|
10
|
+
onMeasureTextLayout: ({ nativeEvent: { lines } }: TextLayoutEvent) => void;
|
|
11
|
+
onFitTextLayout: ({ nativeEvent: { lines } }: TextLayoutEvent) => void;
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=useExpandableText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useExpandableText.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useExpandableText.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,EACL,eAAe,EAGf,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,eAAe,EAAE,CAAC;AAiB3B,eAAO,MAAM,wBAAwB,YAA6B,CAAC;AAsBnE,MAAM,MAAM,kBAAkB,GAC1B,OAAO,GACP,WAAW,GACX,SAAS,GACT,WAAW,CAAC;AAEhB,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,cAAc;;;;sDAwCQ,eAAe;kDAgBf,eAAe;EAuB/C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatDetail.d.ts","sourceRoot":"","sources":["../../../../../src/screens/chat-detail/ChatDetail.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ChatDetail.d.ts","sourceRoot":"","sources":["../../../../../src/screens/chat-detail/ChatDetail.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AA2V/C,QAAA,MAAM,YAAY,GAAI,OAAO,eAAe,gCAS3C,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PinnedMessagesSheet.d.ts","sourceRoot":"","sources":["../../../../../src/screens/chat-detail/PinnedMessagesSheet.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"PinnedMessagesSheet.d.ts","sourceRoot":"","sources":["../../../../../src/screens/chat-detail/PinnedMessagesSheet.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,+BAA+B,CAAC;AA0L9E,MAAM,WAAW,6BAA6B;IAC5C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,wBAAwB,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,SAAS,CAAC;IAC5D,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CACjD;AAED,eAAO,MAAM,uBAAuB,GAAI,4EAKrC,6BAA6B,SAyB/B,CAAC"}
|
|
@@ -2,6 +2,7 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
import type { MessageItem } from '@droppii/openim-rn-client-sdk';
|
|
3
3
|
import type { MediaItem } from '../../../hooks/useMediaViewer';
|
|
4
4
|
import type { GroupPosition } from '../../../utils/legendListMessage';
|
|
5
|
+
import type { MessageSurface } from '../../../store/expandedMessage';
|
|
5
6
|
interface LegendChatMessageProps {
|
|
6
7
|
message: MessageItem;
|
|
7
8
|
/** Stable per-row id (see getMessageRenderId) — bound into onPress/onLongPress locally so those callbacks stay referentially stable across unrelated list re-renders. */
|
|
@@ -38,7 +39,16 @@ interface LegendChatMessageProps {
|
|
|
38
39
|
showSenderGutter?: boolean;
|
|
39
40
|
/** Position within its same-sender cluster — drives the bubble tail. */
|
|
40
41
|
groupPosition?: GroupPosition;
|
|
42
|
+
/**
|
|
43
|
+
* Which surface is rendering this message. Expansion state is scoped by it
|
|
44
|
+
* (BR-003), so Pin Management must pass `'pin'` explicitly — the default
|
|
45
|
+
* keeps the timeline and the toolbox overlay sharing one scope, which is
|
|
46
|
+
* what makes the overlay clone mirror the row it lifted from.
|
|
47
|
+
*/
|
|
48
|
+
surface?: MessageSurface;
|
|
49
|
+
/** Toolbox overlay clone — see `BaseLegendMessageProps.isPreview`. */
|
|
50
|
+
isPreview?: boolean;
|
|
41
51
|
}
|
|
42
|
-
export declare const LegendChatMessage: import("react").MemoExoticComponent<({ message, messageId, isOutgoing, createdAtTime, onMediaPress, onPressUrl, onRetry, onPress, alwaysHandlePress, onLongPress, customMessageItemFactory, isHighlighted, isTimeRevealed, showName, showAvatar, showSenderGutter, groupPosition, }: LegendChatMessageProps) => string | number | bigint | true | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode>> | import("react").JSX.Element>;
|
|
52
|
+
export declare const LegendChatMessage: import("react").MemoExoticComponent<({ message, messageId, isOutgoing, createdAtTime, onMediaPress, onPressUrl, onRetry, onPress, alwaysHandlePress, onLongPress, customMessageItemFactory, isHighlighted, isTimeRevealed, showName, showAvatar, showSenderGutter, groupPosition, surface, isPreview, }: LegendChatMessageProps) => string | number | bigint | true | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode>> | import("react").JSX.Element>;
|
|
43
53
|
export {};
|
|
44
54
|
//# sourceMappingURL=LegendChatMessage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LegendChatMessage.d.ts","sourceRoot":"","sources":["../../../../../../src/screens/chat-detail/legend/LegendChatMessage.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAoC,SAAS,EAAE,MAAM,OAAO,CAAC;AAEzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAGjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAe/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"LegendChatMessage.d.ts","sourceRoot":"","sources":["../../../../../../src/screens/chat-detail/legend/LegendChatMessage.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAoC,SAAS,EAAE,MAAM,OAAO,CAAC;AAEzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAGjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAe/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AASrE,UAAU,sBAAsB;IAC9B,OAAO,EAAE,WAAW,CAAC;IACrB,yKAAyK;IACzK,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACzC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAChE,wBAAwB,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,KAAK,SAAS,CAAC;IACzD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,wEAAwE;IACxE,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAsCD,eAAO,MAAM,iBAAiB,2SAqBzB,sBAAsB,wRAsH1B,CAAC"}
|
package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageToolboxOverlay.d.ts","sourceRoot":"","sources":["../../../../../../src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAEV,iBAAiB,EAElB,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAG3D,OAAO,
|
|
1
|
+
{"version":3,"file":"MessageToolboxOverlay.d.ts","sourceRoot":"","sources":["../../../../../../src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAEV,iBAAiB,EAElB,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAG3D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,+BAA+B,CAAC;AAqBvC,UAAU,0BAA0B;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wBAAwB,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,SAAS,CAAC;IAC5D,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC9C,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAIhD,MAAM,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;CAC/B;AAED,eAAO,MAAM,qBAAqB,0HAiOjC,CAAC"}
|
|
@@ -3,9 +3,22 @@ type MessageToolboxWrapperProps = {
|
|
|
3
3
|
isOutgoing?: boolean;
|
|
4
4
|
rect: MeasuredRect;
|
|
5
5
|
message?: React.ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* Lets touches reach the clone's own press targets — needed for `Xem thêm`
|
|
8
|
+
* (D1). Off by default: types like FileMessage and consumer custom nodes
|
|
9
|
+
* have self-contained handlers that must stay inert here.
|
|
10
|
+
*/
|
|
11
|
+
isCloneInteractive?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Tapping the bubble used to fall through to the backdrop and dismiss. The
|
|
14
|
+
* ScrollView below is hit-testable and swallows that, so the gesture is
|
|
15
|
+
* reinstated explicitly. A touch that a press target inside the clone
|
|
16
|
+
* claims (`Xem thêm`) never reaches it.
|
|
17
|
+
*/
|
|
18
|
+
onDismiss?: () => void;
|
|
6
19
|
reaction?: React.ReactNode;
|
|
7
20
|
toolbox?: React.ReactNode;
|
|
8
21
|
};
|
|
9
|
-
export declare const MessageToolboxWrapper: import("react").MemoExoticComponent<({ isOutgoing, rect, message, reaction, toolbox, }: MessageToolboxWrapperProps) => import("react").JSX.Element>;
|
|
22
|
+
export declare const MessageToolboxWrapper: import("react").MemoExoticComponent<({ isOutgoing, rect, message, isCloneInteractive, onDismiss, reaction, toolbox, }: MessageToolboxWrapperProps) => import("react").JSX.Element>;
|
|
10
23
|
export {};
|
|
11
24
|
//# sourceMappingURL=MessageToolboxWrapper.d.ts.map
|
package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageToolboxWrapper.d.ts","sourceRoot":"","sources":["../../../../../../src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MessageToolboxWrapper.d.ts","sourceRoot":"","sources":["../../../../../../src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAY5C,KAAK,0BAA0B,GAAG;IAChC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAEF,eAAO,MAAM,qBAAqB,yHAS7B,0BAA0B,iCAuI9B,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ClonePreviewBudget } from '../../../components/messages/PreviewScrollableText';
|
|
2
|
+
/**
|
|
3
|
+
* Wiring between the overlay's clone and the expanded text that scrolls
|
|
4
|
+
* inside it (see `PreviewScrollableText`).
|
|
5
|
+
*
|
|
6
|
+
* `dismissDisabled`: the clone sits in a tap-to-dismiss Pressable. On iOS a
|
|
7
|
+
* ScrollView refuses to scroll while an ANCESTOR holds the JS responder
|
|
8
|
+
* (`RCTScrollView _shouldDisableScrollInteraction`), so with the Pressable
|
|
9
|
+
* enabled a drag on the text never scrolls — it ends as a tap and closes the
|
|
10
|
+
* overlay. While the text is scrollable the Pressable stands down; the
|
|
11
|
+
* backdrop and the menu still dismiss.
|
|
12
|
+
*/
|
|
13
|
+
export declare const useClonePreview: (maxHeight: number) => {
|
|
14
|
+
budget: ClonePreviewBudget;
|
|
15
|
+
onContentSizeChange: (_width: number, height: number) => void;
|
|
16
|
+
dismissDisabled: boolean;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=useClonePreview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useClonePreview.d.ts","sourceRoot":"","sources":["../../../../../../src/screens/chat-detail/messageToolbox/useClonePreview.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oDAAoD,CAAC;AAE7F;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,GAAI,WAAW,MAAM;;kCAKpC,MAAM,UAAU,MAAM;;CAclC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { expandedKey, type MessageSurface } from '../utils/expandableText';
|
|
2
|
+
export type { MessageSurface };
|
|
3
|
+
export { expandedKey };
|
|
4
|
+
export interface ExpandedMessageStore {
|
|
5
|
+
/**
|
|
6
|
+
* Flat record rather than a Set so a row can subscribe with a cheap boolean
|
|
7
|
+
* selector — `useExpandedMessageStore(s => !!s.expanded[key])` — which stays
|
|
8
|
+
* referentially stable for every message the change didn't touch.
|
|
9
|
+
*/
|
|
10
|
+
expanded: Record<string, true>;
|
|
11
|
+
expand: (surface: MessageSurface, messageId: string) => void;
|
|
12
|
+
clearSurface: (surface: MessageSurface) => void;
|
|
13
|
+
}
|
|
14
|
+
export declare const useExpandedMessageStore: import("zustand").UseBoundStore<import("zustand").StoreApi<ExpandedMessageStore>>;
|
|
15
|
+
//# sourceMappingURL=expandedMessage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expandedMessage.d.ts","sourceRoot":"","sources":["../../../../src/store/expandedMessage.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EAEX,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AAEjC,YAAY,EAAE,cAAc,EAAE,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,CAAC;AAEvB,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC/B,MAAM,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,YAAY,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;CACjD;AAED,eAAO,MAAM,uBAAuB,mFAoBnC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type MessageSurface = 'timeline' | 'pin';
|
|
2
|
+
export declare const COLLAPSED_LINES = 5;
|
|
3
|
+
export declare const expandedKey: (surface: MessageSurface, messageId: string) => string;
|
|
4
|
+
/**
|
|
5
|
+
* Character count alone is not an approximation of rendered lines — it is
|
|
6
|
+
* wrong for the shapes this chat carries most (pasted addresses, order lists),
|
|
7
|
+
* where an eight-line message can be under 70 characters. Newlines cost one
|
|
8
|
+
* character and a whole line, so both signals are needed. Message text is
|
|
9
|
+
* sender-controlled, so a length-only gate is also trivially defeated.
|
|
10
|
+
*/
|
|
11
|
+
export declare const isMeasurementCandidate: (text: string) => boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Returns a copy without the entries belonging to `surface`, or the original
|
|
14
|
+
* reference when nothing matched — callers use that identity to skip a
|
|
15
|
+
* pointless store update.
|
|
16
|
+
*/
|
|
17
|
+
export declare const withoutSurface: (expanded: Record<string, true>, surface: MessageSurface) => Record<string, true>;
|
|
18
|
+
//# sourceMappingURL=expandableText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expandableText.d.ts","sourceRoot":"","sources":["../../../../src/utils/expandableText.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,KAAK,CAAC;AAEhD,eAAO,MAAM,eAAe,IAAI,CAAC;AAIjC,eAAO,MAAM,WAAW,GAAI,SAAS,cAAc,EAAE,WAAW,MAAM,WAC3C,CAAC;AAU5B;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,GAAI,MAAM,MAAM,YAEX,CAAC;AAEzC;;;;GAIG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,EAC9B,SAAS,cAAc,KACtB,MAAM,CAAC,MAAM,EAAE,IAAI,CAcrB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cut-point arithmetic for the inline `... Xem thêm` preview. Everything here
|
|
3
|
+
* works on the lines RN reports from `onTextLayout`, whose `text` includes the
|
|
4
|
+
* line's trailing break — so joining them reproduces the rendered string and a
|
|
5
|
+
* running length is a valid character offset into it.
|
|
6
|
+
*/
|
|
7
|
+
export interface RenderedLine {
|
|
8
|
+
text: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const joinLines: (lines: readonly RenderedLine[]) => string;
|
|
11
|
+
/**
|
|
12
|
+
* Backs `cut` off trailing whitespace — a kept `\n` would push the suffix onto
|
|
13
|
+
* a line of its own — and off half an emoji, which would render as a tofu box.
|
|
14
|
+
*/
|
|
15
|
+
export declare const settleCut: (full: string, cut: number) => number;
|
|
16
|
+
/** First guess: everything on the first `maxLines` lines. */
|
|
17
|
+
export declare const initialCut: (lines: readonly RenderedLine[], maxLines: number) => number;
|
|
18
|
+
/**
|
|
19
|
+
* The suffix did not fit and wrapped past `maxLines`. Drop at least as many
|
|
20
|
+
* characters as spilled over — the spill includes the suffix itself, so this
|
|
21
|
+
* converges in one or two passes — then prefer ending on a word.
|
|
22
|
+
*/
|
|
23
|
+
export declare const shrinkCut: (full: string, cut: number, fittedLines: readonly RenderedLine[], maxLines: number) => number;
|
|
24
|
+
//# sourceMappingURL=inlineTruncation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inlineTruncation.d.ts","sourceRoot":"","sources":["../../../../src/utils/inlineTruncation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd;AAUD,eAAO,MAAM,SAAS,GAAI,OAAO,SAAS,YAAY,EAAE,WACf,CAAC;AAE1C;;;GAGG;AACH,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,WAKlD,CAAC;AAEF,6DAA6D;AAC7D,eAAO,MAAM,UAAU,GACrB,OAAO,SAAS,YAAY,EAAE,EAC9B,UAAU,MAAM,WAIjB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,SAAS,GACpB,MAAM,MAAM,EACZ,KAAK,MAAM,EACX,aAAa,SAAS,YAAY,EAAE,EACpC,UAAU,MAAM,WAcjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"truncateTextChildren.d.ts","sourceRoot":"","sources":["../../../../src/utils/truncateTextChildren.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AA4CrE,eAAO,MAAM,oBAAoB,GAAI,MAAM,SAAS,EAAE,OAAO,MAAM,cAC7C,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
2
|
+
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
import { KColors, KContainer, KLabel } from '@droppii/libs';
|
|
4
|
+
import { trans } from '../../translation';
|
|
5
|
+
import {
|
|
6
|
+
COLLAPSED_LINES,
|
|
7
|
+
useExpandableText,
|
|
8
|
+
} from '../../hooks/useExpandableText';
|
|
9
|
+
import { truncateTextChildren } from '../../utils/truncateTextChildren';
|
|
10
|
+
import { PreviewScrollableText } from './PreviewScrollableText';
|
|
11
|
+
import type { MessageSurface } from '../../store/expandedMessage';
|
|
12
|
+
|
|
13
|
+
type TypoType = ComponentProps<typeof KLabel.Text>['typo'];
|
|
14
|
+
|
|
15
|
+
// Values from `@droppii/libs`' Typography.tsx (`generateTextStyle`'s third
|
|
16
|
+
// argument), not looked up at runtime — the package does not cleanly
|
|
17
|
+
// re-export a way to do that. All three call sites use `TextMdNormal` today;
|
|
18
|
+
// add an entry here before passing any other `typo` to this component.
|
|
19
|
+
const TYPO_LINE_HEIGHT: Record<string, number> = {
|
|
20
|
+
TextMdNormal: 25.6,
|
|
21
|
+
};
|
|
22
|
+
const DEFAULT_LINE_HEIGHT = 22.4;
|
|
23
|
+
|
|
24
|
+
// Figma (Live Chat, node 15389-207764): `…và... Xem thêm` — the ellipsis in
|
|
25
|
+
// the body colour, the control inline on the last visible line.
|
|
26
|
+
const ELLIPSIS = '... ';
|
|
27
|
+
|
|
28
|
+
interface ExpandableMessageTextProps {
|
|
29
|
+
messageId: string;
|
|
30
|
+
surface: MessageSurface;
|
|
31
|
+
/** Raw body text — drives the length/newline pre-check only. */
|
|
32
|
+
text: string;
|
|
33
|
+
/**
|
|
34
|
+
* The already-rendered body. Taken as children rather than re-derived from
|
|
35
|
+
* `text` because the three call sites build structurally different bodies
|
|
36
|
+
* (plain run, mention segments, linkified runs) and re-tokenising them here
|
|
37
|
+
* would duplicate three parsers.
|
|
38
|
+
*/
|
|
39
|
+
children: ReactNode;
|
|
40
|
+
typo?: TypoType;
|
|
41
|
+
color?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const ExpandableMessageText = ({
|
|
45
|
+
messageId,
|
|
46
|
+
surface,
|
|
47
|
+
text,
|
|
48
|
+
children,
|
|
49
|
+
typo = 'TextMdNormal',
|
|
50
|
+
color,
|
|
51
|
+
}: ExpandableMessageTextProps) => {
|
|
52
|
+
const { mode, cut, expand, onMeasureTextLayout, onFitTextLayout } =
|
|
53
|
+
useExpandableText(messageId, text, surface);
|
|
54
|
+
|
|
55
|
+
if (mode === 'plain') {
|
|
56
|
+
// Only the overlay provides a budget; everywhere else this is a no-op.
|
|
57
|
+
return (
|
|
58
|
+
<PreviewScrollableText>
|
|
59
|
+
<KLabel.Text typo={typo} color={color}>
|
|
60
|
+
{children}
|
|
61
|
+
</KLabel.Text>
|
|
62
|
+
</PreviewScrollableText>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const label = trans('chat:info_view_more') || 'Xem thêm';
|
|
67
|
+
|
|
68
|
+
if (mode === 'measuring') {
|
|
69
|
+
// One layout pass. The visible copy is clipped to N lines by height so the
|
|
70
|
+
// bubble does not flash at full length; the hidden copy is unconstrained,
|
|
71
|
+
// which is what makes its `onTextLayout` report every line on iOS too.
|
|
72
|
+
const lineHeight =
|
|
73
|
+
TYPO_LINE_HEIGHT[typo ?? 'TextMdNormal'] ?? DEFAULT_LINE_HEIGHT;
|
|
74
|
+
return (
|
|
75
|
+
<KContainer.View>
|
|
76
|
+
<View
|
|
77
|
+
style={[
|
|
78
|
+
styles.clampedBox,
|
|
79
|
+
{ maxHeight: lineHeight * COLLAPSED_LINES },
|
|
80
|
+
]}
|
|
81
|
+
>
|
|
82
|
+
<KLabel.Text typo={typo} color={color}>
|
|
83
|
+
{children}
|
|
84
|
+
</KLabel.Text>
|
|
85
|
+
</View>
|
|
86
|
+
<View
|
|
87
|
+
style={styles.measuringCopy}
|
|
88
|
+
pointerEvents="none"
|
|
89
|
+
accessibilityElementsHidden
|
|
90
|
+
importantForAccessibility="no-hide-descendants"
|
|
91
|
+
>
|
|
92
|
+
<KLabel.Text
|
|
93
|
+
typo={typo}
|
|
94
|
+
color={color}
|
|
95
|
+
onTextLayout={onMeasureTextLayout}
|
|
96
|
+
>
|
|
97
|
+
{children}
|
|
98
|
+
</KLabel.Text>
|
|
99
|
+
</View>
|
|
100
|
+
</KContainer.View>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 'fitting' re-checks that the suffix still fits in N lines; 'collapsed'
|
|
105
|
+
// renders a cut that already passed that check.
|
|
106
|
+
return (
|
|
107
|
+
<KLabel.Text
|
|
108
|
+
typo={typo}
|
|
109
|
+
color={color}
|
|
110
|
+
onTextLayout={mode === 'fitting' ? onFitTextLayout : undefined}
|
|
111
|
+
>
|
|
112
|
+
{truncateTextChildren(children, cut)}
|
|
113
|
+
{ELLIPSIS}
|
|
114
|
+
<KLabel.Text
|
|
115
|
+
typo="TextMdMedium"
|
|
116
|
+
color={KColors.primary.normal}
|
|
117
|
+
onPress={expand}
|
|
118
|
+
accessibilityRole="button"
|
|
119
|
+
accessibilityLabel={label}
|
|
120
|
+
>
|
|
121
|
+
{label}
|
|
122
|
+
</KLabel.Text>
|
|
123
|
+
</KLabel.Text>
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
ExpandableMessageText.displayName = 'ExpandableMessageText';
|
|
128
|
+
|
|
129
|
+
const styles = StyleSheet.create({
|
|
130
|
+
clampedBox: {
|
|
131
|
+
overflow: 'hidden',
|
|
132
|
+
},
|
|
133
|
+
// `left`+`right` take the container's width in the same layout pass, so the
|
|
134
|
+
// copy wraps exactly like the visible one without a round trip through
|
|
135
|
+
// `onLayout`. No `bottom`: pinning it (i.e. absoluteFill) would make this
|
|
136
|
+
// inherit the clamped parent's height, and iOS would lay out only the lines
|
|
137
|
+
// that fit — reporting a clamped message as short.
|
|
138
|
+
measuringCopy: {
|
|
139
|
+
opacity: 0,
|
|
140
|
+
position: 'absolute',
|
|
141
|
+
left: 0,
|
|
142
|
+
right: 0,
|
|
143
|
+
top: 0,
|
|
144
|
+
},
|
|
145
|
+
});
|