Skip to content

Commit

Permalink
Release v2.2.0 (#61)
Browse files Browse the repository at this point in the history
* fix extension for youtube premium users (#58)
* update selector used for playlistMetadata element in youtube premium layouts
* add support for FR locale (#60)
  * add french translations
  * implement sorting by upload date & view count on fr locale

---------
Co-authored-by: pifopi (DOTTEL Gaël) <[email protected]>
  • Loading branch information
nrednav authored Aug 4, 2024
1 parent c5cdfe4 commit dc8661d
Show file tree
Hide file tree
Showing 11 changed files with 316 additions and 19 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0.html).

## [v2.2.0] - 2024-07-27

### Added

- Added try-catch error handling to `main()`
- Created `getPlaylistMetadataElement` function
- The playlist metadata element appears to have a different identifier
depending on if the user has YouTube premium or not
- This function will take that into account and use the appropriate selector
to find the metadata element
- Added `youtubePremium` variant to list of `playlistMetadata` element selectors
- Added translations for `fr` locale
- Implemented sorting by view count & upload date for `fr` locale
- Added tests for the `fr` locale parsers

### Fixed

- Fixed extension not loading for youtube premium layouts

## [v2.1.4] - 2024-07-26

### Removed
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "An extension to calculate & display the total duration of a youtube playlist.",
"author": "nrednav",
"private": true,
"version": "2.1.4",
"version": "2.2.0",
"type": "module",
"engines": {
"node": ">=20",
Expand All @@ -13,6 +13,7 @@
"dev": "vite",
"build:chrome": "pnpm run clean && vite build --mode chrome",
"build:firefox": "pnpm run clean && vite build --mode firefox",
"test": "node --test",
"clean": "pnpm exec del dist/",
"watch": "pnpm run clean && vite build --watch --mode development",
"lint": "eslint .",
Expand Down
98 changes: 98 additions & 0 deletions public/_locales/fr/messages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{
"loaderMessage": {
"message": "Calcul en cours...",
"description": "Text to display when the extension is loading"
},
"videoTitle_private": {
"message": "[Vidéo privée]",
"description": "Title displayed by YouTube for private videos"
},
"videoTitle_deleted": {
"message": "[Vidéo supprimée]",
"description": "Title displayed by YouTube for deleted videos"
},
"videoTitle_unavailable_v1": {
"message": "[Indisponible]",
"description": "First variation of title displayed by YouTube for unavailable videos"
},
"videoTitle_unavailable_v2": {
"message": "[Vidéo indisponible]",
"description": "Second variation of title displayed by YouTube for unavailable videos"
},
"videoTitle_restricted": {
"message": "[Vidéo restreinte]",
"description": "Title displayed by YouTube for restricted videos"
},
"videoTitle_ageRestricted": {
"message": "[Limite d'âge]",
"description": "Title displayed by YouTube for age-restricted videos"
},
"problemEncountered_paragraphOne": {
"message": "Un problème est survenu.",
"description": "Text to display in first paragraph when a problem has been encountered"
},
"problemEncountered_paragraphTwo": {
"message": "Veuillez recharger cette page pour recalculer la durée de la playlist.",
"description": "Text to display in second paragraph when a problem has been encountered"
},
"playlistSummary_totalDuration": {
"message": "Durée totale:",
"description": "Text to display as label for the playlist duration"
},
"playlistSummary_videosCounted": {
"message": "Vidéos comptées:",
"description": "Text to display as label for the videos counted"
},
"playlistSummary_videosNotCounted": {
"message": "Vidéos non comptées:",
"description": "Text to display as label for the videos not counted"
},
"playlistSummary_tooltip": {
"message": "Faites défiler vers le bas pour compter plus de vidéos",
"description": "Text to display within tooltip"
},
"sortDropdown_label": {
"message": "Trier par:",
"description": "Text to display as label for the sort dropdown"
},
"sortType_index_label_asc": {
"message": "Index (Croissant)",
"description": "Text to display for the ascending 'sort by index' option"
},
"sortType_index_label_desc": {
"message": "Index (Décroissant)",
"description": "Text to display for the descending 'sort by index' option"
},
"sortType_duration_label_asc": {
"message": "Durée (Plus courte)",
"description": "Text to display for the ascending 'sort by duration' option"
},
"sortType_duration_label_desc": {
"message": "Durée (Plus longue)",
"description": "Text to display for the descending 'sort by duration' option"
},
"sortType_channelName_label_asc": {
"message": "Nom de chaîne (A-Z)",
"description": "Text to display for the ascending 'sort by channel name' option"
},
"sortType_channelName_label_desc": {
"message": "Nom de chaîne (Z-A)",
"description": "Text to display for the descending 'sort by channel name' option"
},
"sortType_views_label_asc": {
"message": "Vues (Moins vues)",
"description": "Text to display for the ascending 'sort by views' option"
},
"sortType_views_label_desc": {
"message": "Vues (Plus vues)",
"description": "Text to display for the descending 'sort by views' option"
},
"sortType_uploadDate_label_asc": {
"message": "Date de mise en ligne (Plus récente)",
"description": "Text to display for the ascending 'sort by upload date' option"
},
"sortType_uploadDate_label_desc": {
"message": "Date de mise en ligne (Plus ancienne)",
"description": "Text to display for the descending 'sort by upload date' option"
}
}
76 changes: 63 additions & 13 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import {
import "./main.css";

const main = () => {
setupPage();
checkPlaylistReady();
try {
setupPage();
checkPlaylistReady();
} catch (error) {
logger.error(error.message);
}
};

const checkPlaylistReady = () => {
Expand Down Expand Up @@ -77,7 +81,10 @@ const checkPlaylistReady = () => {

const displayLoader = () => {
const playlistSummaryElement = getPlaylistSummaryElement();
if (!playlistSummaryElement) return;

if (!playlistSummaryElement) {
return;
}

const loaderElement = document.createElement("div");
loaderElement.id = "ytpdc-loader";
Expand Down Expand Up @@ -171,6 +178,7 @@ const isElementVisible = (element) => {
const getPlaylistSummaryElement = () => {
const selector =
elementSelectors.playlistSummary[isNewDesign() ? "new" : "old"];

return document.querySelector(selector);
};

Expand Down Expand Up @@ -199,9 +207,11 @@ const countUnavailableTimestamps = () => {
**/
const getVideos = () => {
const playlistElement = document.querySelector(elementSelectors.playlist);

if (!playlistElement) return [];

const videos = playlistElement.getElementsByTagName(elementSelectors.video);

return [...videos];
};

Expand Down Expand Up @@ -260,6 +270,7 @@ const processPlaylist = () => {
Array.isArray(timestamps) && timestamps.length > 0
? timestamps.reduce((a, b) => a + b)
: 0;

const playlistDuration = convertSecondsToTimestamp(totalDurationInSeconds);

addPlaylistSummaryToPage({ timestamps, playlistDuration, playlistObserver });
Expand All @@ -278,10 +289,15 @@ const setupPlaylistObserver = () => {
if (window.ytpdc.playlistObserver) return window.ytpdc.playlistObserver;

const playlistElement = document.querySelector(elementSelectors.playlist);
if (!playlistElement) return null;

if (!playlistElement) {
return null;
}

const playlistObserver = new MutationObserver(onPlaylistMutated);

playlistObserver.observe(playlistElement, { childList: true });

window.ytpdc.playlistObserver = playlistObserver;

return {
Expand Down Expand Up @@ -309,12 +325,13 @@ const onPlaylistMutated = (mutationList, observer) => {
chrome.i18n.getMessage("problemEncountered_paragraphOne"),
chrome.i18n.getMessage("problemEncountered_paragraphTwo")
]);

observer.disconnect();

return;
}

// No problem encountered, continue processing mutation

const removedVideo = mutation.removedNodes[0];

// If the playlist was sorted, YouTube removes the wrong video from the
Expand All @@ -332,8 +349,11 @@ const onPlaylistMutated = (mutationList, observer) => {
}

observer.disconnect();

window.ytpdc.lastVideoInteractedWith.remove();

observer.observe(playlistElement, { childList: true });

main();
} else {
main();
Expand Down Expand Up @@ -363,7 +383,10 @@ const shouldRequestPageReload = (mutation) => {
*/
const displayMessages = (messages) => {
const playlistSummaryElement = getPlaylistSummaryElement();
if (!playlistSummaryElement) return;

if (!playlistSummaryElement) {
return;
}

const containerElement = document.createElement("div");
containerElement.id = "messages-container";
Expand Down Expand Up @@ -396,14 +419,20 @@ const addPlaylistSummaryToPage = ({
if (existingPlaylistSummaryElement) {
existingPlaylistSummaryElement.replaceWith(playlistSummaryElement);
} else {
const metadataElement = document.querySelector(
elementSelectors.playlistMetadata[isNewDesign() ? "new" : "old"]
);
if (!metadataElement) return null;
const playlistMetadataElement = getPlaylistMetadataElement();

if (!playlistMetadataElement) {
throw new Error(
[
"Cannot add playlist summary to page",
"Reason = Cannot find playlist metadata element in document"
].join(", ")
);
}

metadataElement.parentElement.insertBefore(
playlistMetadataElement.parentElement.insertBefore(
playlistSummaryElement,
metadataElement.nextElementSibling
playlistMetadataElement.nextElementSibling
);
}
};
Expand Down Expand Up @@ -436,13 +465,15 @@ const createPlaylistSummaryElement = ({
`${playlistDuration}`,
"#86efac"
);

containerElement.appendChild(totalDuration);

const videosCounted = createSummaryItem(
chrome.i18n.getMessage("playlistSummary_videosCounted"),
`${timestamps.length}`,
"#fdba74"
);

containerElement.appendChild(videosCounted);

const totalVideosInPlaylist = countTotalVideosInPlaylist();
Expand All @@ -453,6 +484,7 @@ const createPlaylistSummaryElement = ({
}`,
"#fca5a5"
);

containerElement.appendChild(videosNotCounted);

if (totalVideosInPlaylist <= 100) {
Expand All @@ -473,16 +505,19 @@ const createPlaylistSummaryElement = ({
"http://www.w3.org/2000/svg",
"svg"
);

iconElement.setAttribute("preserveAspectRatio", "xMidYMid meet");
iconElement.setAttribute("viewBox", "0 0 24 24");
iconElement.innerHTML = `<path fill="white" fill-rule="evenodd" d="M12 1C5.925 1 1
5.925 1 12s4.925 11 11 11s11-4.925 11-11S18.075 1 12 1Zm-.5 5a1 1 0 1 0 0
2h.5a1 1 0 1 0 0-2h-.5ZM10 10a1 1 0 1 0 0 2h1v3h-1a1 1 0 1 0 0 2h4a1 1 0 1 0
0-2h-1v-4a1 1 0 0 0-1-1h-2Z" clip-rule="evenodd"/>`;

tooltipElement.appendChild(iconElement);

const textElement = document.createElement("p");
textElement.textContent = chrome.i18n.getMessage("playlistSummary_tooltip");

tooltipElement.appendChild(textElement);

containerElement.appendChild(tooltipElement);
Expand All @@ -491,6 +526,20 @@ const createPlaylistSummaryElement = ({
return containerElement;
};

const getPlaylistMetadataElement = () => {
const playlistMetadataElement = document.querySelector(
elementSelectors.playlistMetadata[isNewDesign() ? "new" : "old"]
);

if (!playlistMetadataElement) {
return document.querySelector(
elementSelectors.playlistMetadata.youtubePremium
);
}

return playlistMetadataElement;
};

const isDarkMode = () => {
return document.documentElement.getAttribute("dark") !== null;
};
Expand Down Expand Up @@ -571,7 +620,6 @@ const createSortDropdown = (playlistObserver) => {

const playlistElement = document.querySelector(elementSelectors.playlist);
const videos = playlistElement.getElementsByTagName(elementSelectors.video);

const playlistSorter = new PlaylistSorter(
event.target.getAttribute("value")
);
Expand All @@ -593,8 +641,10 @@ const createSortDropdown = (playlistObserver) => {

dropdownButtonElement.appendChild(dropdownButtonTextElement);
dropdownButtonElement.appendChild(caretDownIcon);

dropdownElement.appendChild(dropdownButtonElement);
dropdownElement.appendChild(dropdownOptionsElement);

containerElement.appendChild(labelElement);
containerElement.appendChild(dropdownElement);

Expand Down
28 changes: 28 additions & 0 deletions src/modules/sorting/sort-by-upload-date/parsers/fr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export class FrUploadDateParser {
/** @param {Element} videoInfo */
parse(videoInfo) {
const secondsByUnit = {
minute: 60,
heure: 60 * 60,
jour: 1 * 86400,
semaine: 7 * 86400,
mois: 30 * 86400,
an: 365 * 86400
};

const uploadDateRegex =
/(?:Diffusé )?il y a (\d+) (minutes?|heures?|jours?|semaines?|mois|ans?)/u;

const uploadDateElement = videoInfo.children[2];

const [value, unit] = uploadDateElement.textContent
.toLowerCase()
.match(uploadDateRegex)
.slice(1);

const seconds =
secondsByUnit[unit] ?? secondsByUnit[unit.slice(0, -1)] ?? 1;

return parseFloat(value) * seconds;
}
}
Loading

0 comments on commit dc8661d

Please sign in to comment.