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

Stage 1: Refactor codebase #472

Merged
merged 37 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
5abcc96
refactor: reduce indentation on `setDefaultValues()`
mist8kengas May 2, 2024
1713323
refactor: optimize function
mist8kengas May 2, 2024
f6d57ef
refactor: qol improvements
mist8kengas May 2, 2024
1d1f65a
refactor: custom css
mist8kengas May 2, 2024
9c88d92
Merge branch 'dev' of https://github.com/YouTube-Enhancer/extension i…
mist8kengas May 10, 2024
91e4e98
refactor: deep dark css
mist8kengas May 10, 2024
109c28b
refactor: feature menu
mist8kengas May 10, 2024
55cbe80
refactor: hide x features
mist8kengas May 10, 2024
f33ab7c
refactor: loop button
mist8kengas May 10, 2024
3356e64
refactor: maximize button
mist8kengas May 10, 2024
a576f5c
refactor: transcript button
mist8kengas May 10, 2024
2b3b621
refactor: open settings on hover feature
mist8kengas May 10, 2024
f45afef
fix: make types constrained
mist8kengas May 10, 2024
3d7a1fa
refactor: playback speed button
mist8kengas May 10, 2024
f75ca53
refactor: player speed
mist8kengas May 10, 2024
6cb943f
refactor: remaining time
mist8kengas May 10, 2024
11eed28
refactor: remove redirect links
mist8kengas May 10, 2024
f5d0dcc
refactor: screenshot button
mist8kengas May 10, 2024
82bc5bc
refactor: scroll wheel speed control
mist8kengas May 10, 2024
279a22b
refactor: scroll wheel volume control
mist8kengas May 10, 2024
86c2cee
refactor: share shortener
mist8kengas May 10, 2024
29afd29
refactor: auto scroll shorts
mist8kengas May 10, 2024
dc4aca8
refactor: video history
mist8kengas May 10, 2024
7865dfc
refactor: volume boost logic
mist8kengas May 10, 2024
3151b94
refactor: pause background players
mist8kengas May 10, 2024
59c9d0a
refactor: player quality
mist8kengas May 10, 2024
f6383ac
refactor: remember volume
mist8kengas May 10, 2024
2205a63
refactor: skip continue watching
mist8kengas May 10, 2024
4ea23fb
refactor: useNotifications provider
mist8kengas May 10, 2024
04f9aad
refactor: pages
mist8kengas May 10, 2024
79f2303
feat(docs): add contributing page
mist8kengas May 10, 2024
b750794
Merge branch 'dev' into dev
VampireChicken12 May 11, 2024
663c56b
Merge branch 'dev' into dev
VampireChicken12 May 19, 2024
aa51abd
Merge branch 'dev' into dev
VampireChicken12 May 19, 2024
b8b31fe
Merge branch 'dev' into dev
VampireChicken12 May 20, 2024
de260f3
Merge branch 'dev' into dev
VampireChicken12 Jun 13, 2024
c2ad23c
Merge branch 'dev' into dev
VampireChicken12 Jun 14, 2024
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
83 changes: 83 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Contributing

## Table of Contents

- [Glossary](#📚-glossary)
- [Getting Started](#🎉-getting-started)
- [Commits, Issues, and Pull Requests](#✏-commits-issues-and-pull-requests)
- [Contributor Workflow](#🗃-contributor-workflow)
- [Internationalization](#🌐-internationalization-i18n)
- [Code Quality](#🔧-code-quality)

## 📚 Glossary

Here are some terms that we interchangeably refer to in this document:

- **Pull Request** (PR): Pull requests let you tell others about changes you've pushed to a branch in the repository. Once a pull request is opened, you can discuss and review the potential changes with collaborators and add follow-up commits before your changes are merged into the base branch.
- **Continuous Integration** (CI): A development practice where developers frequently integrate their code changes into a shared repository. Each integration triggers automated tests to ensure that the new code doesn't break existing functionality. CI aims to detect and fix integration errors quickly, promoting collaboration and maintaining a consistent codebase.
- **Continuous Development** (CD): An extension of Continuous Integration (CI) that focuses on automating the deployment of code changes to the production environment.

## 🎉 Getting Started

- Because our release CI/CD workflow is automated, we rely on commit messages that follow a format convention for semantic versioning[^1].
As such, we use the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) spec in commit messages.

## ✏ Commits, Issues, and Pull Requests

There are a few guidelines that we follow in order to maintain the quality of the codebase:

- Make sure that commit messages are meaningful, and describe the commit itself.
- When creating [Bug Report Issues](https://github.com/YouTube-Enhancer/extension/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=), make sure to follow the template and explain the issue in a clear and straightforward manner.
- Although we do not strictly enforce the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) spec in pull requests, it is preferred over other ways of creating PR titles.
- In the description of the pull request, briefly describe the goal of the PR and the changes it will bring to the codebase.

## 🗃 Contributor Workflow

We use the "contributor workflow" to manage the code. Everyone suggests changes by making pull requests. This helps people contribute, make tests easier, and get feedback from others.

To contribute to the codebase, the workflow is as follows:

1. Fork the repository
2. Commit changes to the fork (using the `dev` branch)
3. Create a pull request (on the `dev` branch)

_It is ill-advised to create pull requests against the `main` branch as `dev` changes are merged to the main branch in batches by the core maintainers._

> Read more about forking and making pull requests [here](https://docs.github.com/get-started/exploring-projects-on-github/contributing-to-a-project).

## 🌐 Internationalization (i18n)

### Crowdin Translation Project

Our YouTube Enhancer extension supports multiple languages to provide a more inclusive experience for users around the world. We use Crowdin for managing translations.

### Contributing Translations

We welcome contributions to improve translations and make the extension accessible to a wider audience. If you'd like to contribute translations or suggest improvements, follow these steps:

1. Visit our [Crowdin project](https://crowdin.com/project/youtube-enhancer).
2. Select your language and start translating.
3. If your language is not listed, feel free to request its addition.

## 🔧 Code Quality

Before new code gets merged into the repository, we do automated lint tests to verify the format of the code.

It is recommended to test your code before committing by running the following commands:

1. Lint check: `npm run lint`
2. Fix lint errors: `npm run lint:fix`

> You won't need to do this if you use a supported editor[^2], as the process is automated.

While we don't yet have a strict guideline on what kind of code should be in the repository, here are a few principles we loosely follow to maintain the general consistency of the code:

- [DRY principle](https://en.wikipedia.org/wiki/Don't_repeat_yourself)
- [Rule of three](<https://en.wikipedia.org/wiki/Rule_of_three_(computer_programming)>)
- [Single source of truth](https://en.wikipedia.org/wiki/Single_source_of_truth)

---

[^1]: [Why Use Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#why-use-conventional-commits)
[^2]: [ESLint: A List of Editor Integrations](https://eslint.org/docs/latest/use/integrations#editors)

15 changes: 7 additions & 8 deletions src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,13 @@ function setDefaultValues() {
JSON.parse(storedValueString)
: storedValueString;
// Check if the parsed value is an object and has properties
if (typeof storedValue === "object" && storedValue !== null) {
// Deep merge missing keys with their default values
const updatedValue = deepMerge(defaultValue as Record<string, unknown>, storedValue as Record<string, unknown>);
// Set the updated value in localStorage
localStorage.setItem(option, JSON.stringify(updatedValue));
// Set the updated value in chrome storage
void chrome.storage.local.set({ [option]: updatedValue });
}
if (typeof storedValue !== "object" || storedValue === null) continue;
// Deep merge missing keys with their default values
const updatedValue = deepMerge(defaultValue as Record<string, unknown>, storedValue as Record<string, unknown>);
// Set the updated value in localStorage
localStorage.setItem(option, JSON.stringify(updatedValue));
// Set the updated value in chrome storage
void chrome.storage.local.set({ [option]: updatedValue });
} catch (error) {
// Handle errors during JSON parsing
console.error(`Error parsing stored value for option ${option}:`, error);
Expand Down
12 changes: 4 additions & 8 deletions src/features/automaticTheaterMode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,21 @@ import type { YouTubePlayerDiv } from "@/src/types";
import { isWatchPage, waitForSpecificMessage } from "@/src/utils/utilities";

export async function enableAutomaticTheaterMode() {
if (!isWatchPage()) return;
// Wait for the "options" message from the content script
const optionsData = await waitForSpecificMessage("options", "request_data", "content");
const {
data: {
options: { enable_automatic_theater_mode }
}
} = optionsData;
} = await waitForSpecificMessage("options", "request_data", "content");
// If automatic theater mode isn't enabled return
if (!enable_automatic_theater_mode) return;
if (!isWatchPage()) return;
// Get the player element
const playerContainer = isWatchPage() ? document.querySelector<YouTubePlayerDiv>("div#movie_player") : null;
const playerContainer = document.querySelector<YouTubePlayerDiv>("div#movie_player");
// If player element is not available, return
if (!playerContainer) return;
const { width } = await playerContainer.getSize();
const {
body: { clientWidth }
} = document;
const isTheaterMode = width === clientWidth;
const isTheaterMode = document.body.clientWidth === width;
// Get the size button
const sizeButton = document.querySelector<HTMLButtonElement>("button.ytp-size-button");
// If the size button is not available return
Expand Down
20 changes: 6 additions & 14 deletions src/features/buttonPlacement/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export function makeFeatureButton<Name extends AllButtonNames, Placement extends
isToggle: boolean,
initialChecked: boolean = false
) {
const featureName = findKeyByValue(buttonName as MultiButtonNames) ?? (buttonName as SingleButtonFeatureNames);
if (placement === "feature_menu") throw new Error("Cannot make a feature button for the feature menu");
const featureName = findKeyByValue(buttonName as MultiButtonNames) ?? (buttonName as SingleButtonFeatureNames);
const buttonExists = document.querySelector(`button#${getFeatureButtonId(buttonName)}`) !== null;
const button = createStyledElement({
classlist: ["ytp-button"],
Expand Down Expand Up @@ -72,18 +72,12 @@ export function makeFeatureButton<Name extends AllButtonNames, Placement extends
return button;
}

const isIconToggle = typeof icon === "object" && "off" in icon && "on" in icon;
if (isToggle) {
button.ariaChecked = initialChecked ? "true" : "false";
if (typeof icon === "object" && "off" in icon && "on" in icon) {
button.append(initialChecked ? icon.on : icon.off);
} else if (icon instanceof SVGSVGElement) {
button.append(icon);
}
} else {
if (icon instanceof SVGSVGElement) {
button.append(icon);
}
if (isIconToggle) button.append(icon.off);
}
if (!isIconToggle && icon instanceof SVGSVGElement) button.append(icon);

eventManager.addEventListener(button, "mouseover", tooltipListener, featureName);
eventManager.addEventListener(
Expand All @@ -98,9 +92,7 @@ export function makeFeatureButton<Name extends AllButtonNames, Placement extends
return button;
}
export function updateFeatureButtonIcon(button: HTMLButtonElement, icon: SVGElement) {
if (button.firstChild) {
button.firstChild.replaceWith(icon);
}
button.firstChild?.replaceWith(icon);
}
export function updateFeatureButtonTitle(buttonName: AllButtonNames, title: string) {
const button = document.querySelector<HTMLButtonElement>(`#${getFeatureButtonId(buttonName)}`);
Expand Down Expand Up @@ -166,7 +158,7 @@ export function checkIfFeatureButtonExists(buttonName: AllButtonNames, placement
}
}
}
export function getFeatureButtonId(buttonName: AllButtonNames) {
export function getFeatureButtonId<ButtonName extends AllButtonNames>(buttonName: ButtonName) {
return `yte-feature-${buttonName}-button` as const;
}
export function getFeatureButton(buttonName: AllButtonNames) {
Expand Down
18 changes: 4 additions & 14 deletions src/features/customCSS/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,22 @@ import { createCustomCSSElement, customCSSExists, updateCustomCSS } from "./util
export const customCssID = "yte-custom-css";
export async function enableCustomCSS() {
// Wait for the "options" message from the content script
const optionsData = await waitForSpecificMessage("options", "request_data", "content");
const {
data: {
options: { custom_css_code, enable_custom_css }
}
} = optionsData;
} = await waitForSpecificMessage("options", "request_data", "content");
// Check if custom CSS is enabled
if (!enable_custom_css) return;
if (customCSSExists()) {
updateCustomCSS({
custom_css_code
});
return;
}
if (customCSSExists()) return updateCustomCSS({ custom_css_code });
// Create the custom CSS style element
const customCSSStyleElement = createCustomCSSElement({
custom_css_code
});
const customCSSStyleElement = createCustomCSSElement({ custom_css_code });
// Insert the custom CSS style element
document.head.appendChild(customCSSStyleElement);
}
export function disableCustomCSS() {
// Get the custom CSS style element
const customCSSStyleElement = document.querySelector<HTMLStyleElement>(`#${customCssID}`);
// Check if the custom CSS style element exists
if (!customCSSStyleElement) return;
// Remove the custom CSS style element
customCSSStyleElement.remove();
customCSSStyleElement?.remove();
}
7 changes: 2 additions & 5 deletions src/features/customCSS/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import { customCssID } from "@/src/features/customCSS";
export function updateCustomCSS({ custom_css_code }: Pick<configuration, "custom_css_code">) {
// Get the custom CSS style element
const customCSSStyleElement = document.querySelector<HTMLStyleElement>(`#${customCssID}`);
// Check if the custom CSS style element exists
if (!customCSSStyleElement) return;
customCSSStyleElement.replaceWith(createCustomCSSElement({ custom_css_code }));
customCSSStyleElement?.replaceWith(createCustomCSSElement({ custom_css_code }));
}
export function createCustomCSSElement({ custom_css_code }: Pick<configuration, "custom_css_code">) {
// Create the custom CSS style element
Expand All @@ -20,6 +18,5 @@ export function customCSSExists() {
// Get the custom CSS style element
const customCSSStyleElement = document.querySelector<HTMLStyleElement>(`#${customCssID}`);
// Check if the custom CSS style element exists
if (!customCSSStyleElement) return false;
return true;
return customCSSStyleElement !== null;
}
12 changes: 5 additions & 7 deletions src/features/deepDarkCSS/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import { createDeepDarkCSSElement, deepDarkCSSExists, getDeepDarkCustomThemeStyl
export const deepDarkCssID = "yte-deep-dark-css";
export async function enableDeepDarkCSS() {
// Wait for the "options" message from the content script
const optionsData = await waitForSpecificMessage("options", "request_data", "content");
const {
data: {
options: { deep_dark_custom_theme_colors, deep_dark_preset, enable_deep_dark_theme }
}
} = optionsData;
} = await waitForSpecificMessage("options", "request_data", "content");
// Check if deep dark theme is enabled
if (!enable_deep_dark_theme) return;
if (deepDarkCSSExists()) {
updateDeepDarkCSS(deep_dark_preset === "Custom" ? getDeepDarkCustomThemeStyle(deep_dark_custom_theme_colors) : deepDarkPresets[deep_dark_preset]);
return;
return updateDeepDarkCSS(
deep_dark_preset === "Custom" ? getDeepDarkCustomThemeStyle(deep_dark_custom_theme_colors) : deepDarkPresets[deep_dark_preset]
);
}
// Create the deep dark theme style element
const deepDarkThemeStyleElement = createDeepDarkCSSElement(
Expand All @@ -28,8 +28,6 @@ export async function enableDeepDarkCSS() {
export function disableDeepDarkCSS() {
// Get the deep dark theme style element
const deepDarkThemeStyleElement = document.querySelector<HTMLStyleElement>(`#${deepDarkCssID}`);
// Check if the deep dark theme style element exists
if (!deepDarkThemeStyleElement) return;
// Remove the deep dark theme style element
deepDarkThemeStyleElement.remove();
deepDarkThemeStyleElement?.remove();
}
7 changes: 2 additions & 5 deletions src/features/deepDarkCSS/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import { deepDarkCssID } from "@/src/features/deepDarkCSS";
export function updateDeepDarkCSS(css_code: string) {
// Get the custom CSS style element
const customCSSStyleElement = document.querySelector<HTMLStyleElement>(`#${deepDarkCssID}`);
// Check if the custom CSS style element exists
if (!customCSSStyleElement) return;
customCSSStyleElement.replaceWith(createDeepDarkCSSElement(css_code));
customCSSStyleElement?.replaceWith(createDeepDarkCSSElement(css_code));
}
export function createDeepDarkCSSElement(css_code: string) {
// Create the custom CSS style element
Expand All @@ -21,8 +19,7 @@ export function deepDarkCSSExists() {
// Get the custom CSS style element
const customCSSStyleElement = document.querySelector<HTMLStyleElement>(`#${deepDarkCssID}`);
// Check if the custom CSS style element exists
if (!customCSSStyleElement) return false;
return true;
return customCSSStyleElement !== null;
}

export function getDeepDarkCustomThemeStyle({
Expand Down
37 changes: 14 additions & 23 deletions src/features/featureMenu/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,11 @@ export async function enableFeatureMenu() {
}
function adjustAdsContainerStyles(featureMenuOpen: boolean) {
const adsContainer = document.querySelector<HTMLDivElement>("div.video-ads.ytp-ad-module");
if (adsContainer) {
const adsSpan = adsContainer.querySelector<HTMLSpanElement>("span.ytp-ad-preview-container");
if (adsSpan) {
adsSpan.style.opacity = featureMenuOpen ? "0.4" : "";
adsSpan.style.zIndex = featureMenuOpen ? "36" : "";
}
}
if (!adsContainer) return;
const adsSpan = adsContainer.querySelector<HTMLSpanElement>("span.ytp-ad-preview-container");
if (!adsSpan) return;
adsSpan.style.opacity = featureMenuOpen ? "0.4" : "";
adsSpan.style.zIndex = featureMenuOpen ? "36" : "";
}
export function setupFeatureMenuEventListeners(featureMenuOpenType: FeatureMenuOpenType) {
eventManager.removeEventListeners("featureMenu");
Expand Down Expand Up @@ -211,23 +209,16 @@ export function setupFeatureMenuEventListeners(featureMenuOpenType: FeatureMenuO
}
function handleMutation(mutations: MutationRecord[]) {
mutations.forEach((mutation) => {
if (mutation.type === "childList") {
const addedNodes = Array.from(mutation.addedNodes);
const isAdsElementAdded = addedNodes.some(
(node) => (node as HTMLDivElement).classList?.contains("video-ads") && (node as HTMLDivElement).classList?.contains("ytp-ad-module")
);
if (isAdsElementAdded) {
const featureMenu = document.querySelector<HTMLDivElement>("#yte-feature-menu");
if (featureMenu) {
adjustAdsContainerStyles(featureMenu.style.display === "block");
}
}
}
if (mutation.type !== "childList") return;
const addedNodes = Array.from(mutation.addedNodes);
const isAdsElementAdded = addedNodes.some(
(node) => (node as HTMLDivElement).classList?.contains("video-ads") && (node as HTMLDivElement).classList?.contains("ytp-ad-module")
);
if (!isAdsElementAdded) return;
const featureMenu = document.querySelector<HTMLDivElement>("#yte-feature-menu");
if (featureMenu) adjustAdsContainerStyles(featureMenu.style.display === "block");
});
}
const observer = new MutationObserver(handleMutation);
observer.observe(playerContainer, {
childList: true,
subtree: true
});
observer.observe(playerContainer, { childList: true, subtree: true });
}
24 changes: 8 additions & 16 deletions src/features/featureMenu/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,15 @@ export async function addFeatureItemToMenu<Name extends AllButtonNames, Toggle e
menuItem.appendChild(menuItemLabel);

// If it's a toggle item, create the toggle elements
const menuItemContent = document.createElement("div");
menuItemContent.classList.add("ytp-menuitem-content");
if (isToggle) {
const menuItemContent = document.createElement("div");
menuItemContent.classList.add("ytp-menuitem-content");
const menuItemToggle = document.createElement("div");
menuItemToggle.classList.add("ytp-menuitem-toggle-checkbox");
menuItemContent.appendChild(menuItemToggle);
menuItem.appendChild(menuItemContent);
menuItem.ariaChecked = initialChecked ? "true" : "false";
} else {
const menuItemContent = document.createElement("div");
menuItemContent.classList.add("ytp-menuitem-content");
menuItem.appendChild(menuItemContent);
menuItem.ariaChecked = initialChecked ? "true" : "false";
}
menuItem.appendChild(menuItemContent);

// Add the item to the feature menu panel
featureMenuPanel.appendChild(menuItem);
Expand Down Expand Up @@ -182,14 +178,10 @@ export function updateFeatureMenuTitle(title: string) {
* @param buttonName the name of the button
* @returns { featureMenuItemIconId, featureMenuItemId, featureMenuItemLabelId}
*/
export function getFeatureIds(buttonName: AllButtonNames): {
featureMenuItemIconId: FeatureMenuItemIconId;
featureMenuItemId: FeatureMenuItemId;
featureMenuItemLabelId: FeatureMenuItemLabelId;
} {
const featureMenuItemIconId: FeatureMenuItemIconId = `yte-${buttonName}-icon`;
const featureMenuItemId: FeatureMenuItemId = `yte-feature-${buttonName}-menuitem`;
const featureMenuItemLabelId: FeatureMenuItemLabelId = `yte-${buttonName}-label`;
export function getFeatureIds<ButtonName extends AllButtonNames>(buttonName: ButtonName) {
const featureMenuItemIconId = `yte-${buttonName}-icon` as const;
const featureMenuItemId = `yte-feature-${buttonName}-menuitem` as const;
const featureMenuItemLabelId = `yte-${buttonName}-label` as const;
return {
featureMenuItemIconId,
featureMenuItemId,
Expand Down
Loading