Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Feat: show tab-specific icon status and turn on/off completion on each page manually #51

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/codemirror.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ export class CodeMirrorManager {
relativePath: string | undefined,
createDisposables: (() => IDisposable[]) | undefined
): Promise<void> {
if (!window.codeium_enabled) {
return;
}
const clientSettings = await this.client.clientSettingsPoller.clientSettings;
if (clientSettings.apiKey === undefined) {
return;
Expand Down
221 changes: 221 additions & 0 deletions src/component/Popup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import LoginIcon from '@mui/icons-material/Login';
import LogoutIcon from '@mui/icons-material/Logout';
import SaveAltIcon from '@mui/icons-material/SaveAlt';
import SettingsIcon from '@mui/icons-material/Settings';
import {
Alert,
Box,
Button,
IconButton,
Snackbar,
TextField,
Toolbar,
Typography,
} from '@mui/material';
import React, { useEffect } from 'react';

import { openAuthTab } from '../auth';
import { loggedOut } from '../shared';
import { computeAllowlist, defaultAllowlist, getStorageItem, setStorageItem } from '../storage';

// Function to extract domain and convert it to a regex pattern
function domainToRegex(url: string) {
const { hostname } = new URL(url);
// Extract the top-level domain (TLD) and domain name
const domainParts = hostname.split('.').slice(-2); // This takes the last two parts of the hostname
const domain = domainParts.join('.');
// Escape special regex characters (just in case) and create a regex that matches any subdomain or path
const regexSafeDomain = domain.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
return new RegExp(`https?://(.*\\.)?${regexSafeDomain}(/.*)?`);
}

const getCurrentUrl = async () => {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
return tabs[0].url;
};

const addToAllowlist = async (input: string) => {
try {
const item = new RegExp(input);
const allowlist = computeAllowlist(await getStorageItem('allowlist')) || undefined;
for (const regex of allowlist) {
const r = new RegExp(regex);
if (r.source === item.source) {
return 'Already in the whitelist';
}
}
allowlist.push(input);

await setStorageItem('allowlist', { defaults: defaultAllowlist, current: allowlist });
return 'success';
} catch (e) {
return (e as Error).message ?? 'Unknown error';
}
};

const getAllowlist = async () => (await getStorageItem('allowlist')) ?? [];

export const PopupPage = () => {
const [open, setOpen] = React.useState(false);
const [message, setMessage] = React.useState('');
const [severity, setSeverity] = React.useState<'success' | 'error'>('success');
const [user, setUser] = React.useState<any>();
const [matched, setMatched] = React.useState(false);
const [regexItem, setRegexItem] = React.useState('');

const addItem = async () => {
const result = await addToAllowlist(regexItem);
if (result === 'success') {
setSeverity('success');
setMessage('Added to the whitelist. Please refresh');
} else {
setSeverity('error');
setMessage(result);
}
setOpen(true);
};

useEffect(() => {
getStorageItem('user')
.then((u) => {
setUser(u as any);
})
.catch(console.log);
getCurrentUrl()
.then((tabURL: string | undefined) => {
const curURL: string = tabURL ?? '';
getAllowlist()
.then((allowlist) => {
for (const regex of computeAllowlist(
allowlist as { defaults: string[]; current: string[] }
)) {
if (new RegExp(regex).test(curURL)) {
setMatched(true);
setRegexItem(regex);
return;
}
}
setRegexItem(domainToRegex(curURL ?? '').source);
})
.catch(console.log);
})
.catch(console.log);
}, []);

const logout = async () => {
await setStorageItem('user', {});
await loggedOut();
window.close();
};

return (
<Box width={400}>
<Toolbar>
<IconButton edge="start" color="inherit" aria-label="logo">
<img src="icons/32/codeium_square_logo.png" alt="Codeium" />
</IconButton>
<Typography variant="h6" component="div">
Codeium
</Typography>
<Box sx={{ flexGrow: 1 }}>
<IconButton
edge="end"
color="inherit"
aria-label="login"
onClick={async () => {
console.log('user', user);
if (user) {
await chrome.tabs.create({ url: 'https://codeium.com/profile' });
}
}}
sx={{ display: user?.name ? 'flex' : 'none', float: 'right' }}
>
<AccountCircleIcon />
</IconButton>
</Box>
</Toolbar>
<Typography variant="body1" component={'span'}>
{user?.name ? (
<Typography variant="body1" component="div">
{`Welcome, ${user.name}`}
</Typography>
) : (
<Typography variant="body1" component="div">
Please login
</Typography>
)}
</Typography>

<Typography variant="caption" component="div" color={matched ? 'success' : 'error'}>
{matched ? 'Current URL matches:' : 'Adding current URL to whitelist:'}
</Typography>

<TextField
label="URL"
variant="standard"
value={regexItem}
onChange={(e) => setRegexItem(e.target.value)}
fullWidth
disabled={matched}
InputProps={{
readOnly: matched,
endAdornment: !matched && (
<IconButton onClick={addItem}>
<SaveAltIcon />
</IconButton>
),
}}
sx={{ marginTop: 2, marginBottom: 2 }}
/>

<Box
sx={{ display: 'flex', justifyContent: 'center', mt: 2, flexDirection: 'column', gap: 1 }}
>
<Button
fullWidth
startIcon={user?.name ? <LogoutIcon /> : <LoginIcon />}
variant="outlined"
color="primary"
onClick={async () => {
if (user?.name) {
await logout();
} else {
await openAuthTab();
getStorageItem('user')
.then((u) => {
setUser(u as any);
})
.catch(console.log);
}
}}
>
{user?.name ? 'Logout' : 'Login'}
</Button>

<Button
fullWidth
startIcon={<SettingsIcon />}
variant="outlined"
color="primary"
onClick={async () => {
chrome.runtime.openOptionsPage();
}}
>
Options
</Button>

<Snackbar
open={open}
autoHideDuration={3000}
onClose={() => setOpen(false)}
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
>
<Alert severity={severity} sx={{ width: '100%' }} onClose={() => setOpen(false)}>
{message}
</Alert>
</Snackbar>
</Box>
</Box>
);
};
26 changes: 20 additions & 6 deletions src/contentScript.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
const s = document.createElement('script');
s.src = chrome.runtime.getURL('script.js?') + new URLSearchParams({ id: chrome.runtime.id });
s.onload = function () {
(this as HTMLScriptElement).remove();
};
(document.head || document.documentElement).prepend(s);
// avoid injecting the script into redundant frames
if (window.top === window.self) {
const s = document.createElement('script');
s.src = chrome.runtime.getURL('script.js?') + new URLSearchParams({ id: chrome.runtime.id });
s.onload = function () {
(this as HTMLScriptElement).remove();
};
(document.head || document.documentElement).prepend(s);

let codeiumEnabled = true;

chrome.runtime.onMessage.addListener((message) => {
if (message.type === 'codeium_toggle') {
codeiumEnabled = !codeiumEnabled;
window.dispatchEvent(
new CustomEvent('CodeiumEvent', { detail: { enabled: codeiumEnabled } })
);
}
});
}
2 changes: 2 additions & 0 deletions src/monacoCompletionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ export class MonacoCompletionProvider implements monaco.languages.InlineCompleti
model: monaco.editor.ITextModel,
position: monaco.Position
): Promise<monaco.languages.InlineCompletions | undefined> {
if (!window.codeium_enabled) return;
const clientSettings = await this.client.clientSettingsPoller.clientSettings;
if (clientSettings.apiKey === undefined) {
return;
Expand Down Expand Up @@ -454,6 +455,7 @@ export class MonacoCompletionProvider implements monaco.languages.InlineCompleti
)
.filter((item): item is monaco.languages.InlineCompletion => item !== undefined);
void chrome.runtime.sendMessage(this.extensionId, { type: 'success' });
console.log(items);
return { items };
}

Expand Down
89 changes: 0 additions & 89 deletions src/popup.ts

This file was deleted.

12 changes: 12 additions & 0 deletions src/popup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import '../styles/popup.scss';

import React from 'react';
import ReactDOM from 'react-dom';

import { PopupPage } from './component/Popup';

const container = document.getElementById('codeium-popup');

if (container !== null) {
ReactDOM.render(<PopupPage />, container);
}
Loading
Loading