Skip to content

Commit 598e66e

Browse files
committed
Merge remote-tracking branch 'origin/mobile-main' into dev
2 parents 1ebe3c7 + 79a0766 commit 598e66e

11 files changed

Lines changed: 133 additions & 31 deletions

File tree

apps/mobile/changelog/0.5.0.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# What's New in v0.5.0
2+
3+
## Shiny new things
4+
5+
- Added streaming text-to-speech playback
6+
- Enabled background OTA updates
7+
- Added Spotlight settings with refined highlighting controls
8+
9+
## Improvements
10+
11+
- Upgraded the app to Expo SDK 55 and the latest React Native Firebase stack
12+
- Improved OTA policy handling and release metadata flow
13+
- Increased AI summary font size for better readability
14+
15+
## No longer broken
16+
17+
- Fixed YouTube embeds closing incorrectly on iOS
18+
- Fixed Android video playback changing the original audio pitch
19+
- Fixed social sign-in state refresh
20+
- Fixed session refresh after cookie updates
21+
- Fixed iOS archive and build issues on Expo SDK 55 / Xcode 26.4
22+
23+
## Thanks
24+
25+
Special thanks to volunteer contributor @luckycold for their valuable contribution

apps/mobile/changelog/next.md

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,10 @@
22

33
## Shiny new things
44

5-
- Added streaming text-to-speech playback
6-
- Enabled background OTA updates
7-
- Added Spotlight settings with refined highlighting controls
8-
95
## Improvements
106

11-
- Upgraded the app to Expo SDK 55 and the latest React Native Firebase stack
12-
- Improved OTA policy handling and release metadata flow
13-
- Increased AI summary font size for better readability
14-
157
## No longer broken
168

17-
- Fixed YouTube embeds closing incorrectly on iOS
18-
- Fixed Android video playback changing the original audio pitch
19-
- Fixed social sign-in state refresh
20-
- Fixed session refresh after cookie updates
21-
- Fixed iOS archive and build issues on Expo SDK 55 / Xcode 26.4
22-
239
## Thanks
2410

25-
Special thanks to volunteer contributor @luckycold for their valuable contribution
11+
Special thanks to volunteer contributors @ for their valuable contributions

apps/mobile/ios/Folo/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
<key>CFBundlePackageType</key>
3434
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
3535
<key>CFBundleShortVersionString</key>
36-
<string>0.4.1</string>
36+
<string>0.5.0</string>
3737
<key>CFBundleSignature</key>
3838
<string>????</string>
3939
<key>CFBundleURLTypes</key>
@@ -54,7 +54,7 @@
5454
</dict>
5555
</array>
5656
<key>CFBundleVersion</key>
57-
<string>1</string>
57+
<string>3</string>
5858
<key>ITSAppUsesNonExemptEncryption</key>
5959
<false/>
6060
<key>LSApplicationCategoryType</key>

apps/mobile/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@follow/mobile",
3-
"version": "0.4.1",
3+
"version": "0.5.0",
44
"private": true,
55
"main": "src/main.tsx",
66
"scripts": {

apps/mobile/release.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "0.4.1",
2+
"version": "0.5.0",
33
"mode": "store",
44
"runtimeVersion": null,
55
"channel": null

apps/mobile/src/modules/entry-list/templates/EntryTranslation.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { useMemo } from "react"
22
import type { TextProps } from "react-native"
3-
import { View } from "react-native"
3+
import { Text as RNText, View } from "react-native"
44

55
import { useGeneralSettingKey } from "@/src/atoms/settings/general"
6+
import { useSpotlightSettingKey } from "@/src/atoms/settings/spotlight"
67
import { Text } from "@/src/components/ui/typography/Text"
8+
import { HighlightedText } from "@/src/modules/spotlight/HighlightedText"
79

810
export const EntryTranslation = ({
911
source,
@@ -22,6 +24,7 @@ export const EntryTranslation = ({
2224
bilingual?: boolean
2325
} & TextProps) => {
2426
const bilingualFinal = useGeneralSettingKey("translationMode") === "bilingual" || bilingual
27+
const spotlightRules = useSpotlightSettingKey("spotlights")
2528
const nextSource = useMemo(() => {
2629
if (!source) {
2730
return ""
@@ -37,25 +40,27 @@ export const EntryTranslation = ({
3740
if (!bilingualFinal) {
3841
return (
3942
<Text {...props} className={className}>
40-
{nextTarget || nextSource}
43+
<HighlightedText text={nextTarget || nextSource} rules={spotlightRules} />
4144
</Text>
4245
)
4346
}
4447
if (inline) {
4548
return (
4649
<Text {...props} className={className}>
47-
{`${nextTarget ? `${nextTarget} ⇋ ` : ""}${nextSource}`}
50+
{nextTarget ? <HighlightedText text={nextTarget} rules={spotlightRules} /> : null}
51+
{nextTarget ? <RNText>{" ⇋ "}</RNText> : null}
52+
<HighlightedText text={nextSource} rules={spotlightRules} />
4853
</Text>
4954
)
5055
}
5156
return (
5257
<View>
5358
<Text {...props} className={className}>
54-
{nextSource}
59+
<HighlightedText text={nextSource} rules={spotlightRules} />
5560
</Text>
5661
{nextTarget && (
5762
<Text {...props} className={className}>
58-
{nextTarget}
63+
<HighlightedText text={nextTarget} rules={spotlightRules} />
5964
</Text>
6065
)}
6166
</View>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { SpotlightRule } from "@follow/shared/spotlight"
2+
import { Fragment, useMemo } from "react"
3+
import { Text as RNText } from "react-native"
4+
5+
import { getHighlightedTextSegments } from "./highlightedTextSegments"
6+
import { getHighlightedTextSegmentStyle } from "./highlightedTextStyle"
7+
8+
export const HighlightedText = ({
9+
text,
10+
rules,
11+
}: {
12+
text?: string | null
13+
rules: SpotlightRule[]
14+
}) => {
15+
const segments = useMemo(() => getHighlightedTextSegments(text, rules), [rules, text])
16+
const keyedSegments = useMemo(() => {
17+
let offset = 0
18+
19+
return segments.map((segment) => {
20+
const key = segment.highlight ? `${offset}-${segment.highlight.ruleId}-${segment.text}` : null
21+
offset += segment.text.length
22+
return { key: key ?? `${offset}-${segment.text}`, segment }
23+
})
24+
}, [segments])
25+
26+
return (
27+
<>
28+
{keyedSegments.map(({ key, segment }) =>
29+
segment.highlight ? (
30+
<RNText key={key} style={getHighlightedTextSegmentStyle(segment.highlight.color)}>
31+
{segment.text}
32+
</RNText>
33+
) : (
34+
<Fragment key={key}>{segment.text}</Fragment>
35+
),
36+
)}
37+
</>
38+
)
39+
}

apps/mobile/web-app/html-renderer/src/App.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createStore, Provider, useAtomValue } from "jotai"
22

3-
import { entryAtom, noMediaAtom, readerRenderInlineStyleAtom } from "./atoms"
3+
import { entryAtom, noMediaAtom, readerRenderInlineStyleAtom, spotlightAtom } from "./atoms"
44
import { HTML } from "./HTML"
55
import { WebViewBridgeManager } from "./managers/webview-bridge"
66

@@ -14,10 +14,15 @@ export const App = () => {
1414
const entry = useAtomValue(entryAtom, { store })
1515
const readerRenderInlineStyle = useAtomValue(readerRenderInlineStyleAtom, { store })
1616
const noMedia = useAtomValue(noMediaAtom, { store })
17+
const spotlightRules = useAtomValue(spotlightAtom, { store })
1718

1819
return (
1920
<Provider store={store}>
20-
<HTML renderInlineStyle={readerRenderInlineStyle} noMedia={noMedia}>
21+
<HTML
22+
renderInlineStyle={readerRenderInlineStyle}
23+
noMedia={noMedia}
24+
spotlightRules={spotlightRules}
25+
>
2126
{entry?.content}
2227
</HTML>
2328
</Provider>

apps/mobile/web-app/html-renderer/src/HTML.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { MemoedDangerousHTMLStyle } from "@follow/components/common/MemoedDangerousHTMLStyle.js"
2+
import type { SpotlightRule } from "@follow/shared/spotlight"
23
import { clsx } from "clsx"
34
import katexStyle from "katex/dist/katex.min.css?raw"
45
import * as React from "react"
@@ -14,6 +15,7 @@ export type HTMLProps<A extends keyof React.JSX.IntrinsicElements = "div"> = {
1415

1516
accessory?: React.ReactNode
1617
noMedia?: boolean
18+
spotlightRules?: SpotlightRule[]
1719
} & React.JSX.IntrinsicElements[A] &
1820
Partial<{
1921
renderInlineStyle: boolean
@@ -25,25 +27,29 @@ export const HTML = <A extends keyof React.JSX.IntrinsicElements = "div">(props:
2527
as = "article",
2628
accessory,
2729
noMedia,
30+
spotlightRules,
2831

2932
...rest
3033
} = props
3134
const [remarkOptions, setRemarkOptions] = useState({
3235
renderInlineStyle,
3336
noMedia,
37+
spotlightRules,
3438
})
3539
const [shouldForceReMountKey, setShouldForceReMountKey] = useState(0)
3640

3741
useEffect(() => {
3842
setRemarkOptions((options) => {
39-
if (JSON.stringify(options) === JSON.stringify({ renderInlineStyle, noMedia })) {
43+
if (
44+
JSON.stringify(options) === JSON.stringify({ renderInlineStyle, noMedia, spotlightRules })
45+
) {
4046
return options
4147
}
4248

4349
setShouldForceReMountKey((key) => key + 1)
44-
return { ...options, renderInlineStyle, noMedia }
50+
return { ...options, renderInlineStyle, noMedia, spotlightRules }
4551
})
46-
}, [renderInlineStyle, noMedia])
52+
}, [renderInlineStyle, noMedia, spotlightRules])
4753

4854
const [refElement, setRefElement] = useState<HTMLDivElement | null>(null)
4955

apps/mobile/web-app/html-renderer/src/parser.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { MemoedDangerousHTMLStyle } from "@follow/components/common/MemoedDangerousHTMLStyle.jsx"
22
import { Checkbox } from "@follow/components/ui/checkbox/index.jsx"
3+
import type { SpotlightRule } from "@follow/shared/spotlight"
34
import { parseHtml as parseHtmlGeneral } from "@follow/utils/html"
45
import type { Components } from "hast-util-to-jsx-runtime"
56
import { createElement } from "react"
@@ -9,8 +10,9 @@ import { createHeadingRenderer, MarkdownLink, MarkdownP } from "./components"
910
import { MarkdownImage } from "./components/image"
1011
import { Math } from "./components/math"
1112
import { ShikiHighLighter } from "./components/shiki"
13+
import { applySpotlightToHtmlRendererTree } from "./spotlight"
1214

13-
const Style: Components["style"] = ({ node, ...props }) => {
15+
const renderStyleTag: Components["style"] = ({ node, ...props }) => {
1416
if (typeof props.children === "string") {
1517
return createElement(MemoedDangerousHTMLStyle, null, props.children)
1618
}
@@ -22,10 +24,16 @@ export const parseHtml = (
2224
options?: Partial<{
2325
renderInlineStyle: boolean
2426
noMedia?: boolean
27+
spotlightRules?: SpotlightRule[]
2528
}>,
2629
) => {
30+
const spotlightRules = options?.spotlightRules ?? []
31+
2732
return parseHtmlGeneral(content, {
2833
...options,
34+
hastTransform: (tree) => {
35+
applySpotlightToHtmlRendererTree(tree, spotlightRules)
36+
},
2937
components: {
3038
a: ({ node, ...props }) => {
3139
// Ignore link wrapper when child is an image to ensure image preview
@@ -62,7 +70,7 @@ export const parseHtml = (
6270
h6: (props) => {
6371
return createHeadingRenderer(6)(props)
6472
},
65-
style: Style,
73+
style: renderStyleTag,
6674
img: ({ node, ...props }) => {
6775
return createElement(MarkdownImage, props as any)
6876
},

0 commit comments

Comments
 (0)