Compare commits

..

1 Commits

Author SHA1 Message Date
Dan Abramov c3dff3d75a [NOT FOR MERGE] Add instrumentation for module init 2023-10-27 02:05:49 +01:00
39 changed files with 313 additions and 567 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ The Authenticated Transfer Protocol ("AT Protocol" or "atproto") is a decentrali
- [Protocol Specifications](https://atproto.com/specs/atp)
- [Blogpost on self-authenticating data structures](https://blueskyweb.xyz/blog/3-6-2022-a-self-authenticating-social-protocol)
The Bluesky Social application encompasses a set of schemas and APIs built in the overall AT Protocol framework. The namespace for these "Lexicons" is `app.bsky.*`.
The Bluesky Social application encompases a set of schemas and APIs built in the overall AT Protocol framework. The namespace for these "Lexicons" is `app.bsky.*`.
## Contributions
+2 -2
View File
@@ -6,7 +6,7 @@ module.exports = function () {
slug: 'bluesky',
scheme: 'bluesky',
owner: 'blueskysocial',
version: '1.55.0',
version: '1.54.0',
runtimeVersion: {
policy: 'appVersion',
},
@@ -43,7 +43,7 @@ module.exports = function () {
backgroundColor: '#ffffff',
},
android: {
versionCode: 44,
versionCode: 43,
adaptiveIcon: {
foregroundImage: './assets/adaptive-icon.png',
backgroundColor: '#ffffff',
-9
View File
@@ -1,20 +1,11 @@
module.exports = function (api) {
api.cache(true)
const isTestEnv = process.env.NODE_ENV === 'test'
return {
presets: [
[
'babel-preset-expo',
{
lazyImports: true,
native: {
// We should be able to remove this after upgrading Expo
// to a version that includes https://github.com/expo/expo/pull/24672.
unstable_transformProfile: 'hermes-stable',
// Disable ESM -> CJS compilation because Metro takes care of it.
// However, we need it in Jest tests since those run without Metro.
disableImportExportTransform: !isTestEnv,
},
},
],
],
+2 -2
View File
@@ -1,13 +1,13 @@
# Testing instructions
### Using Maestro E2E tests
1. Install Maestro by following [these instructions](https://maestro.mobile.dev/getting-started/installing-maestro). This will help us run the E2E tests.
1. Install Maestro by following [these instuctions](https://maestro.mobile.dev/getting-started/installing-maestro). This will help us run the E2E tests.
2. You can write Maestro tests in `__e2e__/maestro` directory by creating a new `.yaml` file or by modifying an existing one.
3. You can also use [Maestro Studio](https://maestro.mobile.dev/getting-started/maestro-studio) which automatically generates commands by recording your actions on the app. Therefore, you can create realistic tests without having to manually write any code. Use the `maestro studio` command to start recording your actions.
### Using Flashlight for Performance Testing
1. Make sure Maestro is installed (optional: only for automated testing) by following the instructions above
1. Make sure Maestro is installed (optional: only for auomated testing) by following the instructions above
2. Install Flashlight by following [these instructions](https://docs.flashlight.dev/)
3. The simplest way to get started is by running `yarn perf:measure` which will run a live preview of the performance test results. You can [see a demo here](https://github.com/bamlab/flashlight/assets/4534323/4038a342-f145-4c3b-8cde-17949bf52612)
4. The `yarn perf:test:measure` will run the `scroll.yaml` test located in `__e2e__/maestro/scroll.yaml` and give the results in `.perf/results.json` which can be viewed by running `yarn:perf:results`
-10
View File
@@ -8,17 +8,7 @@ cfg.resolver.sourceExts = process.env.RN_SRC_EXT
cfg.transformer.getTransformOptions = async () => ({
transform: {
experimentalImportSupport: true,
inlineRequires: true,
nonInlinedRequires: [
// We can remove this option and rely on the default after
// https://github.com/facebook/metro/pull/1126 is released.
'React',
'react',
'react/jsx-dev-runtime',
'react/jsx-runtime',
'react-native',
],
},
})
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "bsky.app",
"version": "1.55.0",
"version": "1.54.0",
"private": true,
"scripts": {
"prepare": "is-ci || husky install",
@@ -31,7 +31,7 @@
"build:apk": "eas build -p android --profile dev-android-apk"
},
"dependencies": {
"@atproto/api": "^0.6.21",
"@atproto/api": "^0.6.20",
"@bam.tech/react-native-image-resizer": "^3.0.4",
"@braintree/sanitize-url": "^6.0.2",
"@emoji-mart/react": "^1.1.1",
@@ -102,6 +102,7 @@
"expo-system-ui": "~2.4.0",
"expo-updates": "~0.18.12",
"fast-text-encoding": "^1.0.6",
"graphemer": "^1.4.0",
"history": "^5.3.0",
"js-sha256": "^0.9.0",
"lande": "^1.0.10",
-14
View File
@@ -1,14 +0,0 @@
diff --git a/node_modules/babel-preset-expo/index.js b/node_modules/babel-preset-expo/index.js
index 2099ee3..2b9e092 100644
--- a/node_modules/babel-preset-expo/index.js
+++ b/node_modules/babel-preset-expo/index.js
@@ -105,7 +105,8 @@ module.exports = function (api, options = {}) {
],
],
plugins: [
- getObjectRestSpreadPlugin(),
+ // - dan: This will be disabled anyway when we upgrade Expo, but let's do it now.
+ // getObjectRestSpreadPlugin(),
...extraPlugins,
getAliasPlugin(),
[require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }],
-12
View File
@@ -1,12 +0,0 @@
diff --git a/node_modules/babel-preset-fbjs/plugins/inline-requires.js b/node_modules/babel-preset-fbjs/plugins/inline-requires.js
index b11fc83..e18661a 100644
--- a/node_modules/babel-preset-fbjs/plugins/inline-requires.js
+++ b/node_modules/babel-preset-fbjs/plugins/inline-requires.js
@@ -256,6 +256,7 @@ function getInlineableModule(path, state) {
return moduleName == null ||
state.ignoredRequires.has(moduleName) ||
+ moduleName.startsWith('@babel/runtime/') ||
isRequireInScope
? null
: { moduleName, requireFnName: fnName };
-44
View File
@@ -1,44 +0,0 @@
diff --git a/node_modules/metro/src/ModuleGraph/worker/JsFileWrapping.js b/node_modules/metro/src/ModuleGraph/worker/JsFileWrapping.js
index 48a1409..ef185c9 100644
--- a/node_modules/metro/src/ModuleGraph/worker/JsFileWrapping.js
+++ b/node_modules/metro/src/ModuleGraph/worker/JsFileWrapping.js
@@ -70,14 +70,19 @@ function wrapModule(
importDefaultName,
importAllName,
dependencyMapName,
- globalPrefix
+ globalPrefix,
+ moduleFactoryName
) {
const params = buildParameters(
importDefaultName,
importAllName,
dependencyMapName
);
- const factory = functionFromProgram(fileAst.program, params);
+ const factory = functionFromProgram(
+ fileAst.program,
+ params,
+ moduleFactoryName
+ );
const def = t.callExpression(t.identifier(`${globalPrefix}__d`), [factory]);
const ast = t.file(t.program([t.expressionStatement(def)]));
const requireName = renameRequires(ast);
@@ -107,7 +112,16 @@ function wrapJson(source, globalPrefix) {
"});",
].join("\n");
}
-function functionFromProgram(program, parameters) {
+const JS_INVALID_IDENT_RE = /[^a-zA-Z0-9$_]/g;
+function functionFromProgram(program, parameters, moduleFactoryName) {
+ let identifier;
+ if (typeof moduleFactoryName === "string" && moduleFactoryName !== "") {
+ // Keep the name readable so it shows up in profiler traces.
+ // Add an unlikely suffix to avoid collisions with the module code.
+ identifier = t.identifier(
+ `${moduleFactoryName.replace(JS_INVALID_IDENT_RE, "_")}__module_factory__`
+ );
+ }
return t.functionExpression(
undefined,
parameters.map(makeIdentifier),
+13 -36
View File
@@ -1,49 +1,26 @@
diff --git a/node_modules/metro-runtime/src/polyfills/require.js b/node_modules/metro-runtime/src/polyfills/require.js
index ce67cb4..eeeae84 100644
index ce67cb4..1a0a6a9 100644
--- a/node_modules/metro-runtime/src/polyfills/require.js
+++ b/node_modules/metro-runtime/src/polyfills/require.js
@@ -22,6 +22,13 @@ global.__c = clear;
global.__registerSegment = registerSegment;
var modules = clear();
+if (__DEV__) {
+ // Added by Dan for module init logging.
+ global.__INIT_LOGS__ = []
+ var initModuleCounter = 0
+ var initModuleStack = []
+}
+
// Don't use a Symbol here, it would pull in an extra polyfill with all sorts of
// additional stuff (e.g. Array.from).
const EMPTY = {};
@@ -303,7 +310,30 @@ function loadModuleImplementation(moduleId, module) {
@@ -280,6 +280,7 @@ function registerSegment(segmentId, moduleDefiner, moduleIds) {
});
}
}
+var moduleCount = 0;
function loadModuleImplementation(moduleId, module) {
if (!module && moduleDefinersBySegmentID.length > 0) {
const segmentId = definingSegmentByModuleID.get(moduleId) ?? 0;
@@ -303,7 +304,13 @@ function loadModuleImplementation(moduleId, module) {
throw module.error;
}
if (__DEV__) {
- var Systrace = requireSystrace();
+ // Added by Dan for module init logging.
+ var Systrace = {
+ beginEvent(label) {
+ let fullLabel = initModuleCounter++ + ' ' + label
+ global.__INIT_LOGS__.push(
+ ' '.repeat(initModuleStack.length) +
+ ' ENTER ' + fullLabel
+ )
+ initModuleStack.push({
+ fullLabel,
+ startTime: nativePerformanceNow(),
+ })
+ moduleCount++;
+ console.log('MODULE INIT', moduleCount, label)
+ },
+ endEvent() {
+ const res = initModuleStack.pop()
+ const fullLabel = res.fullLabel
+ const startTime = res.startTime
+ const timeElapsed = Math.round(nativePerformanceNow() - startTime)
+ global.__INIT_LOGS__.push(
+ ' '.repeat(initModuleStack.length) +
+ ' LEAVE ' + fullLabel + ' [' + timeElapsed + 'ms]',
+ )
+ }
+ endEvent() {}
+ };
var Refresh = requireRefresh();
}
@@ -1,41 +0,0 @@
diff --git a/node_modules/metro-transform-worker/src/index.js b/node_modules/metro-transform-worker/src/index.js
index 27d4cb3..fd71f47 100644
--- a/node_modules/metro-transform-worker/src/index.js
+++ b/node_modules/metro-transform-worker/src/index.js
@@ -190,6 +190,10 @@ async function transformJS(file, { config, options, projectRoot }) {
let dependencyMapName = "";
let dependencies;
let wrappedAst;
+ const minify =
+ options.minify &&
+ options.unstable_transformProfile !== "hermes-canary" &&
+ options.unstable_transformProfile !== "hermes-stable";
// If the module to transform is a script (meaning that is not part of the
// dependency graph and it code will just be prepended to the bundle modules),
@@ -229,19 +233,20 @@ async function transformJS(file, { config, options, projectRoot }) {
if (config.unstable_disableModuleWrapping === true) {
wrappedAst = ast;
} else {
+ let moduleFactoryName;
+ if (options.dev && !minify) {
+ moduleFactoryName = file.filename;
+ }
({ ast: wrappedAst } = JsFileWrapping.wrapModule(
ast,
importDefault,
importAll,
dependencyMapName,
- config.globalPrefix
+ config.globalPrefix,
+ moduleFactoryName
));
}
}
- const minify =
- options.minify &&
- options.unstable_transformProfile !== "hermes-canary" &&
- options.unstable_transformProfile !== "hermes-stable";
const reserved = [];
if (config.unstable_dependencyMapReservedName != null) {
reserved.push(config.unstable_dependencyMapReservedName);
+18 -9
View File
@@ -1,23 +1,22 @@
import 'react-native-url-polyfill/auto'
import 'lib/sentry' // must be near top
import React, {useState, useEffect} from 'react'
import 'lib/sentry' // must be relatively on top
import {withSentry} from 'lib/sentry'
import {Linking} from 'react-native'
import {RootSiblingParent} from 'react-native-root-siblings'
import * as SplashScreen from 'expo-splash-screen'
import {GestureHandlerRootView} from 'react-native-gesture-handler'
import {observer} from 'mobx-react-lite'
import {QueryClientProvider} from '@tanstack/react-query'
import 'view/icons'
import {withSentry} from 'lib/sentry'
import {ThemeProvider} from 'lib/ThemeContext'
import {s} from 'lib/styles'
import * as view from './view/index'
import {RootStoreModel, setupState, RootStoreProvider} from './state'
import {Shell} from 'view/shell'
import {Shell} from './view/shell'
import * as notifications from 'lib/notifications/notifications'
import * as analytics from 'lib/analytics/analytics'
import * as Toast from 'view/com/util/Toast'
import * as Toast from './view/com/util/Toast'
import {handleLink} from './Navigation'
import {QueryClientProvider} from '@tanstack/react-query'
import {queryClient} from 'lib/react-query'
import {TestCtrls} from 'view/com/testing/TestCtrls'
@@ -30,10 +29,20 @@ const App = observer(function AppImpl() {
// init
useEffect(() => {
view.setup()
setupState().then(store => {
setRootStore(store)
analytics.init(store)
notifications.init(store)
SplashScreen.hideAsync()
Linking.getInitialURL().then((url: string | null) => {
if (url) {
handleLink(url)
}
})
Linking.addEventListener('url', ({url}) => {
handleLink(url)
})
store.onSessionDropped(() => {
Toast.show('Sorry! Your session expired. Please log in again.')
})
+7 -9
View File
@@ -1,18 +1,15 @@
import 'lib/sentry' // must be near top
import React, {useState, useEffect} from 'react'
import {observer} from 'mobx-react-lite'
import {QueryClientProvider} from '@tanstack/react-query'
import 'lib/sentry' // must be relatively on top
import {SafeAreaProvider} from 'react-native-safe-area-context'
import {RootSiblingParent} from 'react-native-root-siblings'
import 'view/icons'
import * as view from './view/index'
import * as analytics from 'lib/analytics/analytics'
import {RootStoreModel, setupState, RootStoreProvider} from './state'
import {Shell} from 'view/shell/index'
import {ToastContainer} from 'view/com/util/Toast.web'
import {Shell} from './view/shell/index'
import {ToastContainer} from './view/com/util/Toast.web'
import {ThemeProvider} from 'lib/ThemeContext'
import {observer} from 'mobx-react-lite'
import {QueryClientProvider} from '@tanstack/react-query'
import {queryClient} from 'lib/react-query'
const App = observer(function AppImpl() {
@@ -22,6 +19,7 @@ const App = observer(function AppImpl() {
// init
useEffect(() => {
view.setup()
setupState().then(store => {
setRootStore(store)
analytics.init(store)
+41 -65
View File
@@ -1,6 +1,5 @@
import * as React from 'react'
import {StyleSheet} from 'react-native'
import * as SplashScreen from 'expo-splash-screen'
import {observer} from 'mobx-react-lite'
import {
NavigationContainer,
@@ -92,42 +91,42 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
<>
<Stack.Screen
name="NotFound"
getComponent={() => NotFoundScreen}
component={NotFoundScreen}
options={{title: title('Not Found')}}
/>
<Stack.Screen
name="Moderation"
getComponent={() => ModerationScreen}
component={ModerationScreen}
options={{title: title('Moderation')}}
/>
<Stack.Screen
name="ModerationMuteLists"
getComponent={() => ModerationMuteListsScreen}
component={ModerationMuteListsScreen}
options={{title: title('Mute Lists')}}
/>
<Stack.Screen
name="ModerationMutedAccounts"
getComponent={() => ModerationMutedAccounts}
component={ModerationMutedAccounts}
options={{title: title('Muted Accounts')}}
/>
<Stack.Screen
name="ModerationBlockedAccounts"
getComponent={() => ModerationBlockedAccounts}
component={ModerationBlockedAccounts}
options={{title: title('Blocked Accounts')}}
/>
<Stack.Screen
name="Settings"
getComponent={() => SettingsScreen}
component={SettingsScreen}
options={{title: title('Settings')}}
/>
<Stack.Screen
name="LanguageSettings"
getComponent={() => LanguageSettingsScreen}
component={LanguageSettingsScreen}
options={{title: title('Language Settings')}}
/>
<Stack.Screen
name="Profile"
getComponent={() => ProfileScreen}
component={ProfileScreen}
options={({route}) => ({
title: title(`@${route.params.name}`),
animation: 'none',
@@ -135,101 +134,101 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
/>
<Stack.Screen
name="ProfileFollowers"
getComponent={() => ProfileFollowersScreen}
component={ProfileFollowersScreen}
options={({route}) => ({
title: title(`People following @${route.params.name}`),
})}
/>
<Stack.Screen
name="ProfileFollows"
getComponent={() => ProfileFollowsScreen}
component={ProfileFollowsScreen}
options={({route}) => ({
title: title(`People followed by @${route.params.name}`),
})}
/>
<Stack.Screen
name="ProfileList"
getComponent={() => ProfileListScreen}
component={ProfileListScreen}
options={{title: title('Mute List')}}
/>
<Stack.Screen
name="PostThread"
getComponent={() => PostThreadScreen}
component={PostThreadScreen}
options={({route}) => ({title: title(`Post by @${route.params.name}`)})}
/>
<Stack.Screen
name="PostLikedBy"
getComponent={() => PostLikedByScreen}
component={PostLikedByScreen}
options={({route}) => ({title: title(`Post by @${route.params.name}`)})}
/>
<Stack.Screen
name="PostRepostedBy"
getComponent={() => PostRepostedByScreen}
component={PostRepostedByScreen}
options={({route}) => ({title: title(`Post by @${route.params.name}`)})}
/>
<Stack.Screen
name="CustomFeed"
getComponent={() => CustomFeedScreen}
component={CustomFeedScreen}
options={{title: title('Feed')}}
/>
<Stack.Screen
name="CustomFeedLikedBy"
getComponent={() => CustomFeedLikedByScreen}
component={CustomFeedLikedByScreen}
options={{title: title('Liked by')}}
/>
<Stack.Screen
name="Debug"
getComponent={() => DebugScreen}
component={DebugScreen}
options={{title: title('Debug')}}
/>
<Stack.Screen
name="Log"
getComponent={() => LogScreen}
component={LogScreen}
options={{title: title('Log')}}
/>
<Stack.Screen
name="Support"
getComponent={() => SupportScreen}
component={SupportScreen}
options={{title: title('Support')}}
/>
<Stack.Screen
name="PrivacyPolicy"
getComponent={() => PrivacyPolicyScreen}
component={PrivacyPolicyScreen}
options={{title: title('Privacy Policy')}}
/>
<Stack.Screen
name="TermsOfService"
getComponent={() => TermsOfServiceScreen}
component={TermsOfServiceScreen}
options={{title: title('Terms of Service')}}
/>
<Stack.Screen
name="CommunityGuidelines"
getComponent={() => CommunityGuidelinesScreen}
component={CommunityGuidelinesScreen}
options={{title: title('Community Guidelines')}}
/>
<Stack.Screen
name="CopyrightPolicy"
getComponent={() => CopyrightPolicyScreen}
component={CopyrightPolicyScreen}
options={{title: title('Copyright Policy')}}
/>
<Stack.Screen
name="AppPasswords"
getComponent={() => AppPasswords}
component={AppPasswords}
options={{title: title('App Passwords')}}
/>
<Stack.Screen
name="SavedFeeds"
getComponent={() => SavedFeeds}
component={SavedFeeds}
options={{title: title('Edit My Feeds')}}
/>
<Stack.Screen
name="PreferencesHomeFeed"
getComponent={() => PreferencesHomeFeed}
component={PreferencesHomeFeed}
options={{title: title('Home Feed Preferences')}}
/>
<Stack.Screen
name="PreferencesThreads"
getComponent={() => PreferencesThreads}
component={PreferencesThreads}
options={{title: title('Threads Preferences')}}
/>
</>
@@ -254,17 +253,14 @@ function TabsNavigator() {
backBehavior="initialRoute"
screenOptions={{headerShown: false, lazy: true}}
tabBar={tabBar}>
<Tab.Screen name="HomeTab" getComponent={() => HomeTabNavigator} />
<Tab.Screen name="SearchTab" getComponent={() => SearchTabNavigator} />
<Tab.Screen name="FeedsTab" getComponent={() => FeedsTabNavigator} />
<Tab.Screen name="HomeTab" component={HomeTabNavigator} />
<Tab.Screen name="SearchTab" component={SearchTabNavigator} />
<Tab.Screen name="FeedsTab" component={FeedsTabNavigator} />
<Tab.Screen
name="NotificationsTab"
getComponent={() => NotificationsTabNavigator}
/>
<Tab.Screen
name="MyProfileTab"
getComponent={() => MyProfileTabNavigator}
component={NotificationsTabNavigator}
/>
<Tab.Screen name="MyProfileTab" component={MyProfileTabNavigator} />
</Tab.Navigator>
)
}
@@ -281,7 +277,7 @@ function HomeTabNavigator() {
animationDuration: 250,
contentStyle,
}}>
<HomeTab.Screen name="Home" getComponent={() => HomeScreen} />
<HomeTab.Screen name="Home" component={HomeScreen} />
{commonScreens(HomeTab)}
</HomeTab.Navigator>
)
@@ -298,7 +294,7 @@ function SearchTabNavigator() {
animationDuration: 250,
contentStyle,
}}>
<SearchTab.Screen name="Search" getComponent={() => SearchScreen} />
<SearchTab.Screen name="Search" component={SearchScreen} />
{commonScreens(SearchTab as typeof HomeTab)}
</SearchTab.Navigator>
)
@@ -315,7 +311,7 @@ function FeedsTabNavigator() {
animationDuration: 250,
contentStyle,
}}>
<FeedsTab.Screen name="Feeds" getComponent={() => FeedsScreen} />
<FeedsTab.Screen name="Feeds" component={FeedsScreen} />
{commonScreens(FeedsTab as typeof HomeTab)}
</FeedsTab.Navigator>
)
@@ -334,7 +330,7 @@ function NotificationsTabNavigator() {
}}>
<NotificationsTab.Screen
name="Notifications"
getComponent={() => NotificationsScreen}
component={NotificationsScreen}
/>
{commonScreens(NotificationsTab as typeof HomeTab)}
</NotificationsTab.Navigator>
@@ -356,7 +352,7 @@ const MyProfileTabNavigator = observer(function MyProfileTabNavigatorImpl() {
<MyProfileTab.Screen
name="MyProfile"
// @ts-ignore // TODO: fix this broken type in ProfileScreen
getComponent={() => ProfileScreen}
component={ProfileScreen}
initialParams={{
name: store.me.did,
}}
@@ -387,22 +383,22 @@ const FlatNavigator = observer(function FlatNavigatorImpl() {
}}>
<Flat.Screen
name="Home"
getComponent={() => HomeScreen}
component={HomeScreen}
options={{title: title('Home')}}
/>
<Flat.Screen
name="Search"
getComponent={() => SearchScreen}
component={SearchScreen}
options={{title: title('Search')}}
/>
<Flat.Screen
name="Feeds"
getComponent={() => FeedsScreen}
component={FeedsScreen}
options={{title: title('Feeds')}}
/>
<Flat.Screen
name="Notifications"
getComponent={() => NotificationsScreen}
component={NotificationsScreen}
options={{title: title('Notifications')}}
/>
{commonScreens(Flat as typeof HomeTab, unreadCountLabel)}
@@ -466,14 +462,6 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
linking={LINKING}
theme={theme}
onReady={() => {
SplashScreen.hideAsync()
const initMs = Math.round(
// @ts-ignore Emitted by Metro in the bundle prelude
performance.now() - global.__BUNDLE_START_TIME__,
)
console.log(`Time to first paint: ${initMs} ms`)
logModuleInitTrace()
// Register the navigation container with the Sentry instrumentation (only works on native)
if (isNative) {
const routingInstrumentation = getRoutingInstrumentation()
@@ -587,18 +575,6 @@ const styles = StyleSheet.create({
},
})
function logModuleInitTrace() {
if (__DEV__) {
// This log is noisy, so keep false committed
const shouldLog = false
// Relies on our patch to polyfill.js in metro-runtime
const initLogs = (global as any).__INIT_LOGS__
if (shouldLog && Array.isArray(initLogs)) {
console.log(initLogs.join('\n'))
}
}
}
export {
navigate,
resetToTab,
+1 -47
View File
@@ -35,57 +35,11 @@ export class AuthorFeedAPI implements FeedAPI {
this.cursor = res.data.cursor
return {
cursor: res.data.cursor,
feed: this._filter(res.data.feed),
feed: res.data.feed,
}
}
return {
feed: [],
}
}
_filter(feed: AppBskyFeedDefs.FeedViewPost[]) {
if (this.params.filter === 'posts_no_replies') {
return feed.filter(post => {
const isReply = post.reply
const isRepost = AppBskyFeedDefs.isReasonRepost(post.reason)
if (!isReply) return true
if (isRepost) return true
return isReply && isAuthorReplyChain(this.params.actor, post, feed)
})
}
return feed
}
}
function isAuthorReplyChain(
actor: string,
post: AppBskyFeedDefs.FeedViewPost,
posts: AppBskyFeedDefs.FeedViewPost[],
): boolean {
// current post is by a different user (shouldn't happen)
if (post.post.author.handle !== actor) return false
const replyParent = post.reply?.parent
if (AppBskyFeedDefs.isPostView(replyParent)) {
// reply parent is by a different user
if (replyParent.author.handle !== actor) return false
// A top-level post that matches the parent of the current post.
const parentPost = posts.find(p => p.post.uri === replyParent.uri)
/*
* Either we haven't fetched the parent at the top level, or the only
* record we have is on feedItem.reply.parent, which we've already checked
* above.
*/
if (!parentPost) return true
// Walk up to parent
return isAuthorReplyChain(actor, parentPost, posts)
}
// Just default to showing it
return true
}
-1
View File
@@ -147,4 +147,3 @@ export const HITSLOP_10 = createHitslop(10)
export const HITSLOP_20 = createHitslop(20)
export const HITSLOP_30 = createHitslop(30)
export const BACK_HITSLOP = HITSLOP_30
export const MAX_POST_LINES = 25
+1 -1
View File
@@ -1,4 +1,4 @@
export function bskyTitle(page: string, unreadCountLabel?: string) {
const unreadPrefix = unreadCountLabel ? `(${unreadCountLabel}) ` : ''
return `${unreadPrefix}${page} Bluesky`
return `${unreadPrefix}${page} - Bluesky`
}
-5
View File
@@ -32,8 +32,3 @@ export function toHashCode(str: string, seed = 0): number {
return 4294967296 * (2097151 & h2) + (h1 >>> 0)
}
export function countLines(str: string | undefined): number {
if (!str) return 0
return str.match(/\n/g)?.length ?? 0
}
+16
View File
@@ -1,4 +1,5 @@
import 'fast-text-encoding'
import Graphemer from 'graphemer'
// @ts-ignore no decl -prf
import findLast from 'array.prototype.findlast'
export {}
@@ -53,3 +54,18 @@ globalThis.atob = (str: string): string => {
}
return result
}
const splitter = new Graphemer()
globalThis.Intl = globalThis.Intl || {}
// @ts-ignore we're polyfilling -prf
globalThis.Intl.Segmenter =
// @ts-ignore we're polyfilling -prf
globalThis.Intl.Segmenter ||
class Segmenter {
constructor() {}
// NOTE
// this is not a precisely correct polyfill but it's sufficient for our needs
// -prf
segment = splitter.iterateGraphemes
}
+13
View File
@@ -6,3 +6,16 @@ findLast.shim()
// @ts-ignore whatever typescript wants to complain about here, I dont care about -prf
window.setImmediate = (cb: () => void) => setTimeout(cb, 0)
// @ts-ignore not on the TS signature due to bad support -prf
if (!globalThis.Intl?.Segmenter) {
// NOTE loading as a separate script to reduce main bundle size, as this is only needed in FF -prf
const script = document.createElement('script')
script.setAttribute('src', '/static/js/intl-segmenter-polyfill.min.js')
document.head.appendChild(script)
// loading emoji mart data
const emojiMartScript = document.createElement('script')
emojiMartScript.setAttribute('src', '/static/js/emoji-mart-data.js')
document.head.appendChild(emojiMartScript)
}
+4 -10
View File
@@ -11,7 +11,6 @@ import {TextInput} from '../util/TextInput'
import {Policies} from './Policies'
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
import {useStores} from 'state/index'
import {isWeb} from 'platform/detection'
/** STEP 2: Your account
* @field Invite code or waitlist
@@ -61,11 +60,10 @@ export const Step2 = observer(function Step2Impl({
Don't have an invite code?{' '}
<TouchableWithoutFeedback
onPress={onPressWaitlist}
accessibilityLabel="Join the waitlist."
accessibilityHint="">
<View style={styles.touchable}>
<Text style={pal.link}>Join the waitlist.</Text>
</View>
accessibilityRole="button"
accessibilityLabel="Waitlist"
accessibilityHint="Opens Bluesky waitlist form">
<Text style={pal.link}>Join the waitlist.</Text>
</TouchableWithoutFeedback>
</Text>
) : (
@@ -153,8 +151,4 @@ const styles = StyleSheet.create({
borderRadius: 6,
paddingVertical: 14,
},
// @ts-expect-error: Suppressing error due to incomplete `ViewStyle` type definition in react-native-web, missing `cursor` prop as discussed in https://github.com/necolas/react-native-web/issues/832.
touchable: {
...(isWeb && {cursor: 'pointer'}),
},
})
@@ -72,9 +72,8 @@ export function EmojiPicker({close}: {close: () => void}) {
},
]}>
<Picker
data={async () => {
return (await import('./EmojiPickerData.json')).default
}}
// @ts-ignore we set emojiMartData in `emoji-mart-data.js` file
data={window.emojiMartData}
onEmojiSelect={onInsert}
autoFocus={false}
/>
+13 -21
View File
@@ -25,11 +25,6 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext'
import {useAnalytics} from 'lib/analytics/analytics'
import {cleanError, isNetworkError} from 'lib/strings/errors'
import Animated, {FadeOut} from 'react-native-reanimated'
import {isWeb} from 'platform/detection'
const AnimatedTouchableOpacity =
Animated.createAnimatedComponent(TouchableOpacity)
export const snapPoints = ['fullscreen']
@@ -149,7 +144,7 @@ export function Component({
])
return (
<KeyboardAvoidingView style={s.flex1} behavior="height">
<KeyboardAvoidingView behavior="height">
<ScrollView style={[pal.view]} testID="editProfileModal">
<Text style={[styles.title, pal.text]}>Edit my profile</Text>
<View style={styles.photos}>
@@ -224,21 +219,18 @@ export function Component({
</LinearGradient>
</TouchableOpacity>
)}
{!isProcessing && (
<AnimatedTouchableOpacity
exiting={!isWeb ? FadeOut : undefined}
testID="editProfileCancelBtn"
style={s.mt5}
onPress={onPressCancel}
accessibilityRole="button"
accessibilityLabel="Cancel profile editing"
accessibilityHint=""
onAccessibilityEscape={onPressCancel}>
<View style={[styles.btn]}>
<Text style={[s.black, s.bold, pal.text]}>Cancel</Text>
</View>
</AnimatedTouchableOpacity>
)}
<TouchableOpacity
testID="editProfileCancelBtn"
style={s.mt5}
onPress={onPressCancel}
accessibilityRole="button"
accessibilityLabel="Cancel profile editing"
accessibilityHint=""
onAccessibilityEscape={onPressCancel}>
<View style={[styles.btn]}>
<Text style={[s.black, s.bold, pal.text]}>Cancel</Text>
</View>
</TouchableOpacity>
</View>
</ScrollView>
</KeyboardAvoidingView>
+2 -19
View File
@@ -8,7 +8,7 @@ import {
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {PostThreadItemModel} from 'state/models/content/post-thread-item'
import {Link, TextLink} from '../util/Link'
import {Link} from '../util/Link'
import {RichText} from '../util/text/RichText'
import {Text} from '../util/text/Text'
import {PostDropdownBtn} from '../util/forms/PostDropdownBtn'
@@ -18,7 +18,7 @@ import {s} from 'lib/styles'
import {niceDate} from 'lib/strings/time'
import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles'
import {countLines, pluralize} from 'lib/strings/helpers'
import {pluralize} from 'lib/strings/helpers'
import {isEmbedByEmbedder} from 'lib/embeds'
import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers'
import {useStores} from 'state/index'
@@ -35,7 +35,6 @@ import {formatCount} from '../util/numeric/format'
import {TimeElapsed} from 'view/com/util/TimeElapsed'
import {makeProfileLink} from 'lib/routes/links'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {MAX_POST_LINES} from 'lib/constants'
export const PostThreadItem = observer(function PostThreadItem({
item,
@@ -51,9 +50,6 @@ export const PostThreadItem = observer(function PostThreadItem({
const pal = usePalette('default')
const store = useStores()
const [deleted, setDeleted] = React.useState(false)
const [limitLines, setLimitLines] = React.useState(
countLines(item.richText?.text) >= MAX_POST_LINES,
)
const styles = useStyles()
const record = item.postRecord
const hasEngagement = item.post.likeCount || item.post.repostCount
@@ -155,10 +151,6 @@ export const PostThreadItem = observer(function PostThreadItem({
)
}, [item, store])
const onPressShowMore = React.useCallback(() => {
setLimitLines(false)
}, [setLimitLines])
if (!record) {
return <ErrorMessage message="Invalid or unsupported post record" />
}
@@ -497,18 +489,9 @@ export const PostThreadItem = observer(function PostThreadItem({
richText={item.richText}
style={[pal.text, s.flex1]}
lineHeight={1.3}
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
/>
</View>
) : undefined}
{limitLines ? (
<TextLink
text="Show More"
style={pal.link}
onPress={onPressShowMore}
href="#"
/>
) : undefined}
{item.post.embed && (
<ContentHider
style={styles.contentHider}
+2 -19
View File
@@ -14,7 +14,7 @@ import {AtUri} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {PostThreadModel} from 'state/models/content/post-thread'
import {PostThreadItemModel} from 'state/models/content/post-thread-item'
import {Link, TextLink} from '../util/Link'
import {Link} from '../util/Link'
import {UserInfoText} from '../util/UserInfoText'
import {PostMeta} from '../util/PostMeta'
import {PostEmbeds} from '../util/post-embeds'
@@ -30,8 +30,6 @@ import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {getTranslatorLink} from '../../../locale/helpers'
import {makeProfileLink} from 'lib/routes/links'
import {MAX_POST_LINES} from 'lib/constants'
import {countLines} from 'lib/strings/helpers'
export const Post = observer(function PostImpl({
view,
@@ -105,9 +103,7 @@ const PostLoaded = observer(function PostLoadedImpl({
}) {
const pal = usePalette('default')
const store = useStores()
const [limitLines, setLimitLines] = React.useState(
countLines(item.richText?.text) >= MAX_POST_LINES,
)
const itemUri = item.post.uri
const itemCid = item.post.cid
const itemUrip = new AtUri(item.post.uri)
@@ -186,10 +182,6 @@ const PostLoaded = observer(function PostLoadedImpl({
)
}, [item, setDeleted, store])
const onPressShowMore = React.useCallback(() => {
setLimitLines(false)
}, [setLimitLines])
return (
<Link href={itemHref} style={[styles.outer, pal.view, pal.border, style]}>
{showReplyLine && <View style={styles.replyLine} />}
@@ -247,19 +239,10 @@ const PostLoaded = observer(function PostLoadedImpl({
type="post-text"
richText={item.richText}
lineHeight={1.3}
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
style={s.flex1}
/>
</View>
) : undefined}
{limitLines ? (
<TextLink
text="Show More"
style={pal.link}
onPress={onPressShowMore}
href="#"
/>
) : undefined}
{item.post.embed ? (
<ContentHider
moderation={item.moderation.embed}
+3 -21
View File
@@ -9,7 +9,7 @@ import {
} from '@fortawesome/react-native-fontawesome'
import {PostsFeedItemModel} from 'state/models/feeds/post'
import {FeedSourceInfo} from 'lib/api/feed/types'
import {Link, TextLinkOnWebOnly, TextLink} from '../util/Link'
import {Link, DesktopWebTextLink} from '../util/Link'
import {Text} from '../util/text/Text'
import {UserInfoText} from '../util/UserInfoText'
import {PostMeta} from '../util/PostMeta'
@@ -30,8 +30,6 @@ import {sanitizeHandle} from 'lib/strings/handles'
import {getTranslatorLink} from '../../../locale/helpers'
import {makeProfileLink} from 'lib/routes/links'
import {isEmbedByEmbedder} from 'lib/embeds'
import {MAX_POST_LINES} from 'lib/constants'
import {countLines} from 'lib/strings/helpers'
export const FeedItem = observer(function FeedItemImpl({
item,
@@ -51,9 +49,6 @@ export const FeedItem = observer(function FeedItemImpl({
const pal = usePalette('default')
const {track} = useAnalytics()
const [deleted, setDeleted] = useState(false)
const [limitLines, setLimitLines] = useState(
countLines(item.richText?.text) >= MAX_POST_LINES,
)
const record = item.postRecord
const itemUri = item.post.uri
const itemCid = item.post.cid
@@ -141,10 +136,6 @@ export const FeedItem = observer(function FeedItemImpl({
)
}, [track, item, setDeleted, store])
const onPressShowMore = React.useCallback(() => {
setLimitLines(false)
}, [setLimitLines])
const outerStyles = [
styles.outer,
pal.view,
@@ -198,7 +189,7 @@ export const FeedItem = observer(function FeedItemImpl({
lineHeight={1.2}
numberOfLines={1}>
From{' '}
<TextLinkOnWebOnly
<DesktopWebTextLink
type="sm-bold"
style={pal.textLight}
lineHeight={1.2}
@@ -229,7 +220,7 @@ export const FeedItem = observer(function FeedItemImpl({
lineHeight={1.2}
numberOfLines={1}>
Reposted by{' '}
<TextLinkOnWebOnly
<DesktopWebTextLink
type="sm-bold"
style={pal.textLight}
lineHeight={1.2}
@@ -316,19 +307,10 @@ export const FeedItem = observer(function FeedItemImpl({
type="post-text"
richText={item.richText}
lineHeight={1.3}
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
style={s.flex1}
/>
</View>
) : undefined}
{limitLines ? (
<TextLink
text="Show More"
style={pal.link}
onPress={onPressShowMore}
href="#"
/>
) : undefined}
{item.post.embed ? (
<ContentHider
testID="contentHider-embed"
+1 -5
View File
@@ -119,11 +119,7 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({
const [showSuggestedFollows, setShowSuggestedFollows] = React.useState(false)
const onPressBack = React.useCallback(() => {
if (navigation.canGoBack()) {
navigation.goBack()
} else {
navigation.navigate('Home')
}
navigation.goBack()
}, [navigation])
const onPressAvi = React.useCallback(() => {
+8 -5
View File
@@ -27,10 +27,11 @@ import {
isExternalUrl,
linkRequiresWarning,
} from 'lib/strings/url-helpers'
import {isAndroid, isWeb} from 'platform/detection'
import {isAndroid} from 'platform/detection'
import {sanitizeUrl} from '@braintree/sanitize-url'
import {PressableWithHover} from './PressableWithHover'
import FixedTouchableHighlight from '../pager/FixedTouchableHighlight'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
type Event =
| React.MouseEvent<HTMLAnchorElement, MouseEvent>
@@ -221,7 +222,7 @@ export const TextLink = memo(function TextLink({
/**
* Only acts as a link on desktop web
*/
interface TextLinkOnWebOnlyProps extends TextProps {
interface DesktopWebTextLinkProps extends TextProps {
testID?: string
type?: TypographyVariant
style?: StyleProp<TextStyle>
@@ -234,7 +235,7 @@ interface TextLinkOnWebOnlyProps extends TextProps {
accessibilityHint?: string
title?: string
}
export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({
export const DesktopWebTextLink = memo(function DesktopWebTextLink({
testID,
type = 'md',
style,
@@ -243,8 +244,10 @@ export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({
numberOfLines,
lineHeight,
...props
}: TextLinkOnWebOnlyProps) {
if (isWeb) {
}: DesktopWebTextLinkProps) {
const {isDesktop} = useWebMediaQueries()
if (isDesktop) {
return (
<TextLink
testID={testID}
+3 -3
View File
@@ -1,7 +1,7 @@
import React from 'react'
import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native'
import {Text} from './text/Text'
import {TextLinkOnWebOnly} from './Link'
import {DesktopWebTextLink} from './Link'
import {niceDate} from 'lib/strings/time'
import {usePalette} from 'lib/hooks/usePalette'
import {TypographyVariant} from 'lib/ThemeContext'
@@ -47,7 +47,7 @@ export const PostMeta = observer(function PostMetaImpl(opts: PostMetaOpts) {
</View>
)}
<View style={styles.maxWidth}>
<TextLinkOnWebOnly
<DesktopWebTextLink
type={opts.displayNameType || 'lg-bold'}
style={[pal.text, opts.displayNameStyle]}
numberOfLines={1}
@@ -78,7 +78,7 @@ export const PostMeta = observer(function PostMetaImpl(opts: PostMetaOpts) {
)}
<TimeElapsed timestamp={opts.timestamp}>
{({timeElapsed}) => (
<TextLinkOnWebOnly
<DesktopWebTextLink
type="md"
style={pal.textLight}
lineHeight={1.2}
+2 -2
View File
@@ -1,7 +1,7 @@
import React, {useState, useEffect} from 'react'
import {AppBskyActorGetProfile as GetProfile} from '@atproto/api'
import {StyleProp, StyleSheet, TextStyle} from 'react-native'
import {TextLinkOnWebOnly} from './Link'
import {DesktopWebTextLink} from './Link'
import {Text} from './text/Text'
import {LoadingPlaceholder} from './LoadingPlaceholder'
import {useStores} from 'state/index'
@@ -65,7 +65,7 @@ export function UserInfoText({
)
} else if (profile) {
inner = (
<TextLinkOnWebOnly
<DesktopWebTextLink
type={type}
style={style}
lineHeight={1.2}
+6 -6
View File
@@ -52,20 +52,20 @@ export function AutoSizedImage({
if (onPress || onLongPress || onPressIn) {
return (
// disable a11y rule because in this case we want the tags on the image (#1640)
// eslint-disable-next-line react-native-a11y/has-valid-accessibility-descriptors
<Pressable
onPress={onPress}
onLongPress={onLongPress}
onPressIn={onPressIn}
style={[styles.container, style]}>
style={[styles.container, style]}
accessible={true}
accessibilityRole="button"
accessibilityLabel={alt || 'Image'}
accessibilityHint="Tap to view fully">
<Image
style={[styles.image, {aspectRatio}]}
source={uri}
accessible={true} // Must set for `accessibilityLabel` to work
accessible={false} // Must set for `accessibilityLabel` to work
accessibilityIgnoresInvertColors
accessibilityLabel={alt}
accessibilityHint="Tap to view fully"
/>
{children}
</Pressable>
-1
View File
@@ -52,7 +52,6 @@ export function RichText({
testID={testID}
type={type}
style={[style, pal.text, lineHeightStyle]}
numberOfLines={numberOfLines}
// @ts-ignore web only -prf
dataSet={WORD_WRAP}>
{text}
+102 -100
View File
@@ -99,103 +99,105 @@ import {faX} from '@fortawesome/free-solid-svg-icons/faX'
import {faXmark} from '@fortawesome/free-solid-svg-icons/faXmark'
import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown'
library.add(
faAddressCard,
faAngleDown,
faAngleLeft,
faAngleRight,
faAngleUp,
faArrowLeft,
faArrowRight,
faArrowUp,
faArrowDown,
faArrowRightFromBracket,
faArrowUpFromBracket,
faArrowUpRightFromSquare,
faArrowRotateLeft,
faArrowTrendUp,
faArrowsRotate,
faAt,
faBan,
faBars,
faBell,
farBell,
faBookmark,
farBookmark,
farCalendar,
faCamera,
faCheck,
faChevronRight,
faCircle,
faCircleCheck,
farCircleCheck,
faCircleExclamation,
faCircleUser,
faCircleDot,
faClone,
farClone,
faComment,
faCommentSlash,
faComments,
faCompass,
faEllipsis,
faEnvelope,
faEye,
faExclamation,
farEyeSlash,
faFaceSmile,
faFire,
faFlask,
faFloppyDisk,
faGear,
faGlobe,
faHand,
farHand,
faHeart,
fasHeart,
faHouse,
faImage,
farImage,
faInfo,
faLanguage,
faLink,
faList,
faListUl,
faLock,
faMagnifyingGlass,
faMessage,
faNoteSticky,
faPaste,
faPause,
faPen,
faPenNib,
faPenToSquare,
faPlay,
faPlus,
faQuoteLeft,
faReply,
faRetweet,
faRss,
faSatelliteDish,
faShare,
faShareFromSquare,
faShield,
faSignal,
faSliders,
faSquare,
faSquareCheck,
faSquarePlus,
faUser,
faUsers,
faUserCheck,
faUserSlash,
faUserPlus,
faUserXmark,
faUsersSlash,
faThumbtack,
faTicket,
faTrashCan,
faX,
faXmark,
faChevronDown,
)
export function setup() {
library.add(
faAddressCard,
faAngleDown,
faAngleLeft,
faAngleRight,
faAngleUp,
faArrowLeft,
faArrowRight,
faArrowUp,
faArrowDown,
faArrowRightFromBracket,
faArrowUpFromBracket,
faArrowUpRightFromSquare,
faArrowRotateLeft,
faArrowTrendUp,
faArrowsRotate,
faAt,
faBan,
faBars,
faBell,
farBell,
faBookmark,
farBookmark,
farCalendar,
faCamera,
faCheck,
faChevronRight,
faCircle,
faCircleCheck,
farCircleCheck,
faCircleExclamation,
faCircleUser,
faCircleDot,
faClone,
farClone,
faComment,
faCommentSlash,
faComments,
faCompass,
faEllipsis,
faEnvelope,
faEye,
faExclamation,
farEyeSlash,
faFaceSmile,
faFire,
faFlask,
faFloppyDisk,
faGear,
faGlobe,
faHand,
farHand,
faHeart,
fasHeart,
faHouse,
faImage,
farImage,
faInfo,
faLanguage,
faLink,
faList,
faListUl,
faLock,
faMagnifyingGlass,
faMessage,
faNoteSticky,
faPaste,
faPause,
faPen,
faPenNib,
faPenToSquare,
faPlay,
faPlus,
faQuoteLeft,
faReply,
faRetweet,
faRss,
faSatelliteDish,
faShare,
faShareFromSquare,
faShield,
faSignal,
faSliders,
faSquare,
faSquareCheck,
faSquarePlus,
faUser,
faUsers,
faUserCheck,
faUserSlash,
faUserPlus,
faUserXmark,
faUsersSlash,
faThumbtack,
faTicket,
faTrashCan,
faX,
faXmark,
faChevronDown,
)
}
+4 -8
View File
@@ -460,22 +460,18 @@ const styles = StyleSheet.create({
justifyContent: 'center',
width: 140,
borderRadius: 24,
paddingTop: 10,
paddingBottom: 12, // visually aligns the text vertically inside the button
paddingLeft: 16,
paddingRight: 18, // looks nicer like this
paddingVertical: 10,
paddingHorizontal: 16,
backgroundColor: colors.blue3,
marginLeft: 12,
marginTop: 20,
marginBottom: 10,
gap: 8,
},
newPostBtnIconWrapper: {
marginTop: 2, // aligns the icon visually with the text
},
newPostBtnIconWrapper: {},
newPostBtnLabel: {
color: colors.white,
fontSize: 16,
fontWeight: '600',
fontWeight: 'bold',
},
})
+2 -5
View File
@@ -21,10 +21,7 @@ import {usePalette} from 'lib/hooks/usePalette'
import * as backHandler from 'lib/routes/back-handler'
import {RoutesContainer, TabsNavigator} from '../../Navigation'
import {isStateAtTabRoot} from 'lib/routes/helpers'
import {
SafeAreaProvider,
initialWindowMetrics,
} from 'react-native-safe-area-context'
import {SafeAreaProvider} from 'react-native-safe-area-context'
import {useOTAUpdate} from 'lib/hooks/useOTAUpdate'
const ShellInner = observer(function ShellInnerImpl() {
@@ -90,7 +87,7 @@ export const Shell: React.FC = observer(function ShellImpl() {
const pal = usePalette('default')
const theme = useTheme()
return (
<SafeAreaProvider initialMetrics={initialWindowMetrics} style={pal.view}>
<SafeAreaProvider style={pal.view}>
<View testID="mobileShellView" style={[styles.outerContainer, pal.view]}>
<StatusBar style={theme.colorScheme === 'dark' ? 'light' : 'dark'} />
<RoutesContainer>
+11
View File
@@ -0,0 +1,11 @@
async function grabEmojiData() {
try {
const response = await fetch('/static/emojis.2023.json')
const emojiMartData = await response.json()
window.emojiMartData = emojiMartData
} catch (error) {
console.warn(`Failed to load emojis`)
}
}
grabEmojiData()
File diff suppressed because one or more lines are too long
+28 -29
View File
@@ -47,19 +47,18 @@
tlds "^1.234.0"
typed-emitter "^2.1.0"
"@atproto/api@^0.6.21":
version "0.6.21"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.6.21.tgz#6e5b00facf46f2556d9766290341aae7e6ef75c8"
integrity sha512-ZWVEnLhZ8nonkCVzeFgdUFZhTOUtPxvicZFuttvb2G2Q5u43RmJ5qXXZvox/S9XQEw7TubG6Jza1mesH7CjfVQ==
"@atproto/api@^0.6.20":
version "0.6.20"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.6.20.tgz#3a7eda60d73a5d5b6938e2dd016c24a7ba180c83"
integrity sha512-+peoKgkaxbglXQg9qEZcZIvyWm39yj0+syV3TBDrz5cWK4OIsdOyYBg2iISy+jvB5RzEUMe2WvOojP6Nq34mOg==
dependencies:
"@atproto/common-web" "^0.2.2"
"@atproto/lexicon" "^0.2.3"
"@atproto/syntax" "^0.1.3"
"@atproto/xrpc" "^0.3.3"
"@atproto/common-web" "^0.2.1"
"@atproto/lexicon" "^0.2.2"
"@atproto/syntax" "^0.1.2"
"@atproto/xrpc" "^0.3.2"
multiformats "^9.9.0"
tlds "^1.234.0"
typed-emitter "^2.1.0"
zod "^3.21.4"
"@atproto/bsky@^0.0.5":
version "0.0.5"
@@ -106,10 +105,10 @@
uint8arrays "3.0.0"
zod "^3.21.4"
"@atproto/common-web@^0.2.2":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.2.2.tgz#decc12584c84f3c34d077d1afe7442bfc21bcf6c"
integrity sha512-XWZHj82kWGdhm0y6e/DxLA5qK0LPHTozfPCH2ws1B/Qh9Hh5DD/gakvlIRT1FouwPM+hWcs8YHVJ8bjnehrhHA==
"@atproto/common-web@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.2.1.tgz#97412cb241321fc6c56a2b8c0b2416b3240caf50"
integrity sha512-5AoDKkKz7JhXSiicjhPihA/MJMlSuTQ9Aed9fflPuoTuT6C3aXbxaUZEcqqipSwlCfGpOzPmJmWJjMWWsYr2ew==
dependencies:
graphemer "^1.4.0"
multiformats "^9.9.0"
@@ -220,13 +219,13 @@
multiformats "^9.9.0"
zod "^3.21.4"
"@atproto/lexicon@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.2.3.tgz#3f8ba24187d5628ec06b1bdbec90747f7cdc0948"
integrity sha512-1xUs0KNw4CopWI5HSlLYZ8UHW5nb6V7sldO5OPONiEVKjETrqqjfopezloYAIBNrekUNXwd1pbp05afkAxW5og==
"@atproto/lexicon@^0.2.2":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.2.2.tgz#938a39482ff41c6a908f4ad43274adba595f3643"
integrity sha512-CvmjaSDavHMOJTuNYE8VjYhL7TVxBYV8QSWh2jHCpzfmj02DvVD9UBIfnoVv67POJkEtWXddjoV9beaIbaq/Xg==
dependencies:
"@atproto/common-web" "^0.2.2"
"@atproto/syntax" "^0.1.3"
"@atproto/common-web" "^0.2.1"
"@atproto/syntax" "^0.1.2"
iso-datestring-validator "^2.2.2"
multiformats "^9.9.0"
zod "^3.21.4"
@@ -298,12 +297,12 @@
dependencies:
"@atproto/common-web" "^0.2.0"
"@atproto/syntax@^0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.1.3.tgz#5cafd5d82eee939fde06a2eacd11b264fb2f3b13"
integrity sha512-Xbw+Rx15puW8wZ/ro40nAQVc7ymPqcGOinVt8Jxi+lcY/1iKpID9a86E6ZOzvw0ncFKONwILYk1+xGeUT6OUNA==
"@atproto/syntax@^0.1.2":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.1.2.tgz#417366d36b53ecf29d9d1f6e35179b1f3feef95b"
integrity sha512-n6VSuccMGouwftCvZBq9WNwI0qYCMOH/lTHSV+/dT232lX7pIrqisOlErUSBoOJ49B1Wxy1DjeeBS26ap9SsGQ==
dependencies:
"@atproto/common-web" "^0.2.2"
"@atproto/common-web" "^0.2.1"
"@atproto/xrpc-server@^0.3.1":
version "0.3.1"
@@ -330,12 +329,12 @@
"@atproto/lexicon" "^0.2.1"
zod "^3.21.4"
"@atproto/xrpc@^0.3.3":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.3.3.tgz#05f1c431ccd366e950637b93acca85faa249f52b"
integrity sha512-o0VUrUGu5Y/1F+ujZKIJYpuHdfXaIDacxuiq2IjwR2rbHXlefh+9FJy5XNkq4do+jMj7U+gSiPrgqaqLYbc9ng==
"@atproto/xrpc@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.3.2.tgz#432a364be4b3bf8660a088a07dadecac10209763"
integrity sha512-D9jGjcFnEMHuGQ56v6+78uX3RiytKLrA5ITLq6shy0Qj6Zvt5MqV+/cTFuNPKrNCrnWOtHFeRQwMqyGhNS9qZQ==
dependencies:
"@atproto/lexicon" "^0.2.3"
"@atproto/lexicon" "^0.2.2"
zod "^3.21.4"
"@babel/code-frame@7.10.4", "@babel/code-frame@~7.10.4":