diff --git a/App.tsx b/App.tsx
index f4d4e80..5019ecf 100644
--- a/App.tsx
+++ b/App.tsx
@@ -1,17 +1,10 @@
-import Constants from "expo-constants";
-import { throttle, noop } from "lodash";
-import React, { useRef, useEffect, useCallback, useState } from "react";
+import { throttle } from "lodash";
+import React, { useRef, useEffect, useCallback } from "react";
import {
View,
- Text,
- StyleSheet,
PanResponder,
- PanResponderInstance,
- Button,
BackHandler,
StatusBar,
- Keyboard,
- ScrollView,
TextInput,
useColorScheme,
} from "react-native";
@@ -24,6 +17,7 @@ enum WebViewMessageType {
DismissKeyboard,
ScrollStarted,
ScrollEnded,
+ ReloadPage,
}
type MessageData = {
@@ -46,30 +40,61 @@ const useUpdateEffect = (
}, deps);
};
+const refreshIcon = /* html */ `
+
+`;
+
+const headerSelector = "#__next > div:nth-of-type(1) > div > div";
+const mainSelector = "#__next > div:nth-of-type(1) > div > main";
+const conversationSelector = `${mainSelector} > div:nth-of-type(1) > div:nth-of-type(1) > div`;
+
+const burgerButtonSelector = `${headerSelector} > button:nth-of-type(1)`;
+
+const plusButtonSelector = `${headerSelector} > button:last-of-type`;
+
+const drawerRoot = 'div[data-headlessui-state="open"]';
+const drawerContentSelector = `${drawerRoot} nav`;
+
const injectedCss = /* css */ `
html {
font-size: 1.2rem;
}
+ ${headerSelector} button {
+ box-shadow: none !important;
+ color: currentColor !important;
+ border: none !important;
+ outline: none !important;
+ }
+
+ ${drawerContentSelector} button {
+ box-shadow: none !important;
+ color: currentColor !important;
+ border: none !important;
+ outline: none !important;
+ }
+
/* Add padding/margin for transparent status bar */
- #__next > div > div > div:first-of-type {
+ ${headerSelector} {
padding-top: calc(${StatusBar.currentHeight}px + 10px);
}
- div[data-headlessui-state="open"] nav {
+ ${drawerContentSelector} {
padding-top: calc(${StatusBar.currentHeight}px + 10px);
}
- /* Hide new chat, switch theme, discord button, clear conversations, close button */
- div[data-headlessui-state="open"] nav > a:nth-of-type(1) {
+ /* Hide new chat, dark node, discord, clear conversation, close button */
+ ${drawerContentSelector} > a:nth-of-type(1) {
display: none;
}
- div[data-headlessui-state=open] nav > a:nth-of-type(2) {
+ ${drawerContentSelector} > a:nth-of-type(2) {
display: none;
}
- div[data-headlessui-state="open"] nav > a:nth-of-type(3) {
+ ${drawerContentSelector} > a:nth-of-type(3) {
display: none;
}
- div[data-headlessui-state=open] nav > a:nth-of-type(4) {
+ ${drawerContentSelector} > a:nth-of-type(4) {
display: none;
}
#headlessui-portal-root button[type=button] {
@@ -80,11 +105,11 @@ const injectedCss = /* css */ `
margin-bottom: 0.5rem;
}
- /* Hide welcome text */
+ /* Hide examples, capabilities, limitations */
main > div:nth-of-type(1) > div > div > div > div:nth-of-type(1) > div.items-start.text-center {
display: none;
}
- /* Align ChatGPT vertically */
+ /* Align "ChatGPT" vertically */
main > div:nth-of-type(1) > div > div > div > div:nth-of-type(1).px-6 {
margin-top: auto;
margin-bottom: auto;
@@ -129,12 +154,45 @@ const setStyleInnerHtml = /* javascript */ `
style.innerHTML = \`${injectedCss}\`;
`;
+const insertRefreshButtonScript = /* javascript */ `
+ var oldRefreshButton = document.querySelector("#refresh");
+ console.log('oldRefreshButton', oldRefreshButton);
+
+ if (!oldRefreshButton) {
+ var header = document.querySelector("${headerSelector}");
+ var plusButton = document.querySelector("${plusButtonSelector}");
+ var refreshButton = document.createElement("button");
+ refreshButton.id = "refresh";
+ refreshButton.className = "px-3";
+ refreshButton.innerHTML = \`${refreshIcon}\`;
+ refreshButton.addEventListener("click", () => {
+ window.ReactNativeWebView.postMessage(
+ JSON.stringify({
+ type: ${WebViewMessageType.ReloadPage},
+ }),
+ );
+ });
+
+ header.insertBefore(refreshButton, plusButton);
+ console.log('inserted refresh button');
+ }
+`;
+
const cssScript = /* javascript */ `
var style = document.createElement('style');
${setStyleInnerHtml}
document.head.appendChild(style);
`;
+const html2canvasScript = /* javascript */ `
+ var html2canvasScript = document.createElement("script");
+ html2canvasScript.src = "https://html2canvas.hertzen.com/dist/html2canvas.min.js";
+ document.body.appendChild(html2canvasScript);
+ // html2canvasScript.onload = () => {
+ // console.log('html2canvas', html2canvas);
+ // };
+`;
+
const storageChangeHandlerScript = /* javascript */ `
var oldStorageSetItem = Storage.prototype.setItem;
Storage.prototype.setItem = function(key, value) {
@@ -157,15 +215,21 @@ const hrefChangeHandlerScript = /* javascript */ `
var currentHref = document.location.href;
mutations.forEach(() => {
if (oldHref !== currentHref) {
- var url = new URL(currentHref);
+ console.log('href changed');
// If chat page
+ var url = new URL(currentHref);
if (url.pathname !== '/chat') {
window.ReactNativeWebView.postMessage(
JSON.stringify({ type: ${WebViewMessageType.DismissKeyboard} }),
);
}
+ // Delay DOM modifications while DOM is rerendering
+ setTimeout(() => {
+ ${insertRefreshButtonScript}
+ }, 500);
+
oldHref = currentHref;
}
});
@@ -203,15 +267,33 @@ const cloudflareRefreshScript = /* javascript */ `
}, 3600000); // 1 hours (though it expires after 2 hours from issuing time)
`;
+const drawerOpenHandlerScript = /* javascript */ `
+ // var burgerButton = document.querySelector("${burgerButtonSelector}");
+ // burgerButton.addEventListener("click", () => {
+ // var isDrawerOpen = !!document.querySelector('${drawerRoot}');
+ // if (!isDrawerOpen) {
+ // setTimeout(() => {
+
+ // }, 500);
+ // }
+ // })
+`;
+
+// cohtml2canvasScriptnst drawer
+
const mainScript = /* javascript */ `
${cssScript}
+ ${html2canvasScript}
+
${hrefChangeHandlerScript}
${scrollScript}
${cloudflareRefreshScript}
+ ${insertRefreshButtonScript}
+
// Sync theme on init
window.ReactNativeWebView.postMessage(
JSON.stringify({
@@ -285,7 +367,7 @@ const App: React.FC = () => {
try {
var drawerOpenObserver;
drawerOpenObserver = new MutationObserver(() => {
- const switchThemeButton = document.querySelector('div[data-headlessui-state="open"] nav > a:nth-of-type(3)');
+ const switchThemeButton = document.querySelector("${drawerContentSelector} > a:nth-of-type(3)");
// console.log("theme button:", switchThemeButton)
if (switchThemeButton) {
switchThemeButton.click();
@@ -315,7 +397,7 @@ const App: React.FC = () => {
webviewRef.current?.injectJavaScript(/* javascript */ `
try {
// console.log('left to right');
- var isDrawerOpen = !!document.querySelector('div[data-headlessui-state="open"]');
+ var isDrawerOpen = !!document.querySelector('${drawerRoot}');
// console.log('Drawer is open: ', isDrawerOpen);
if (!isDrawerOpen) {
@@ -333,7 +415,7 @@ const App: React.FC = () => {
webviewRef.current?.injectJavaScript(/* javascript */ `
try {
// console.log('right to left');
- var isDrawerOpen = !!document.querySelector('div[data-headlessui-state="open"]');
+ var isDrawerOpen = !!document.querySelector('${drawerRoot}');
// console.log('Drawer is open: ', isDrawerOpen);
if (isDrawerOpen) {
@@ -431,6 +513,11 @@ const App: React.FC = () => {
const handleMessage = (event: WebViewMessageEvent) => {
const data: MessageData = JSON.parse(event.nativeEvent.data);
+ if (data.type === WebViewMessageType.ReloadPage) {
+ webviewRef.current?.reload();
+ return;
+ }
+
if (data.type === WebViewMessageType.SyncTheme) {
handleSyncTheme(data);
return;
@@ -487,7 +574,7 @@ const App: React.FC = () => {
/>
{
bounces
pullToRefreshEnabled
overScrollMode="never"
- onScroll={() => console.log("SCROLLING")}
- // allowFileAccess
- // allowFileAccessFromFileURLs
/>
diff --git a/README.md b/README.md
index ccb8714..cbd0dd8 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
ChatGPT 🤖 mobile application bulit using React Native, Expo & Webview
Download Android APK file on
-[Github Releases](https://github.com/nezort11/chatgpt-mobile/releases/).
+[GitHub Releases](https://github.com/nezort11/chatgpt-mobile/releases/).
**Features**:
@@ -11,10 +11,12 @@ Download Android APK file on
- native drawer swipe
- transparent status bar
- themed bottom bar
-- android back button integration (close drawer / quite)
+- android back button (close drawer / quit app)
- startup keyboard auto open
- chat keyboard auto dismiss
- splash screen
+- page reload button
+- "chatgpt is at capacity" workaround