From 7eff2accb2838258c34cbf2aebfb2dee7286e96f Mon Sep 17 00:00:00 2001 From: Aatman Vaidya Date: Thu, 17 Aug 2023 15:16:21 +0530 Subject: [PATCH] adding on-off button, fixing reverse text --- .../plugin/manifest.firefox.json | 8 +- browser-extension/plugin/manifest.json | 4 +- .../plugin/src/content-script.js | 81 +++---- .../plugin/src/transform-general.js | 111 +++++----- .../plugin/src/ui-components/atoms/Toggle.css | 47 +++++ .../ui-components/atoms/ToggleSwitchCustom.js | 85 ++++++++ .../plugin/src/ui-components/pages/App.jsx | 199 +++++++++++++----- .../plugin/src/ui-components/pages/Off.jsx | 16 ++ .../src/ui-components/pages/Preferences.jsx | 16 +- 9 files changed, 398 insertions(+), 169 deletions(-) create mode 100644 browser-extension/plugin/src/ui-components/atoms/Toggle.css create mode 100644 browser-extension/plugin/src/ui-components/atoms/ToggleSwitchCustom.js create mode 100644 browser-extension/plugin/src/ui-components/pages/Off.jsx diff --git a/browser-extension/plugin/manifest.firefox.json b/browser-extension/plugin/manifest.firefox.json index 860a99b2..dee8334d 100644 --- a/browser-extension/plugin/manifest.firefox.json +++ b/browser-extension/plugin/manifest.firefox.json @@ -2,14 +2,10 @@ "manifest_version": 2, "name": "uli", "description": "Moderate your Twitter Feed", - "version": "0.1.12", + "version": "0.1.13", "author": "tattlemade|cis", "content_security_policy": "default-src 'none'; connect-src https://ogbv-plugin.tattle.co.in/ https://uli-media.tattle.co.in/; font-src https://fonts.gstatic.com; object-src 'none'; script-src 'self' ; style-src https://fonts.googleapis.com 'self' 'unsafe-inline'; img-src https://uli-media.tattle.co.in/; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; report-uri 'none';", - "permissions": [ - "storage", - "webRequest", - "contextMenus" - ], + "permissions": ["storage", "webRequest", "contextMenus"], "background": { "scripts": ["background.js"] }, diff --git a/browser-extension/plugin/manifest.json b/browser-extension/plugin/manifest.json index 63fd04dd..d405e85b 100644 --- a/browser-extension/plugin/manifest.json +++ b/browser-extension/plugin/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "uli", "description": "Moderate your Twitter Feed", - "version": "0.1.12", + "version": "0.1.13", "author": "tattlemade|cis", "content_security_policy": { "extension_pages": "default-src 'none'; connect-src https://ogbv-plugin.tattle.co.in/ https://uli-media.tattle.co.in/; font-src https://fonts.gstatic.com; object-src 'none'; script-src 'self'; style-src https://fonts.googleapis.com 'self' 'unsafe-inline'; img-src https://uli-media.tattle.co.in/; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; report-uri 'none';" @@ -23,5 +23,3 @@ }, "icons": { "16": "icon16.png", "48": "icon32.png" } } - - diff --git a/browser-extension/plugin/src/content-script.js b/browser-extension/plugin/src/content-script.js index 5dfe9ceb..71f02592 100644 --- a/browser-extension/plugin/src/content-script.js +++ b/browser-extension/plugin/src/content-script.js @@ -8,15 +8,20 @@ const { getPreferenceData, setPreferenceData } = repository; import { updateSlurList } from './slur-replace'; import transformGeneral from './transform-general'; -log('Content Script Loaded'); +log('Content Script Loaded Test 2'); -function processPage(newUrl) { +(async function test() { + console.log('Async Test'); + const pref = await getPreferenceData(); + console.log(pref); +})(); - const twitterUrl = 'twitter.com' - if (newUrl.includes(twitterUrl)) { +var mainLoadedChecker; - var mainLoadedChecker = setInterval(() => { - +function processPage(newUrl) { + const twitterUrl = 'twitter.com'; + if (newUrl.includes(twitterUrl)) { + mainLoadedChecker = setInterval(() => { if (newUrl.includes(twitterUrl)) { console.log('tick'); const targetNode = document.getElementsByTagName('main')[0]; @@ -25,53 +30,43 @@ function processPage(newUrl) { let currentPage = current(newUrl); const { page } = currentPage; const { getTimeline } = page; - + if (getTimeline === undefined) { log('Unknown State. Could not find Timeline'); } else { let timeline = getTimeline(); // log({ timeline }); - transform_v2.processNewlyAddedNodes_v2(timeline.children); //changed to v2 here - setOnChangeListener(timeline, transform_v2.processNewlyAddedNodes_v2); + transform_v2.processNewlyAddedNodes_v2( + timeline.children + ); //changed to v2 here + setOnChangeListener( + timeline, + transform_v2.processNewlyAddedNodes_v2 + ); } - + clearInterval(mainLoadedChecker); } else { console.log('main section loaded'); } } - }, 500); - - } - else { - - - var mainLoadedChecker = setInterval(() => { - - console.log('tick'); - let body = document.getElementsByTagName("body") - let first_body = body[0] + } else { + mainLoadedChecker = setInterval(() => { + console.log('tick'); + let body = document.getElementsByTagName('body'); + let first_body = body[0]; if (first_body) { console.log('tick 2'); - transformGeneral.processNewlyAddedNodesGeneral(first_body); - clearInterval(mainLoadedChecker); console.log(mainLoadedChecker); - } - else { + } else { console.log('main section loaded'); } - - }, 500) - - + }, 500); } - - - } /** @@ -79,7 +74,6 @@ function processPage(newUrl) { * eg : When a user clicks on a tweet on their home timeline, they * go from the home page to the user status page. */ - chrome.runtime.onMessage.addListener(async function (request) { if (request.type === 'updateData') { console.log('data changed. time to update slurs'); @@ -101,11 +95,13 @@ chrome.runtime.onMessage.addListener(async function (request) { const slur = request.slur; log('slur added from bg', slur); const pref = await getPreferenceData(); + let slurList; if (!pref) { slurList = slur; await setPreferenceData({ ...pref, slurList }); } else { - let { slurList } = pref; + // let { slurList } = pref; + slurList = pref.slurList; if (!slurList || slurList === '') { slurList += slur; } else { @@ -114,15 +110,26 @@ chrome.runtime.onMessage.addListener(async function (request) { await setPreferenceData({ ...pref, slurList }); } - return true; + return true; + } + if (request.type === 'ULI_ENABLE_TOGGLE') { + console.log('Toggle change event received', request.payload); + if (!request.payload) { + clearInterval(mainLoadedChecker); + } } }); window.addEventListener( 'load', - () => { + async () => { console.log('content loaded'); - processPage(location.href); + const pref = await getPreferenceData(); + console.log(pref); + const { uliEnableToggle } = pref; + if (uliEnableToggle) { + processPage(location.href); + } }, false ); diff --git a/browser-extension/plugin/src/transform-general.js b/browser-extension/plugin/src/transform-general.js index c7198188..0d91d5e1 100644 --- a/browser-extension/plugin/src/transform-general.js +++ b/browser-extension/plugin/src/transform-general.js @@ -1,86 +1,69 @@ -import ReactDOM from 'react-dom'; -import { parseTweet } from './twitter/parser'; -import { hashCode } from './util'; import { replaceSlur } from './slur-replace'; -import { TweetControl } from './twitter/tweet-controls'; import { log } from './logger'; -let tweets = {}; - -function debug(id) { - console.log(id, tweets[id]); - // unblur(id); -} +// Traverse dom nodes to find leaf node that are text nodes and process +function bft(node) { + if (node.nodeType === Node.TEXT_NODE) { + const originalText = node.textContent; + // Get cursor position + const originalCursorPosition = getCaretCharacterOffsetWithin(node); + const replacementText = replaceSlur(originalText); -function setBlur(id, blurFlag) { - if (blurFlag === true) { - const tweetToUnBlur = tweets[id]; - for (let i = 0; i < tweetToUnBlur.spans.length; i++) { - tweetToUnBlur.spans[i].innerText = tweetToUnBlur.original_text[i]; - } - } else { - const tweetToBlur = tweets[id]; - for (let i = 0; i < tweetToBlur.spans.length; i++) { - tweetToBlur.spans[i].innerText = replaceSlur( - tweetToBlur.spans[i].innerText - ); + if (replacementText !== originalText) { + node.textContent = replacementText; + // Set cursor position + setCaretPosition(node, originalCursorPosition); } + } else if ( + node.nodeName !== 'STYLE' && + node.nodeName !== 'SCRIPT' && + node.nodeName !== 'NOSCRIPT' + ) { + node.childNodes.forEach(bft); } } -function addInlineMenu(id, item, hasSlur) { - // const id = `ogbv_tweet_${Math.floor(Math.random() * 999999)}`; - // const id = hashCode(item.innerHTML); - - item.setAttribute('id', id); - - let inlineButtonDiv = document.createElement('div'); - inlineButtonDiv.id = id; - item.prepend(inlineButtonDiv); - - ReactDOM.render( - , - inlineButtonDiv - ); +// Function to get the cursor position within a node +function getCaretCharacterOffsetWithin(element) { + let caretOffset = 0; + const sel = window.getSelection(); + if (sel.rangeCount > 0) { + const range = sel.getRangeAt(0); + const preCaretRange = range.cloneRange(); + preCaretRange.selectNodeContents(element); + preCaretRange.setEnd(range.endContainer, range.endOffset); + caretOffset = preCaretRange.toString().length; + } + return caretOffset; } -// Traverse dom nodes to find leaf node that are text nodes and process -function bft(nodes){ - // console.log("inside bft"); - if(nodes.childNodes.length===0 && nodes.nodeType === 3){ - // console.log("found leaf text node", nodes); - // console.log(nodes.textContent); - const replacementText = replaceSlur(nodes.textContent); - nodes.textContent = nodes.textContent.replace(nodes.textContent, replacementText) - } - else if(nodes.nodeName != "STYLE" && nodes.nodeName != "SCRIPT" && nodes.nodeName != "NOSCRIPT") { - // console.log(nodes.nodeName) - nodes.childNodes.forEach((nodes)=>bft(nodes)) - } +// Function to set the cursor position within a node +function setCaretPosition(element, offset) { + const range = document.createRange(); + const sel = window.getSelection(); + range.setStart(element, offset); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); } const processNewlyAddedNodesGeneral = function (firstBody) { log('processing new nodes'); const config = { attributes: true, childList: true, subtree: true }; - - const callback = (mutationList, observer) => { - let elems = firstBody.children + + const callback = () => { + let elems = firstBody.children; const nodes = Array.from(elems); - let relevant_elements = nodes.filter((element)=>["P","DIV"].includes(element.nodeName)) - + let relevant_elements = nodes.filter((element) => + ['P', 'DIV'].includes(element.nodeName) + ); + relevant_elements.map((element) => { - bft(element) + bft(element); }); - } + }; const observer = new MutationObserver(callback); - observer.observe(firstBody, config); - + observer.observe(firstBody, config); }; export default { diff --git a/browser-extension/plugin/src/ui-components/atoms/Toggle.css b/browser-extension/plugin/src/ui-components/atoms/Toggle.css new file mode 100644 index 00000000..e52b460c --- /dev/null +++ b/browser-extension/plugin/src/ui-components/atoms/Toggle.css @@ -0,0 +1,47 @@ +.toggle-container { + display: flex; + align-items: center; +} + +.toggle-switch { + position: relative; + width: 50px; + height: 25px; + margin-right: 10px; +} +.toggle-switch input[type='checkbox'] { + display: none; +} +.toggle-switch .switch { + position: absolute; + cursor: pointer; + background-color: #ffffff; + border-radius: 25px; + top: 0; + right: 0; + bottom: 0; + left: 0; + transition: background-color 0.2s ease; +} +.toggle-switch .switch::before { + position: absolute; + content: ''; + left: 2px; + top: 2px; + width: 21px; + height: 21px; + background-color: #9d9893; + border-radius: 50%; + transition: transform 0.3s ease; +} +.toggle-switch input[type='checkbox']:checked + .switch::before { + transform: translateX(25px); + background-color: #de8821; +} +.toggle-switch input[type='checkbox']:checked + .switch { + background-color: #fabc73; +} + +.label { + color: #212121; +} diff --git a/browser-extension/plugin/src/ui-components/atoms/ToggleSwitchCustom.js b/browser-extension/plugin/src/ui-components/atoms/ToggleSwitchCustom.js new file mode 100644 index 00000000..e4de2051 --- /dev/null +++ b/browser-extension/plugin/src/ui-components/atoms/ToggleSwitchCustom.js @@ -0,0 +1,85 @@ +import PropTypes from 'prop-types'; +// import "./Toggle.css"; + +export const ToggleSwitchCustom = ({ onToggleChange, checked }) => { + const switchStyles = ` + .toggle-container { + display: flex; + align-items: center; + } + + .toggle-switch { + position: relative; + width: 50px; + height: 25px; + margin-right: 10px; + } + + .toggle-switch input[type='checkbox'] { + display: none; + } + + .toggle-switch .switch { + position: absolute; + cursor: pointer; + background-color: #9D9893; + border-radius: 25px; + top: 0; + right: 0; + bottom: 0; + left: 0; + transition: background-color 0.2s ease; + } + + .toggle-switch .switch::before { + position: absolute; + content: ''; + left: 2px; + top: 2px; + width: 21px; + height: 21px; + background-color: #FFFFFF; + border-radius: 50%; + transition: transform 0.3s ease; + } + + .toggle-switch input[type='checkbox']:checked + .switch::before { + transform: translateX(25px); + background-color: #DE8821; + } + + .toggle-switch input[type='checkbox']:checked + .switch { + background-color: #fabc73; + } + + .label { + color: #212121; + } + `; + + return ( +
+ +
+ + {checked ? 'On' : 'Off'} +
+
+ ); +}; + +ToggleSwitchCustom.propTypes = { + onToggleChange: PropTypes.func.isRequired, + checked: PropTypes.bool.isRequired +}; + +export default ToggleSwitchCustom; diff --git a/browser-extension/plugin/src/ui-components/pages/App.jsx b/browser-extension/plugin/src/ui-components/pages/App.jsx index 40d99b45..1b5560e8 100644 --- a/browser-extension/plugin/src/ui-components/pages/App.jsx +++ b/browser-extension/plugin/src/ui-components/pages/App.jsx @@ -12,7 +12,9 @@ import { UserContext, NotificationContext } from '../atoms/AppContext'; import Api from './Api'; import repository from '../../repository'; import { langNameMap } from '../atoms/language'; -const { getPreferenceData } = repository; +const { getPreferenceData, setPreferenceData } = repository; +import { ToggleSwitchCustom } from '../atoms/ToggleSwitchCustom'; +import { Off } from './Off'; export function App() { const [user, setUser] = useState(undefined); @@ -21,6 +23,8 @@ export function App() { const { registerNewUser } = Api; const { getUserData, setUserData } = repository; let navigate = useNavigate(); + const [, setAccountActivated] = useState(false); + const [toggleSwitchOn, setToggleSwitchOn] = useState(true); function showNotification(notification) { setNotification(notification); @@ -33,31 +37,103 @@ export function App() { * This loads an existing user into the UserContext at startup. */ useEffect(async () => { - const userData = await getUserData(); - const preferenceData = await getPreferenceData(); - if (userData != undefined && Object.keys(userData).length != 0) { - setUser(userData); - } - if (preferenceData != undefined) { - const { language } = preferenceData; - i18n.changeLanguage(langNameMap[language]); - } + try { + const userData = await getUserData(); + const preferenceData = await getPreferenceData(); + + if (userData != undefined && Object.keys(userData).length !== 0) { + setUser(userData); + } - navigate('/preferences'); + if (preferenceData != undefined) { + const { language, uliEnableToggle } = preferenceData; + + i18n.changeLanguage(langNameMap[language]); + + if (uliEnableToggle) { + navigate('/preferences'); + } else { + navigate('/off'); + } + setToggleSwitchOn(uliEnableToggle); + } + } catch (error) { + console.error('Error in useEffect:', error); + } // if (userData != undefined && Object.keys(userData).length != 0) { // setUser(userData); // } // alert(process.env.API_URL); }, []); + let userBrowser; + const userAgent = window.navigator.userAgent.toLowerCase(); + if (userAgent.includes('chrome')) { + userBrowser = 'chrome'; + } else if (userAgent.includes('firefox')) { + userBrowser = 'firefox'; + } else { + userBrowser = 'unsupported'; + } + let userBrowserTabs; + if (userBrowser === 'firefox') { + userBrowserTabs = browser.tabs; + } else if (userBrowser === 'chrome') { + userBrowserTabs = chrome.tabs; + } + async function clickActivateAccount() { const { data } = await registerNewUser(); const user = data.user; - await setUserData(user); - setUser(user); - navigate('/preferences'); + const confirmed = window.confirm( + 'To start Uli, please refresh the page' + ); + if (confirmed) { + await setUserData(user); + setUser(user); + setAccountActivated(true); + await setPreferenceData({ + uliEnableToggle: true + }); + userBrowserTabs.reload(); + navigate('/preferences'); + } } + const handleToggleSwitchChange = async () => { + try { + const tabsCurrent = await userBrowserTabs.query({ + active: true, + currentWindow: true + }); + const confirmed = window.confirm( + 'Changing the toggle switch setting requires a page reload. Do you want to continue?' + ); + if (confirmed) { + setToggleSwitchOn(!toggleSwitchOn); + const pref = await getPreferenceData(); + await setPreferenceData({ + ...pref, + uliEnableToggle: !toggleSwitchOn + }); + if (!toggleSwitchOn) { + navigate('/preferences'); + } else { + navigate('/off'); + } + + const tabId = tabsCurrent[0].id; + userBrowserTabs.sendMessage(tabId, { + type: 'ULI_ENABLE_TOGGLE', + payload: toggleSwitchOn + }); + userBrowserTabs.reload(tabId); + } + } catch (error) { + console.error('Error navigating:', error); + } + }; + return ( @@ -70,26 +146,37 @@ export function App() { pad={'small'} > - - + + {'Uli + + + {process.env.NODE_ENV} + + + + {user ? ( + + handleToggleSwitchChange() } - alt={'Uli Logo'} /> - - - {process.env.NODE_ENV} - + ) : null} {notification ? ( - - - {t('navigation_preferences')} - - - {t('navigation_archive')} - - - {t('navigation_resources')} - - - {t('navigation_debug')} - - + <> + {toggleSwitchOn && ( + + + {t('navigation_preferences')} + + + {t('navigation_archive')} + + + {t('navigation_resources')} + + + {t('navigation_debug')} + + + )} + @@ -147,6 +241,7 @@ export function App() { element={} /> } /> + } /> ) : ( diff --git a/browser-extension/plugin/src/ui-components/pages/Off.jsx b/browser-extension/plugin/src/ui-components/pages/Off.jsx new file mode 100644 index 00000000..2e25b802 --- /dev/null +++ b/browser-extension/plugin/src/ui-components/pages/Off.jsx @@ -0,0 +1,16 @@ +import { Box, Text } from 'grommet'; + +export function Off() { + return ( + + + Turn Uli On to start redacting Slurs and Abusive Content Online + + + ); +} diff --git a/browser-extension/plugin/src/ui-components/pages/Preferences.jsx b/browser-extension/plugin/src/ui-components/pages/Preferences.jsx index 2396f044..327cb313 100644 --- a/browser-extension/plugin/src/ui-components/pages/Preferences.jsx +++ b/browser-extension/plugin/src/ui-components/pages/Preferences.jsx @@ -8,10 +8,9 @@ import { Text, Button, Select, - CheckBox, - Anchor + CheckBox } from 'grommet'; -import { HelpCircle } from 'react-feather'; +// import { HelpCircle } from 'react-feather'; import Api from '../pages/Api'; import repository from '../../repository'; import { useTranslation } from 'react-i18next'; @@ -68,21 +67,24 @@ export function Preferences() { }, [user]); async function clickSave(preference) { - console.log('-----'); - console.log({ user, preference }); + const preferenceInLS = await getPreferenceData(); + // alert(JSON.stringify({preferenceInLS, preference})) + try { const preferenceRemote = await savePreference( user.accessToken, preference ); - // alert(JSON.stringify(preferenceRemote.data)); + await setPreferenceData({ ...preferenceRemote.data, enable, enableML, storeLocally, - language + language, + uliEnableToggle: preferenceInLS.uliEnableToggle }); + showNotification({ type: 'message', message: t('message_ok_saved')