assign custom project name to url
This commit is contained in:
93
src/components/CustomProjectNameList.tsx
Normal file
93
src/components/CustomProjectNameList.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { ProjectName } from '../utils/settings';
|
||||
|
||||
type Props = {
|
||||
handleChange: (sites: ProjectName[]) => void;
|
||||
helpText: string;
|
||||
label: string;
|
||||
projectNamePlaceholder?: string;
|
||||
sites: ProjectName[];
|
||||
urlPlaceholder?: string;
|
||||
};
|
||||
|
||||
export default function CustomProjectNameList({
|
||||
handleChange,
|
||||
label,
|
||||
urlPlaceholder,
|
||||
projectNamePlaceholder,
|
||||
sites,
|
||||
}: Props): JSX.Element {
|
||||
const handleAddNewSite = useCallback(() => {
|
||||
handleChange([...sites, { projectName: '', url: '' }]);
|
||||
}, [handleChange, sites]);
|
||||
|
||||
const handleUrlChangeForSite = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>, index: number) => {
|
||||
handleChange(
|
||||
sites.map((item, i) => (i === index ? { ...item, url: event.target.value } : item)),
|
||||
);
|
||||
},
|
||||
[handleChange, sites],
|
||||
);
|
||||
|
||||
const handleOnProjectNameChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>, index: number) => {
|
||||
handleChange(
|
||||
sites.map((item, i) => (i === index ? { ...item, projectName: event.target.value } : item)),
|
||||
);
|
||||
},
|
||||
[handleChange, sites],
|
||||
);
|
||||
|
||||
const handleRemoveSite = useCallback(
|
||||
(index: number) => {
|
||||
handleChange(sites.filter((_, i) => i !== index));
|
||||
},
|
||||
[handleChange, sites],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="form-group mb-4 d-flex flex-column gap-3">
|
||||
<label htmlFor={`${label}-siteList`} className="control-label">
|
||||
{label}
|
||||
</label>
|
||||
|
||||
{sites.length > 0 && (
|
||||
<div className="d-flex flex-column gap-2">
|
||||
{sites.map((site, i) => (
|
||||
<div key={i} className="d-flex gap-2">
|
||||
<div className="flex-fill">
|
||||
<input
|
||||
placeholder={urlPlaceholder ?? 'https://google.com'}
|
||||
className="form-control"
|
||||
value={site.url}
|
||||
onChange={(e) => handleUrlChangeForSite(e, i)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-fill">
|
||||
<input
|
||||
placeholder={projectNamePlaceholder ?? 'Project Name'}
|
||||
value={site.projectName}
|
||||
className="form-control"
|
||||
onChange={(e) => handleOnProjectNameChange(e, i)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-default"
|
||||
onClick={() => handleRemoveSite(i)}
|
||||
>
|
||||
<i className="fa fa-fw fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button type="button" onClick={handleAddNewSite} className="btn btn-default col-12">
|
||||
<i className="fa fa-fw fa-plus me-2"></i>
|
||||
Add Project Name
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,8 +2,9 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import config, { SuccessOrFailType } from '../config/config';
|
||||
import apiKeyInvalid from '../utils/apiKey';
|
||||
import { IS_CHROME } from '../utils/operatingSystem';
|
||||
import { getSettings, saveSettings, Settings } from '../utils/settings';
|
||||
import { getSettings, ProjectName, saveSettings, Settings } from '../utils/settings';
|
||||
import { logUserIn } from '../utils/user';
|
||||
import CustomProjectNameList from './CustomProjectNameList';
|
||||
import SitesList from './SitesList';
|
||||
|
||||
interface State extends Settings {
|
||||
@@ -19,6 +20,7 @@ export default function Options(): JSX.Element {
|
||||
allowList: [],
|
||||
apiKey: '',
|
||||
apiUrl: config.apiUrl,
|
||||
customProjectNames: [],
|
||||
denyList: [],
|
||||
extensionStatus: 'allGood',
|
||||
hostname: '',
|
||||
@@ -54,16 +56,19 @@ export default function Options(): JSX.Element {
|
||||
state.apiUrl = state.apiUrl.slice(0, -1);
|
||||
}
|
||||
await saveSettings({
|
||||
allowList: state.allowList,
|
||||
allowList: state.allowList.filter((item) => !!item.trim()),
|
||||
apiKey: state.apiKey,
|
||||
apiUrl: state.apiUrl,
|
||||
denyList: state.denyList,
|
||||
customProjectNames: state.customProjectNames.filter(
|
||||
(item) => !!item.url.trim() && !!item.projectName.trim(),
|
||||
),
|
||||
denyList: state.denyList.filter((item) => !!item.trim()),
|
||||
extensionStatus: state.extensionStatus,
|
||||
hostname: state.hostname,
|
||||
loggingEnabled: state.loggingEnabled,
|
||||
loggingStyle: state.loggingStyle,
|
||||
loggingType: state.loggingType,
|
||||
socialMediaSites: state.socialMediaSites,
|
||||
socialMediaSites: state.socialMediaSites.filter((item) => !!item.trim()),
|
||||
theme: state.theme,
|
||||
trackSocialMedia: state.trackSocialMedia,
|
||||
});
|
||||
@@ -74,17 +79,24 @@ export default function Options(): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const updateDenyListState = useCallback((sites: string) => {
|
||||
const updateDenyListState = useCallback((denyList: string[]) => {
|
||||
setState((oldState) => ({
|
||||
...oldState,
|
||||
denyList: sites.trim().split('\n'),
|
||||
denyList,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const updateAllowListState = useCallback((sites: string) => {
|
||||
const updateAllowListState = useCallback((allowList: string[]) => {
|
||||
setState((oldState) => ({
|
||||
...oldState,
|
||||
allowList: sites.trim().split('\n'),
|
||||
allowList,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const updateCustomProjectNamesState = useCallback((customProjectNames: ProjectName[]) => {
|
||||
setState((oldState) => ({
|
||||
...oldState,
|
||||
customProjectNames,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
@@ -124,7 +136,7 @@ export default function Options(): JSX.Element {
|
||||
<SitesList
|
||||
handleChange={updateDenyListState}
|
||||
label="Exclude"
|
||||
sites={state.denyList.join('\n')}
|
||||
sites={state.denyList}
|
||||
helpText="Sites that you don't want to show in your reports."
|
||||
/>
|
||||
);
|
||||
@@ -133,9 +145,9 @@ export default function Options(): JSX.Element {
|
||||
<SitesList
|
||||
handleChange={updateAllowListState}
|
||||
label="Include"
|
||||
sites={state.allowList.join('\n')}
|
||||
placeholder="http://google.com http://myproject.com/MyProject"
|
||||
helpText="Only track these sites. You can assign URL to project by adding @@YourProject at the end of line."
|
||||
sites={state.allowList}
|
||||
projectNamePlaceholder="http://google.com http://myproject.com/MyProject"
|
||||
helpText="Only track these sites."
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
@@ -230,6 +242,13 @@ export default function Options(): JSX.Element {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<CustomProjectNameList
|
||||
sites={state.customProjectNames}
|
||||
label="Custom Project Names"
|
||||
handleChange={updateCustomProjectNamesState}
|
||||
helpText=""
|
||||
/>
|
||||
|
||||
<div className="form-group mb-4">
|
||||
<label htmlFor="apiUrl" className="form-label mb-0">
|
||||
API Url
|
||||
@@ -286,16 +305,15 @@ export default function Options(): JSX.Element {
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<SitesList
|
||||
handleChange={(sites: string) => {
|
||||
setState({
|
||||
...state,
|
||||
socialMediaSites: sites.split('\n'),
|
||||
});
|
||||
handleChange={(socialMediaSites) => {
|
||||
setState((oldState) => ({
|
||||
...oldState,
|
||||
socialMediaSites,
|
||||
}));
|
||||
}}
|
||||
label="Social"
|
||||
sites={state.socialMediaSites.join('\n')}
|
||||
sites={state.socialMediaSites}
|
||||
helpText="Sites that you don't want to show in your reports."
|
||||
rows={5}
|
||||
/>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
|
||||
@@ -1,47 +1,74 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
type Props = {
|
||||
handleChange: (sites: string) => void;
|
||||
handleChange: (sites: string[]) => void;
|
||||
helpText: string;
|
||||
label: string;
|
||||
placeholder?: string;
|
||||
rows?: number;
|
||||
sites: string;
|
||||
projectNamePlaceholder?: string;
|
||||
sites: string[];
|
||||
urlPlaceholder?: string;
|
||||
};
|
||||
|
||||
export default function SitesList({
|
||||
handleChange,
|
||||
label,
|
||||
placeholder,
|
||||
rows,
|
||||
urlPlaceholder,
|
||||
sites,
|
||||
helpText,
|
||||
}: Props): JSX.Element {
|
||||
const textareaChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
handleChange(event.target.value);
|
||||
};
|
||||
const handleAddNewSite = useCallback(() => {
|
||||
handleChange([...sites, '']);
|
||||
}, [handleChange, sites]);
|
||||
|
||||
const handleUrlChangeForSite = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>, index: number) => {
|
||||
handleChange(sites.map((item, i) => (i === index ? event.target.value : item)));
|
||||
},
|
||||
[handleChange, sites],
|
||||
);
|
||||
|
||||
const handleRemoveSite = useCallback(
|
||||
(index: number) => {
|
||||
handleChange(sites.filter((_, i) => i !== index));
|
||||
},
|
||||
[handleChange, sites],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="form-group mb-4">
|
||||
<label htmlFor={`${label}-siteList`} className="col-lg-2 control-label">
|
||||
<div className="form-group mb-4 d-flex flex-column gap-2">
|
||||
<label htmlFor={`${label}-siteList`} className="control-label">
|
||||
{label}
|
||||
</label>
|
||||
|
||||
<div className="col-lg-10">
|
||||
<textarea
|
||||
id={`${label}-siteList`}
|
||||
{sites.length > 0 && (
|
||||
<div className="d-flex flex-column gap-2">
|
||||
{sites.map((site, i) => (
|
||||
<div key={i} className="d-flex gap-2">
|
||||
<div className="flex-fill">
|
||||
<input
|
||||
placeholder={urlPlaceholder ?? 'https://google.com'}
|
||||
className="form-control"
|
||||
rows={rows ?? 3}
|
||||
onChange={textareaChange}
|
||||
placeholder={placeholder ?? 'http://google.com'}
|
||||
value={sites}
|
||||
></textarea>
|
||||
<span className="text-secondary">
|
||||
{helpText}
|
||||
<br />
|
||||
One line per site.
|
||||
</span>
|
||||
value={site}
|
||||
onChange={(e) => handleUrlChangeForSite(e, i)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-default"
|
||||
onClick={() => handleRemoveSite(i)}
|
||||
>
|
||||
<i className="fa fa-fw fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button type="button" onClick={handleAddNewSite} className="btn btn-default col-12">
|
||||
<i className="fa fa-fw fa-plus me-2"></i>
|
||||
Add Site
|
||||
</button>
|
||||
<span className="text-secondary">{helpText}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,6 +89,14 @@ class WakaTimeCore {
|
||||
);
|
||||
}
|
||||
|
||||
getProjectNameFromList(url: string, settings: Settings) {
|
||||
const site = settings.customProjectNames.find((pattern) => {
|
||||
const re = new RegExp(pattern.url);
|
||||
return re.test(url);
|
||||
});
|
||||
return site?.projectName;
|
||||
}
|
||||
|
||||
async handleActivity(tabId: number) {
|
||||
const settings = await getSettings();
|
||||
if (!settings.loggingEnabled) {
|
||||
@@ -143,6 +151,9 @@ class WakaTimeCore {
|
||||
).heartbeat;
|
||||
|
||||
const entity = settings.loggingType === 'domain' ? getDomainFromUrl(url) : url;
|
||||
|
||||
const projectNameFromList = this.getProjectNameFromList(url, settings);
|
||||
|
||||
return {
|
||||
branch: heartbeat?.branch ?? '<<LAST_BRANCH>>',
|
||||
category: heartbeat?.category,
|
||||
@@ -151,7 +162,7 @@ class WakaTimeCore {
|
||||
id: uuid4(),
|
||||
language: heartbeat?.language,
|
||||
plugin: heartbeat?.plugin,
|
||||
project: heartbeat?.project ?? '<<LAST_PROJECT>>',
|
||||
project: projectNameFromList ?? heartbeat?.project ?? '<<LAST_PROJECT>>',
|
||||
time: this.getCurrentTime(),
|
||||
type: heartbeat?.entityType ?? (settings.loggingType as EntityType),
|
||||
};
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import browser from 'webextension-polyfill';
|
||||
import config, { ExtensionStatus, LoggingStyle, LoggingType, Theme } from '../config/config';
|
||||
|
||||
export interface ProjectName {
|
||||
projectName: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
allowList: string[];
|
||||
apiKey: string;
|
||||
apiUrl: string;
|
||||
customProjectNames: ProjectName[];
|
||||
denyList: string[];
|
||||
extensionStatus: ExtensionStatus;
|
||||
hostname: string;
|
||||
@@ -22,6 +28,7 @@ export const getSettings = async (): Promise<Settings> => {
|
||||
apiKey: config.apiKey,
|
||||
apiUrl: config.apiUrl,
|
||||
blacklist: null,
|
||||
customProjectNames: [],
|
||||
denyList: [],
|
||||
hostname: config.hostname,
|
||||
loggingStyle: config.loggingStyle,
|
||||
@@ -47,6 +54,7 @@ export const getSettings = async (): Promise<Settings> => {
|
||||
await browser.storage.sync.set({ denyList: settings.denyList });
|
||||
await browser.storage.sync.remove('blacklist');
|
||||
}
|
||||
|
||||
if (typeof settings.socialMediaSites === 'string') {
|
||||
settings.socialMediaSites = settings.socialMediaSites.trim().split('\n');
|
||||
await browser.storage.sync.set({
|
||||
@@ -58,6 +66,7 @@ export const getSettings = async (): Promise<Settings> => {
|
||||
allowList: settings.allowList,
|
||||
apiKey: settings.apiKey,
|
||||
apiUrl: settings.apiUrl,
|
||||
customProjectNames: settings.customProjectNames,
|
||||
denyList: settings.denyList,
|
||||
extensionStatus: settings.extensionStatus,
|
||||
hostname: settings.hostname,
|
||||
|
||||
Reference in New Issue
Block a user