aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAjay Ramachandran <[email protected]>2023-03-18 04:40:26 -0400
committerGitHub <[email protected]>2023-03-18 04:40:26 -0400
commitef00e07647808fcfc13c61f66b647e0dc2312fe0 (patch)
treebd9368241ca04521f4786025caf8ec4218bda656
parent504f5c565b7c5265596e8229ce9da6752da4cd7a (diff)
parent3a1d631e01b863f92067be1d9accd0354f6fecfa (diff)
downloadSponsorBlock-ef00e07647808fcfc13c61f66b647e0dc2312fe0.tar.gz
SponsorBlock-ef00e07647808fcfc13c61f66b647e0dc2312fe0.zip
Merge pull request #1623 from mini-bomba/labels-on-thumbnails
Show Full-Video Labels on thumbnails
-rw-r--r--package-lock.json14
-rw-r--r--package.json2
-rw-r--r--public/_locales/en/messages.json4
-rw-r--r--public/content.css41
-rw-r--r--public/options/options.css5
-rw-r--r--public/options/options.html15
-rw-r--r--src/components/CategoryPillComponent.tsx25
-rw-r--r--src/config.ts2
-rw-r--r--src/content.ts43
-rw-r--r--src/js-components/previewBar.ts3
-rw-r--r--src/utils/thumbnails.ts131
-rw-r--r--src/utils/videoLabels.ts65
-rw-r--r--webpack/webpack.common.js243
13 files changed, 474 insertions, 119 deletions
diff --git a/package-lock.json b/package-lock.json
index 02765509..891e1389 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,7 +27,7 @@
],
"license": "LGPL-3.0-or-later",
"dependencies": {
- "@ajayyy/maze-utils": "1.1.7",
+ "@ajayyy/maze-utils": "1.1.10",
"content-scripts-register-polyfill": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0"
@@ -67,9 +67,9 @@
}
},
"node_modules/@ajayyy/maze-utils": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/@ajayyy/maze-utils/-/maze-utils-1.1.7.tgz",
- "integrity": "sha512-qmakLnRnNJ/CAyDO9ey0ihn71YWoyZfRFxF78ylofA5A+ghBXg4cVVY92iKDN3pivtT2kouLiKDRWgazYKqrOQ==",
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@ajayyy/maze-utils/-/maze-utils-1.1.10.tgz",
+ "integrity": "sha512-JjiPEloeq5WjvjAWIpVEI+5g/pjKEJNtx/uM2ujp9oiT05+c9wKJGqIEC1kb8UeoXSkqrIaKy6b5RMabdy/dRQ==",
"funding": [
{
"type": "individual",
@@ -13858,9 +13858,9 @@
},
"dependencies": {
"@ajayyy/maze-utils": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/@ajayyy/maze-utils/-/maze-utils-1.1.7.tgz",
- "integrity": "sha512-qmakLnRnNJ/CAyDO9ey0ihn71YWoyZfRFxF78ylofA5A+ghBXg4cVVY92iKDN3pivtT2kouLiKDRWgazYKqrOQ=="
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@ajayyy/maze-utils/-/maze-utils-1.1.10.tgz",
+ "integrity": "sha512-JjiPEloeq5WjvjAWIpVEI+5g/pjKEJNtx/uM2ujp9oiT05+c9wKJGqIEC1kb8UeoXSkqrIaKy6b5RMabdy/dRQ=="
},
"@ampproject/remapping": {
"version": "2.2.0",
diff --git a/package.json b/package.json
index 355ff258..5d126a9b 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"description": "",
"main": "background.js",
"dependencies": {
- "@ajayyy/maze-utils": "1.1.7",
+ "@ajayyy/maze-utils": "1.1.10",
"content-scripts-register-polyfill": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0"
diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json
index 9a31e87a..77d68cb7 100644
--- a/public/_locales/en/messages.json
+++ b/public/_locales/en/messages.json
@@ -797,6 +797,10 @@
"message": "Show an icon when a video is entirely an advertisement",
"description": "Referring to the category pill that is now shown on videos that are entirely sponsor or entirely selfpromo"
},
+ "fullVideoLabelsOnThumbnails": {
+ "message": "Show labels on video thumbnails as well",
+ "description": "Referring to the category pill that is shown on videos that are entirely sponsor or entirely selfpromo on recommended videos, in searches or on the homepage."
+ },
"previewColor": {
"message": "Unsubmitted Color",
"description": "Referring to submissions that have not been sent to the server yet."
diff --git a/public/content.css b/public/content.css
index 415d5f43..bea78b17 100644
--- a/public/content.css
+++ b/public/content.css
@@ -803,3 +803,44 @@ input::-webkit-inner-spin-button {
color: #fff;
opacity: .7;
}
+
+/* full video labels on thumbnails */
+.sponsorThumbnailLabel {
+ display: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ padding: 0.5em;
+ margin: 0.5em;
+ border-radius: 2em;
+ z-index: 1000;
+ background-color: var(--category-color, #000);
+ opacity: 0.7;
+ box-shadow: 0 0 8px 2px #333;
+ font-size: 10px;
+}
+
+.sponsorThumbnailLabel.sponsorThumbnailLabelVisible {
+ display: flex;
+}
+
+.sponsorThumbnailLabel svg {
+ height: 2em;
+ fill: var(--category-text-color, #fff);
+}
+
+.sponsorThumbnailLabel span {
+ display: none;
+ padding-left: 0.25em;
+ font-size: 1.5em;
+ color: var(--category-text-color, #fff);
+}
+
+.sponsorThumbnailLabel:hover {
+ border-radius: 0.25em;
+ opacity: 1;
+}
+
+.sponsorThumbnailLabel:hover span {
+ display: inline;
+} \ No newline at end of file
diff --git a/public/options/options.css b/public/options/options.css
index 00035cd0..0ec58be6 100644
--- a/public/options/options.css
+++ b/public/options/options.css
@@ -696,4 +696,9 @@ svg {
.upsellButton {
cursor: pointer;
vertical-align: middle;
+}
+
+.no-bottom-border {
+ border: none !important;
+ padding: 20px 0px 0px 0px !important;
} \ No newline at end of file
diff --git a/public/options/options.html b/public/options/options.html
index cd584374..dfaf7856 100644
--- a/public/options/options.html
+++ b/public/options/options.html
@@ -78,7 +78,7 @@
</div>
</div>
- <div data-type="toggle" data-sync="fullVideoSegments">
+ <div data-type="toggle" data-sync="fullVideoSegments" class="no-bottom-border">
<div class="switch-container">
<label class="switch">
<input id="fullVideoSegments" type="checkbox" checked>
@@ -90,6 +90,19 @@
</div>
</div>
+ <div data-type="toggle" data-sync="fullVideoLabelsOnThumbnails"
+ data-dependent-on="fullVideoSegments">
+ <div class="switch-container">
+ <label class="switch">
+ <input id="fullVideoLabelsOnThumbnails" type="checkbox" checked>
+ <span class="slider round"></span>
+ </label>
+ <label class="switch-label" for="fullVideoLabelsOnThumbnails">
+ __MSG_fullVideoLabelsOnThumbnails__
+ </label>
+ </div>
+ </div>
+
<div data-type="number-change" data-sync="minDuration">
<label class="number-container">
<span class="optionLabel">__MSG_minDuration__</span>
diff --git a/src/components/CategoryPillComponent.tsx b/src/components/CategoryPillComponent.tsx
index a008e396..123f3e93 100644
--- a/src/components/CategoryPillComponent.tsx
+++ b/src/components/CategoryPillComponent.tsx
@@ -115,28 +115,15 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
}
private getColor(): string {
- const configObject = Config.config.barTypes["preview-" + this.state.segment?.category]
- || Config.config.barTypes[this.state.segment?.category];
- return configObject?.color;
+ // Handled by setCategoryColorCSSVariables() of content.ts
+ const category = this.state.segment?.category;
+ return `var(--sb-category-preview-${category}, var(--sb-category-${category}))`;
}
private getTextColor(): string {
- const color = this.getColor();
- if (!color) return null;
-
- const existingCalculatedColor = Config.config.categoryPillColors[this.state.segment?.category];
- if (existingCalculatedColor && existingCalculatedColor.lastColor === color) {
- return existingCalculatedColor.textColor;
- } else {
- const luminance = GenericUtils.getLuminance(color);
- const textColor = luminance > 128 ? "black" : "white";
- Config.config.categoryPillColors[this.state.segment?.category] = {
- lastColor: color,
- textColor
- };
-
- return textColor;
- }
+ // Handled by setCategoryColorCSSVariables() of content.ts
+ const category = this.state.segment?.category;
+ return `var(--sb-category-text-preview-${category}, var(--sb-category-text-${category}))`;
}
private openTooltip(): void {
diff --git a/src/config.ts b/src/config.ts
index 66ed2bd7..6c3c98e4 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -27,6 +27,7 @@ interface SBConfig {
disableSkipping: boolean;
muteSegments: boolean;
fullVideoSegments: boolean;
+ fullVideoLabelsOnThumbnails: boolean;
manualSkipOnFullVideo: boolean;
trackViewCount: boolean;
trackViewCountInPrivate: boolean;
@@ -258,6 +259,7 @@ const syncDefaults = {
disableSkipping: false,
muteSegments: true,
fullVideoSegments: true,
+ fullVideoLabelsOnThumbnails: true,
manualSkipOnFullVideo: false,
trackViewCount: true,
trackViewCountInPrivate: true,
diff --git a/src/content.ts b/src/content.ts
index 6e719c31..cf1bbf8b 100644
--- a/src/content.ts
+++ b/src/content.ts
@@ -43,11 +43,17 @@ import { StorageChangesObject } from "@ajayyy/maze-utils/lib/config";
import { findValidElement } from "@ajayyy/maze-utils/lib/dom"
import { getHash, HashedValue } from "@ajayyy/maze-utils/lib/hash";
import { generateUserID } from "@ajayyy/maze-utils/lib/setup";
+import { setThumbnailListener, updateAll } from "@ajayyy/maze-utils/lib/thumbnailManagement";
+import { labelThumbnails, setupThumbnailPageLoadListener } from "./utils/thumbnails";
+import * as documentScript from "../dist/js/document.js";
const utils = new Utils();
-// Hack to get the CSS loaded on permission-based sites (Invidious)
-utils.wait(() => Config.isReady(), 5000, 10).then(addCSS);
+utils.wait(() => Config.isReady(), 5000, 10).then(() => {
+ // Hack to get the CSS loaded on permission-based sites (Invidious)
+ addCSS();
+ setCategoryColorCSSVariables();
+});
const skipBuffer = 0.003;
@@ -106,8 +112,11 @@ setupVideoModule({
updatePreviewBar();
updateVisibilityOfPlayerControlsButton();
},
- resetValues
+ resetValues,
+ documentScript
}, () => Config);
+setThumbnailListener(labelThumbnails);
+setupThumbnailPageLoadListener();
//the video id of the last preview bar update
let lastPreviewBarUpdate: VideoID;
@@ -332,6 +341,13 @@ function contentConfigUpdateListener(changes: StorageChangesObject) {
case "categorySelections":
sponsorsLookup();
break;
+ case "barTypes":
+ setCategoryColorCSSVariables();
+ break;
+ case "fullVideoSegments":
+ case "fullVideoLabelsOnThumbnails":
+ updateAll();
+ break;
}
}
}
@@ -2466,4 +2482,25 @@ function checkForPreloadedSegment() {
Config.config.unsubmittedSegments[getVideoID()] = sponsorTimesSubmitting;
Config.forceSyncUpdate("unsubmittedSegments");
}
+}
+
+// Generate and inject a stylesheet that creates CSS variables with configured category colors
+function setCategoryColorCSSVariables() {
+ let styleContainer = document.getElementById("sbCategoryColorStyle");
+ if (!styleContainer) {
+ styleContainer = document.createElement("style");
+ styleContainer.id = "sbCategoryColorStyle";
+ document.head.appendChild(styleContainer)
+ }
+
+ let css = ":root {"
+ for (const [category, config] of Object.entries(Config.config.barTypes)) {
+ css += `--sb-category-${category}: ${config.color};`;
+
+ const luminance = GenericUtils.getLuminance(config.color);
+ css += `--sb-category-text-${category}: ${luminance > 128 ? "black" : "white"};`;
+ }
+ css += "}";
+
+ styleContainer.innerText = css;
} \ No newline at end of file
diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts
index 30d54d3a..bec83333 100644
--- a/src/js-components/previewBar.ts
+++ b/src/js-components/previewBar.ts
@@ -331,7 +331,8 @@ class PreviewBar {
const fullCategoryName = (unsubmitted ? 'preview-' : '') + category;
bar.setAttribute('sponsorblock-category', fullCategoryName);
- bar.style.backgroundColor = Config.config.barTypes[fullCategoryName]?.color;
+ // Handled by setCategoryColorCSSVariables() of content.ts
+ bar.style.backgroundColor = `var(--sb-category-${fullCategoryName})`;
if (!this.onMobileYouTube) bar.style.opacity = Config.config.barTypes[fullCategoryName]?.opacity;
bar.style.position = "absolute";
diff --git a/src/utils/thumbnails.ts b/src/utils/thumbnails.ts
new file mode 100644
index 00000000..8b8ed266
--- /dev/null
+++ b/src/utils/thumbnails.ts
@@ -0,0 +1,131 @@
+import { waitFor } from "@ajayyy/maze-utils";
+import { newThumbnails } from "@ajayyy/maze-utils/lib/thumbnailManagement";
+import { isOnInvidious, parseYouTubeVideoIDFromURL } from "@ajayyy/maze-utils/lib/video";
+import Config from "../config";
+import { getVideoLabel } from "./videoLabels";
+
+export async function labelThumbnails(thumbnails: HTMLImageElement[]): Promise<void> {
+ await Promise.all(thumbnails.map((t) => labelThumbnail(t)));
+}
+
+export async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTMLElement | null> {
+ if (!Config.config?.fullVideoSegments || !Config.config?.fullVideoLabelsOnThumbnails) {
+ hideThumbnailLabel(thumbnail);
+ return null;
+ }
+
+ const link = (isOnInvidious() ? thumbnail.parentElement : thumbnail.querySelector("#thumbnail")) as HTMLAnchorElement
+ if (!link || link.nodeName !== "A" || !link.href) return null; // no link found
+ const videoID = parseYouTubeVideoIDFromURL(link.href)?.videoID;
+ if (!videoID) {
+ hideThumbnailLabel(thumbnail);
+ return null;
+ }
+
+ const category = await getVideoLabel(videoID);
+ if (!category) {
+ hideThumbnailLabel(thumbnail);
+ return null;
+ }
+
+ const { overlay, text } = createOrGetThumbnail(thumbnail);
+
+ overlay.style.setProperty('--category-color', `var(--sb-category-preview-${category}, var(--sb-category-${category}))`);
+ overlay.style.setProperty('--category-text-color', `var(--sb-category-text-preview-${category}, var(--sb-category-text-${category}))`);
+ text.innerText = chrome.i18n.getMessage(`category_${category}`);
+ overlay.classList.add("sponsorThumbnailLabelVisible");
+
+ return overlay;
+}
+
+function getOldThumbnailLabel(thumbnail: HTMLImageElement): HTMLElement | null {
+ return thumbnail.querySelector(".sponsorThumbnailLabel") as HTMLElement | null;
+}
+
+function hideThumbnailLabel(thumbnail: HTMLImageElement): void {
+ const oldLabel = getOldThumbnailLabel(thumbnail);
+ if (oldLabel) {
+ oldLabel.classList.remove("sponsorThumbnailLabelVisible");
+ }
+}
+
+function createOrGetThumbnail(thumbnail: HTMLImageElement): { overlay: HTMLElement; text: HTMLElement } {
+ const oldElement = getOldThumbnailLabel(thumbnail);
+ if (oldElement) {
+ return {
+ overlay: oldElement as HTMLElement,
+ text: oldElement.querySelector("span") as HTMLElement
+ };
+ }
+
+ const overlay = document.createElement("div") as HTMLElement;
+ overlay.classList.add("sponsorThumbnailLabel");
+ // Disable hover autoplay
+ overlay.addEventListener("pointerenter", (e) => {
+ e.stopPropagation();
+ thumbnail.dispatchEvent(new PointerEvent("pointerleave", { bubbles: true }));
+ });
+ overlay.addEventListener("pointerleave", (e) => {
+ e.stopPropagation();
+ thumbnail.dispatchEvent(new PointerEvent("pointerenter", { bubbles: true }));
+ });
+
+ const icon = createSBIconElement();
+ const text = document.createElement("span");
+ overlay.appendChild(icon);
+ overlay.appendChild(text);
+ thumbnail.appendChild(overlay);
+
+ return {
+ overlay,
+ text
+ };
+}
+
+function createSBIconElement(): SVGSVGElement {
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg.setAttribute("viewBox", "0 0 565.15 568");
+ const use = document.createElementNS("http://www.w3.org/2000/svg", "use");
+ use.setAttribute("href", "#SponsorBlockIcon");
+ svg.appendChild(use);
+ return svg;
+}
+
+
+// Inserts the icon svg definition, so it can be used elsewhere
+function insertSBIconDefinition() {
+ const container = document.createElement("span");
+
+ // svg from /public/icons/PlayerStartIconSponsorBlocker.svg, with useless stuff removed
+ container.innerHTML = `
+<svg viewBox="0 0 565.15 568" style="display: none">
+ <defs>
+ <g id="SponsorBlockIcon">
+ <path d="M282.58,568a65,65,0,0,1-34.14-9.66C95.41,463.94,2.54,300.46,0,121A64.91,64.91,0,0,1,34,62.91a522.56,522.56,0,0,1,497.16,0,64.91,64.91,0,0,1,34,58.12c-2.53,179.43-95.4,342.91-248.42,437.3A65,65,0,0,1,282.58,568Zm0-548.31A502.24,502.24,0,0,0,43.4,80.22a45.27,45.27,0,0,0-23.7,40.53c2.44,172.67,91.81,330,239.07,420.83a46.19,46.19,0,0,0,47.61,0C453.64,450.73,543,293.42,545.45,120.75a45.26,45.26,0,0,0-23.7-40.54A502.26,502.26,0,0,0,282.58,19.69Z"/>
+ <path d="M 284.70508 42.693359 A 479.9 479.9 0 0 0 54.369141 100.41992 A 22.53 22.53 0 0 0 42.669922 120.41992 C 45.069922 290.25992 135.67008 438.63977 270.83008 522.00977 A 22.48 22.48 0 0 0 294.32031 522.00977 C 429.48031 438.63977 520.08047 290.25992 522.48047 120.41992 A 22.53 22.53 0 0 0 510.7793 100.41992 A 479.9 479.9 0 0 0 284.70508 42.693359 z M 220.41016 145.74023 L 411.2793 255.93945 L 220.41016 366.14062 L 220.41016 145.74023 z "/>
+ </g>
+ </defs>
+</svg>`;
+ document.body.appendChild(container.children[0]);
+}
+
+export function setupThumbnailPageLoadListener(): void {
+ const onLoad = () => {
+ insertSBIconDefinition();
+
+ // Label thumbnails on load if on Invidious (wait for variable initialization before checking)
+ waitFor(() => isOnInvidious() !== null).then(() => {
+ if (isOnInvidious()) newThumbnails();
+ });
+ };
+
+ if (document.readyState === "complete") {
+ onLoad();
+ } else {
+ window.addEventListener("load", onLoad);
+ }
+
+ waitFor(() => Config.isReady(), 5000, 10).then(() => {
+ newThumbnails();
+ });
+} \ No newline at end of file
diff --git a/src/utils/videoLabels.ts b/src/utils/videoLabels.ts
new file mode 100644
index 00000000..ca12c6e4
--- /dev/null
+++ b/src/utils/videoLabels.ts
@@ -0,0 +1,65 @@
+import { Category, VideoID } from "../types";
+import { getHash } from "@ajayyy/maze-utils/lib/hash";
+import Utils from "../utils";
+import { logWarn } from "./logger";
+
+const utils = new Utils();
+
+export interface LabelCacheEntry {
+ timestamp: number;
+ videos: Record<VideoID, Category>;
+}
+
+const labelCache: Record<string, LabelCacheEntry> = {};
+const cacheLimit = 1000;
+
+async function getLabelHashBlock(hashPrefix: string): Promise<LabelCacheEntry | null> {
+ // Check cache
+ const cachedEntry = labelCache[hashPrefix];
+ if (cachedEntry) {
+ return cachedEntry;
+ }
+
+ const response = await utils.asyncRequestToServer("GET", `/api/videoLabels/${hashPrefix}`);
+ if (response.status !== 200) {
+ // No video labels or server down
+ labelCache[hashPrefix] = {
+ timestamp: Date.now(),
+ videos: {},
+ };
+ return null;
+ }
+
+ try {
+ const data = JSON.parse(response.responseText);
+
+ const newEntry: LabelCacheEntry = {
+ timestamp: Date.now(),
+ videos: Object.fromEntries(data.map(video => [video.videoID, video.segments[0].category])),
+ };
+ labelCache[hashPrefix] = newEntry;
+
+ if (Object.keys(labelCache).length > cacheLimit) {
+ // Remove oldest entry
+ const oldestEntry = Object.entries(labelCache).reduce((a, b) => a[1].timestamp < b[1].timestamp ? a : b);
+ delete labelCache[oldestEntry[0]];
+ }
+
+ return newEntry;
+ } catch (e) {
+ logWarn(`Error parsing video labels: ${e}`);
+
+ return null;
+ }
+}
+
+export async function getVideoLabel(videoID: VideoID): Promise<Category | null> {
+ const prefix = (await getHash(videoID, 1)).slice(0, 3);
+ const result = await getLabelHashBlock(prefix);
+
+ if (result) {
+ return result.videos[videoID] ?? null;
+ }
+
+ return null;
+} \ No newline at end of file
diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js
index 0be6c63c..75583107 100644
--- a/webpack/webpack.common.js
+++ b/webpack/webpack.common.js
@@ -25,99 +25,168 @@ const edgeLanguages = [
"zh_CN"
]
-module.exports = env => ({
- entry: {
- popup: path.join(__dirname, srcDir + 'popup.ts'),
- background: path.join(__dirname, srcDir + 'background.ts'),
- content: path.join(__dirname, srcDir + 'content.ts'),
- options: path.join(__dirname, srcDir + 'options.ts'),
- help: path.join(__dirname, srcDir + 'help.ts'),
- permissions: path.join(__dirname, srcDir + 'permissions.ts'),
- document: path.join(__dirname, srcDir + 'document.ts'),
- upsell: path.join(__dirname, srcDir + 'upsell.ts')
- },
- output: {
- path: path.join(__dirname, '../dist/js'),
- },
- optimization: {
- splitChunks: {
- name: 'vendor',
- chunks: "initial"
- }
- },
- module: {
- rules: [
- {
- test: /\.tsx?$/,
- loader: 'ts-loader',
- exclude: /node_modules/,
- options: {
- // disable type checker for user in fork plugin
- transpileOnly: true,
- configFile: env.mode === "production" ? "tsconfig-production.json" : "tsconfig.json"
- }
- }
- ]
- },
- resolve: {
- extensions: ['.ts', '.tsx', '.js']
- },
- plugins: [
- // fork TS checker
- new ForkTsCheckerWebpackPlugin(),
- // exclude locale files in moment
- new CopyPlugin({
- patterns: [
+
+
+module.exports = env => {
+ const documentScriptBuild = webpack({
+ entry: {
+ document: {
+ import: path.join(__dirname, srcDir + 'document.ts'),
+ },
+ },
+ output: {
+ path: path.join(__dirname, '../dist/js'),
+ },
+ module: {
+ rules: [
{
- from: '.',
- to: '../',
- globOptions: {
- ignore: ['manifest.json'],
- },
- context: './public',
- filter: async (path) => {
- if (path.match(/\/_locales\/.+/)) {
- if (env.browser.toLowerCase() === "edge"
- && !edgeLanguages.includes(path.match(/(?<=\/_locales\/)[^/]+(?=\/[^/]+$)/)[0])) {
- return false;
- }
+ test: /\.tsx?$/,
+ loader: 'ts-loader',
+ exclude: /node_modules/,
+ resourceQuery: { not: [/raw/] },
+ options: {
+ // disable type checker for user in fork plugin
+ transpileOnly: true,
+ configFile: env.mode === "production" ? "tsconfig-production.json" : "tsconfig.json"
+ }
+ },
+ ]
+ },
+ resolve: {
+ extensions: ['.ts', '.tsx', '.js']
+ },
+ plugins: [
+ // Don't fork TS checker for document script to speed up
+ // new ForkTsCheckerWebpackPlugin()
+ ]
+ });
- const data = await fs.promises.readFile(path);
- const parsed = JSON.parse(data.toString());
+ class DocumentScriptCompiler {
+ currentWatching = null;
- return parsed.fullName && parsed.Description;
- } else {
- return true;
- }
- },
- transform(content, path) {
- if (path.match(/\/_locales\/.+/)) {
- const parsed = JSON.parse(content.toString());
- if (env.browser.toLowerCase() === "safari") {
- parsed.fullName.message = parsed.fullName.message.match(/^.+(?= -)/)?.[0] || parsed.fullName.message;
- if (parsed.fullName.message.length > 50) {
- parsed.fullName.message = parsed.fullName.message.slice(0, 47) + "...";
+ /**
+ *
+ * @param {webpack.Compiler} compiler
+ */
+ apply(compiler) {
+ compiler.hooks.beforeCompile.tapAsync({ name: 'DocumentScriptCompiler' }, (compiler, callback) => {
+ if (env.WEBPACK_WATCH) {
+ let first = true;
+ if (!this.currentWatching) {
+ this.currentWatching = documentScriptBuild.watch({}, () => {
+ if (first) {
+ first = false;
+ callback();
+ }
+ });
+ } else {
+ callback();
+ }
+ } else {
+ documentScriptBuild.close(() => {
+ documentScriptBuild.run(() => {
+ callback();
+ });
+ });
+ }
+ });
+ }
+ }
+
+ return {
+ entry: {
+ popup: path.join(__dirname, srcDir + 'popup.ts'),
+ background: path.join(__dirname, srcDir + 'background.ts'),
+ content: path.join(__dirname, srcDir + 'content.ts'),
+ options: path.join(__dirname, srcDir + 'options.ts'),
+ help: path.join(__dirname, srcDir + 'help.ts'),
+ permissions: path.join(__dirname, srcDir + 'permissions.ts'),
+ upsell: path.join(__dirname, srcDir + 'upsell.ts')
+ },
+ output: {
+ path: path.join(__dirname, '../dist/js'),
+ },
+ module: {
+ rules: [
+ {
+ test: /\.tsx?$/,
+ loader: 'ts-loader',
+ exclude: /node_modules/,
+ resourceQuery: { not: [/raw/] },
+ options: {
+ // disable type checker for user in fork plugin
+ transpileOnly: true,
+ configFile: env.mode === "production" ? "tsconfig-production.json" : "tsconfig.json"
+ }
+ },
+ {
+ test: /js\/document\.js$/,
+ type: 'asset/source'
+ }
+ ]
+ },
+ resolve: {
+ extensions: ['.ts', '.tsx', '.js']
+ },
+ plugins: [
+ // Prehook to start building document script before normal build
+ new DocumentScriptCompiler(),
+ // fork TS checker
+ new ForkTsCheckerWebpackPlugin(),
+ // exclude locale files in moment
+ new CopyPlugin({
+ patterns: [
+ {
+ from: '.',
+ to: '../',
+ globOptions: {
+ ignore: ['manifest.json'],
+ },
+ context: './public',
+ filter: async (path) => {
+ if (path.match(/\/_locales\/.+/)) {
+ if (env.browser.toLowerCase() === "edge"
+ && !edgeLanguages.includes(path.match(/(?<=\/_locales\/)[^/]+(?=\/[^/]+$)/)[0])) {
+ return false;
}
- parsed.Description.message = parsed.Description.message.match(/^.+(?=\. )/)?.[0] || parsed.Description.message;
- if (parsed.Description.message.length > 80) {
- parsed.Description.message = parsed.Description.message.slice(0, 77) + "...";
+ const data = await fs.promises.readFile(path);
+ const parsed = JSON.parse(data.toString());
+
+ return parsed.fullName && parsed.Description;
+ } else {
+ return true;
+ }
+ },
+ transform(content, path) {
+ if (path.match(/\/_locales\/.+/)) {
+ const parsed = JSON.parse(content.toString());
+ if (env.browser.toLowerCase() === "safari") {
+ parsed.fullName.message = parsed.fullName.message.match(/^.+(?= -)/)?.[0] || parsed.fullName.message;
+ if (parsed.fullName.message.length > 50) {
+ parsed.fullName.message = parsed.fullName.message.slice(0, 47) + "...";
+ }
+
+ parsed.Description.message = parsed.Description.message.match(/^.+(?=\. )/)?.[0] || parsed.Description.message;
+ if (parsed.Description.message.length > 80) {
+ parsed.Description.message = parsed.Description.message.slice(0, 77) + "...";
+ }
}
+
+ return Buffer.from(JSON.stringify(parsed));
}
-
- return Buffer.from(JSON.stringify(parsed));
- }
- return content;
+ return content;
+ }
}
- }
- ]
- }),
- new BuildManifest({
- browser: env.browser,
- pretty: env.mode === "production",
- stream: env.stream
- }),
- new configDiffPlugin()
- ]
-});
+ ]
+ }),
+ new BuildManifest({
+ browser: env.browser,
+ pretty: env.mode === "production",
+ stream: env.stream
+ }),
+ new configDiffPlugin()
+ ]
+ };
+}; \ No newline at end of file