From d4baa1958787578617690f566e68672b3c012cb5 Mon Sep 17 00:00:00 2001 From: Caspian Date: Mon, 15 Jan 2024 15:15:28 +0000 Subject: [PATCH 1/5] feat: Adjust feed item styles. --- .changeset/fluffy-balloons-chew.md | 5 +++++ src/components/FeedList/FeedListItem/index.tsx | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .changeset/fluffy-balloons-chew.md diff --git a/.changeset/fluffy-balloons-chew.md b/.changeset/fluffy-balloons-chew.md new file mode 100644 index 0000000..89e6575 --- /dev/null +++ b/.changeset/fluffy-balloons-chew.md @@ -0,0 +1,5 @@ +--- +"xlog": patch +--- + +Adjust feed item styles. diff --git a/src/components/FeedList/FeedListItem/index.tsx b/src/components/FeedList/FeedListItem/index.tsx index bae4dc2..d5af49e 100644 --- a/src/components/FeedList/FeedListItem/index.tsx +++ b/src/components/FeedList/FeedListItem/index.tsx @@ -28,6 +28,7 @@ export interface Props { } const minHeight = 150; +const maxHeight = 250; const defaultCoverImageHeight = minHeight; export const FeedListItem: FC = (props) => { @@ -48,7 +49,7 @@ export const FeedListItem: FC = (props) => { const relativeHeight = useMemo(() => { const ratio = coverImageSize.width / coverImageSize.height; - const _height = Math.max(width / ratio, minHeight); + const _height = Math.min(Math.max(width / ratio, minHeight), maxHeight); return isNaN(_height) ? defaultCoverImageHeight : _height; }, [coverImageSize.height]); From ea7932c90786f549c555a3926dc1120937f5617b Mon Sep 17 00:00:00 2001 From: Caspian Date: Mon, 15 Jan 2024 15:29:58 +0000 Subject: [PATCH 2/5] fix: Display shorts images normally. --- .changeset/short-vans-hug.md | 5 +++++ src/pages/PostDetails/javascript-content.ts | 12 +++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 .changeset/short-vans-hug.md diff --git a/.changeset/short-vans-hug.md b/.changeset/short-vans-hug.md new file mode 100644 index 0000000..4a8500b --- /dev/null +++ b/.changeset/short-vans-hug.md @@ -0,0 +1,5 @@ +--- +"xlog": patch +--- + +Display shorts images normally. diff --git a/src/pages/PostDetails/javascript-content.ts b/src/pages/PostDetails/javascript-content.ts index a365b2e..1516c9e 100644 --- a/src/pages/PostDetails/javascript-content.ts +++ b/src/pages/PostDetails/javascript-content.ts @@ -61,9 +61,15 @@ export const javaScriptContentLoaded = ( function handleImageClick(event) { event.preventDefault(); - const clickedImageUrl = event.target.dataset.src; - const images = document.getElementsByTagName('img'); - const allImageUrls = Array.from(images).map(img => img.dataset.src); + const isAvatar = img => img.width === 32 && img.height === 32; + + if (isAvatar(event.target)) { + return false; + } + + const clickedImageUrl = event.target.src || event.target.dataset.src; + const images = Array.from(document.getElementsByTagName('img')).filter(img => !isAvatar(img)); + const allImageUrls = Array.from(images).map(img => img.src || img.dataset.src); const imageUrlSet = new Set([clickedImageUrl, ...allImageUrls]); const imageUrlArray = Array.from(imageUrlSet); From 933f3cb5d0000ac6c4e139219dea068440f639a3 Mon Sep 17 00:00:00 2001 From: Caspian Date: Mon, 15 Jan 2024 16:51:53 +0000 Subject: [PATCH 3/5] feat: Supported jumping to short content details when clicking on the short content embed block. --- .changeset/slimy-guests-repair.md | 5 +++ package.json | 1 + src/pages/PostDetails/WebViewRenderer.tsx | 16 +++++++ src/pages/PostDetails/index.tsx | 49 ++++++++++++++------- src/pages/PostDetails/javascript-content.ts | 21 ++++++--- src/utils/get-params-from-shorts-url.ts | 28 ++++++++++++ yarn.lock | 5 +++ 7 files changed, 101 insertions(+), 24 deletions(-) create mode 100644 .changeset/slimy-guests-repair.md create mode 100644 src/utils/get-params-from-shorts-url.ts diff --git a/.changeset/slimy-guests-repair.md b/.changeset/slimy-guests-repair.md new file mode 100644 index 0000000..c72fdb7 --- /dev/null +++ b/.changeset/slimy-guests-repair.md @@ -0,0 +1,5 @@ +--- +"xlog": patch +--- + +Supported jumping to short content details when clicking on the short content embed block. diff --git a/package.json b/package.json index 0f85a9e..aba4fe8 100644 --- a/package.json +++ b/package.json @@ -155,6 +155,7 @@ "npm-run-all": "^4.1.5", "patch-package": "^6.4.7", "path-browserify": "0.0.0", + "path-to-regexp": "^6.2.1", "pinyin-pro": "^3.14.0", "punycode": "^1.4.1", "qrcode": "^1.5.1", diff --git a/src/pages/PostDetails/WebViewRenderer.tsx b/src/pages/PostDetails/WebViewRenderer.tsx index 08b0bd5..196375b 100644 --- a/src/pages/PostDetails/WebViewRenderer.tsx +++ b/src/pages/PostDetails/WebViewRenderer.tsx @@ -12,6 +12,7 @@ import { WebView } from "@/components/WebView"; import { VERSION } from "@/constants"; import { useRootNavigation } from "@/hooks/use-navigation"; import { useThemeStore } from "@/hooks/use-theme-store"; +import { getParamsFromShortsURL } from "@/utils/get-params-from-shorts-url"; import { javaScriptBeforeContentLoaded } from "./javascript-before-content"; import { javaScriptContentLoaded } from "./javascript-content"; @@ -47,6 +48,21 @@ export const WebViewRenderer: FC<{ const { height, imageUrlArray, link, title } = JSON.parse(data); if (link) { + const url = new URL(link); + const params = getParamsFromShortsURL(url); + const isPost = !!params; + + if (isPost) { + navigation.push( + "PostDetails", + { + slug: params.slug, + handle: params.handle, + }, + ); + return; + } + navigation.navigate("Web", { url: link, title, diff --git a/src/pages/PostDetails/index.tsx b/src/pages/PostDetails/index.tsx index cfe1921..2773782 100644 --- a/src/pages/PostDetails/index.tsx +++ b/src/pages/PostDetails/index.tsx @@ -4,15 +4,15 @@ import { useSharedValue, withSpring, withDelay } from "react-native-reanimated"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import type { NativeStackScreenProps } from "@react-navigation/native-stack"; -import type { NoteEntity } from "crossbell"; import { Stack } from "tamagui"; import { DelayedRender } from "@/components/DelayRender"; -import { ImageGallery } from "@/components/ImageGallery"; import { usePostWebViewLink } from "@/hooks/use-post-link"; import { useScrollVisibilityHandler } from "@/hooks/use-scroll-visibility-handler"; import { useThemeStore } from "@/hooks/use-theme-store"; import type { RootStackParamList } from "@/navigation/types"; +import { useGetPage } from "@/queries/page"; +import { useGetSite } from "@/queries/site"; import type { ExpandedNote } from "@/types/crossbell"; import { GA } from "@/utils/GA"; @@ -24,8 +24,10 @@ import { Header } from "./Header"; import { Navigator } from "./Navigator"; export interface Props { - characterId: number - note: ExpandedNote + characterId?: number + note?: ExpandedNote + slug?: string + handle?: string coverImage?: string placeholderCoverImageIndex?: number } @@ -35,7 +37,16 @@ const animationTimeout = 300; export const PostDetailsPage: FC> = (props) => { const { route } = props; const { params } = route; - const { note, characterId } = params; + const { slug, handle, coverImage, placeholderCoverImageIndex } = params; + const site = useGetSite(handle); + const page = useGetPage(site && { + characterId: site.data?.characterId, + slug, + handle, + }); + + const note = params.note || page.data; + const characterId = params.characterId || site.data?.characterId; const { isDarkMode } = useThemeStore(); const { bottom } = useSafeAreaInsets(); const bottomBarHeight = bottom + 45; @@ -44,24 +55,28 @@ export const PostDetailsPage: FC(null); const followAnimValue = useSharedValue(0); const scrollVisibilityHandler = useScrollVisibilityHandler({ scrollThreshold: 30 }); - const postUri = usePostWebViewLink({ ...params, noteId: note.noteId }); + const postUri = usePostWebViewLink({ ...params, characterId, noteId: note?.noteId }); const onTakeScreenshot = React.useCallback(async (): Promise => contentRef.current.takeScreenshot(), []); useEffect(() => { followAnimValue.value = withDelay(1500, withSpring(1)); GA.logEvent("start_reading_post", { - node_id: note.noteId, + node_id: note?.noteId, character_id: characterId, }); }, []); + if (!note || !characterId) { + return null; + } + return ( @@ -75,15 +90,15 @@ export const PostDetailsPage: FC ); }} - characterId={params.characterId} - note={params.note} + characterId={characterId} + note={note} scrollEventHandler={scrollVisibilityHandler} bottomBarHeight={bottomBarHeight} headerContainerHeight={headerContainerHeight} @@ -94,8 +109,8 @@ export const PostDetailsPage: FC diff --git a/src/pages/PostDetails/javascript-content.ts b/src/pages/PostDetails/javascript-content.ts index 1516c9e..68ad938 100644 --- a/src/pages/PostDetails/javascript-content.ts +++ b/src/pages/PostDetails/javascript-content.ts @@ -61,6 +61,8 @@ export const javaScriptContentLoaded = ( function handleImageClick(event) { event.preventDefault(); + event.stopPropagation(); + const isAvatar = img => img.width === 32 && img.height === 32; if (isAvatar(event.target)) { @@ -84,12 +86,17 @@ export const javaScriptContentLoaded = ( function handleLinkClick(event) { event.preventDefault(); - window.ReactNativeWebView.postMessage( - JSON.stringify({ - link: event.target.href, - title: event.target.innerText - }) - ); + + let target = event.target.closest('a'); + if (target) { + window.ReactNativeWebView.postMessage( + JSON.stringify({ + link: target.href, + title: target.innerText + }) + ); + } + return false; } @@ -109,7 +116,7 @@ export const javaScriptContentLoaded = ( for (let i = 0; i < links.length; i++) { if (!observedLinks.has(links[i])) { - links[i].addEventListener('click', handleLinkClick, true); + links[i].addEventListener('click', handleLinkClick); observedLinks.add(links[i]); } } diff --git a/src/utils/get-params-from-shorts-url.ts b/src/utils/get-params-from-shorts-url.ts new file mode 100644 index 0000000..6701f09 --- /dev/null +++ b/src/utils/get-params-from-shorts-url.ts @@ -0,0 +1,28 @@ +import { match } from "path-to-regexp"; + +export function getParamsFromShortsURL(url: URL) { + let slug = ""; + let handle = ""; + + // https://caspian-3030.xlog.app/AtOAglnMV_N6xslDbfhz4?ct=shorts + let matched = match<{ slug: string }>("/:slug")(url.pathname); + + if (matched) { + slug = matched.params.slug; + handle = url.host.split(".")[0]; + } + else { + // https://xlog.app/site/caspian-3030/AtOAglnMV_N6xslDbfhz4?ct=shorts + matched = match<{ slug: string; handle: string }>("/site/:handle/:slug")( + url.pathname, + ); + + if (!matched) return undefined; + + slug = matched?.params.slug || ""; + // @ts-expect-error + handle = matched?.params.handle || ""; + } + + return { slug, handle }; +} diff --git a/yarn.lock b/yarn.lock index f7213f1..d90c159 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15930,6 +15930,11 @@ path-to-regexp@0.1.7: resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +path-to-regexp@^6.2.1: + version "6.2.1" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" + integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw== + path-type@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz" From e98ed81a7c35a72c22895c571551fd2a9c87a870 Mon Sep 17 00:00:00 2001 From: Caspian Date: Mon, 15 Jan 2024 20:35:57 +0000 Subject: [PATCH 4/5] feat: Support to pick images from album directly in the CreateShortsButton. --- .changeset/itchy-snakes-clap.md | 5 ++ src/components/CreateShortsButton.tsx | 115 +++++++++++++++++--------- 2 files changed, 81 insertions(+), 39 deletions(-) create mode 100644 .changeset/itchy-snakes-clap.md diff --git a/.changeset/itchy-snakes-clap.md b/.changeset/itchy-snakes-clap.md new file mode 100644 index 0000000..2425bee --- /dev/null +++ b/.changeset/itchy-snakes-clap.md @@ -0,0 +1,5 @@ +--- +"xlog": patch +--- + +Support to pick images from album directly in the "CreateShortsButton". diff --git a/src/components/CreateShortsButton.tsx b/src/components/CreateShortsButton.tsx index 9252c90..a59ef68 100644 --- a/src/components/CreateShortsButton.tsx +++ b/src/components/CreateShortsButton.tsx @@ -5,23 +5,24 @@ import { TouchableWithoutFeedback } from "react-native-gesture-handler"; import Animated, { Easing, runOnJS, interpolate, interpolateColor, useAnimatedStyle, useSharedValue, withTiming, LinearTransition, FadeInLeft } from "react-native-reanimated"; import { Camera, useCameraPermission, useCameraDevice } from "react-native-vision-camera"; -import { Maximize2, Plus, X } from "@tamagui/lucide-icons"; +import { Album, Image as ImageIcon, Maximize2, Plus, X } from "@tamagui/lucide-icons"; import { BlurView } from "expo-blur"; import { Image } from "expo-image"; import * as MediaLibrary from "expo-media-library"; -import { Button, ScrollView, Stack, View, XStack, YStack, useWindowDimensions } from "tamagui"; +import { Button, ScrollView, Stack, Text, View, XStack, YStack, useWindowDimensions } from "tamagui"; import { IS_ANDROID } from "@/constants"; import { useColors } from "@/hooks/use-colors"; import { useCreateShots } from "@/hooks/use-create-shots"; import { useIsLogin } from "@/hooks/use-is-login"; import { useRootNavigation } from "@/hooks/use-navigation"; +import { usePickImages } from "@/hooks/use-pick-images"; import type { Photo } from "@/pages/TakePhoto"; import { XTouch } from "./XTouch"; export const CreateShortsButton: FC = () => { - const { createShots } = useCreateShots(); + const { pickImages } = usePickImages(); const navigation = useRootNavigation(); const isLogin = useIsLogin(); const { width } = useWindowDimensions(); @@ -137,6 +138,7 @@ export const CreateShortsButton: FC = () => { const isOpenAnimValue = useSharedValue(0); const targetWidth = width * 0.96; const targetHeight = 300; + const photoSize = targetWidth / 4 * 0.9; const containerAnimStyle = useAnimatedStyle(() => { const animValue = Math.max(0, isOpenAnimValue.value); @@ -176,6 +178,22 @@ export const CreateShortsButton: FC = () => { }); }; + const selectImageFromAlbum = () => { + pickImages().then((result) => { + if (result) { + setPhotos(photos => [ + ...result, + ...photos, + ]); + + setSelectedPhotos(selectedPhotos => [ + ...result, + ...selectedPhotos, + ]); + } + }); + }; + return ( { + + + + + + + { mediaPermissionResponse?.granted && photos.map((item) => { - const photoSize = targetWidth / 4 * 0.9; const isSelected = selectedPhotos.some(photo => photo.uri === item.uri); return ( @@ -241,42 +268,52 @@ export const CreateShortsButton: FC = () => { {hasPermission && ( - - {device && ( - - )} - - - - - - - + device + ? ( + + {device && ( + + )} + + + + + + + + + - - + ) + : ( + + + Please use physical device, this feature is not supported on simulator. + + + ) )} From fbe9bf2b5eafa0862442298faf7e150a5e611afe Mon Sep 17 00:00:00 2001 From: Caspian Date: Mon, 15 Jan 2024 21:08:17 +0000 Subject: [PATCH 5/5] fix: Addressed the swipe issue in CreateShortsButton on Android --- .changeset/long-eels-shout.md | 5 + src/components/CreateShortsButton.tsx | 302 ++++++++++++++------------ 2 files changed, 164 insertions(+), 143 deletions(-) create mode 100644 .changeset/long-eels-shout.md diff --git a/.changeset/long-eels-shout.md b/.changeset/long-eels-shout.md new file mode 100644 index 0000000..d0e7399 --- /dev/null +++ b/.changeset/long-eels-shout.md @@ -0,0 +1,5 @@ +--- +"xlog": patch +--- + +Addressed the swipe issue in "CreateShortsButton" on Android. diff --git a/src/components/CreateShortsButton.tsx b/src/components/CreateShortsButton.tsx index a59ef68..4a8c951 100644 --- a/src/components/CreateShortsButton.tsx +++ b/src/components/CreateShortsButton.tsx @@ -1,11 +1,11 @@ import { useState, type FC, useRef } from "react"; import type { ScrollView as RNScrollVIew } from "react-native"; -import { InteractionManager, StyleSheet } from "react-native"; -import { TouchableWithoutFeedback } from "react-native-gesture-handler"; +import { StyleSheet } from "react-native"; +import { PanGestureHandler, TouchableWithoutFeedback } from "react-native-gesture-handler"; import Animated, { Easing, runOnJS, interpolate, interpolateColor, useAnimatedStyle, useSharedValue, withTiming, LinearTransition, FadeInLeft } from "react-native-reanimated"; import { Camera, useCameraPermission, useCameraDevice } from "react-native-vision-camera"; -import { Album, Image as ImageIcon, Maximize2, Plus, X } from "@tamagui/lucide-icons"; +import { Image as ImageIcon, Maximize2, Plus, X } from "@tamagui/lucide-icons"; import { BlurView } from "expo-blur"; import { Image } from "expo-image"; import * as MediaLibrary from "expo-media-library"; @@ -13,7 +13,6 @@ import { Button, ScrollView, Stack, Text, View, XStack, YStack, useWindowDimensi import { IS_ANDROID } from "@/constants"; import { useColors } from "@/hooks/use-colors"; -import { useCreateShots } from "@/hooks/use-create-shots"; import { useIsLogin } from "@/hooks/use-is-login"; import { useRootNavigation } from "@/hooks/use-navigation"; import { usePickImages } from "@/hooks/use-pick-images"; @@ -195,150 +194,167 @@ export const CreateShortsButton: FC = () => { }; return ( - - - - - - - - - - - - - - - - { - mediaPermissionResponse?.granted && photos.map((item) => { - const isSelected = selectedPhotos.some(photo => photo.uri === item.uri); - - return ( - - { - if (isSelected) { - setSelectedPhotos(selectedPhotos.filter(({ uri }) => uri !== item.uri)); - return; - } - - setSelectedPhotos([...selectedPhotos, item]); - }} - > - - + + + + + + + + + + + + + + + + + { + mediaPermissionResponse?.granted && photos.map((item) => { + const isSelected = selectedPhotos.some(photo => photo.uri === item.uri); + + return ( + + { + if (isSelected) { + setSelectedPhotos(selectedPhotos.filter(({ uri }) => uri !== item.uri)); + return; + } + + setSelectedPhotos([...selectedPhotos, item]); + }} + > + + + + {isSelected && } + + + + + ); + }) + } + + + + + + {hasPermission && ( + device + ? ( + + {device && ( + + )} + + + + + + - {isSelected && } - - - - - ); - }) - } - - - - - - {hasPermission && ( - device - ? ( - - {device && ( - - )} - - - - - - - - - - - ) - : ( - - + backgroundColor={"white"} + width={25} + height={25} + /> + + + + ) + : ( + + Please use physical device, this feature is not supported on simulator. - - - ) - )} - - - - +