Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c3dff3d75a |
@@ -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
@@ -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',
|
||||
|
||||
@@ -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
@@ -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`
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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 }],
|
||||
@@ -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 };
|
||||
@@ -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),
|
||||
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
export function bskyTitle(page: string, unreadCountLabel?: string) {
|
||||
const unreadPrefix = unreadCountLabel ? `(${unreadCountLabel}) ` : ''
|
||||
return `${unreadPrefix}${page} — Bluesky`
|
||||
return `${unreadPrefix}${page} - Bluesky`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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":
|
||||
|
||||
Reference in New Issue
Block a user