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'}
>
-
-
+
+
+
+
+ {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')