diff options
author | Ajay Ramachandran <[email protected]> | 2023-03-18 04:40:26 -0400 |
---|---|---|
committer | GitHub <[email protected]> | 2023-03-18 04:40:26 -0400 |
commit | ef00e07647808fcfc13c61f66b647e0dc2312fe0 (patch) | |
tree | bd9368241ca04521f4786025caf8ec4218bda656 | |
parent | 504f5c565b7c5265596e8229ce9da6752da4cd7a (diff) | |
parent | 3a1d631e01b863f92067be1d9accd0354f6fecfa (diff) | |
download | SponsorBlock-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.json | 14 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | public/_locales/en/messages.json | 4 | ||||
-rw-r--r-- | public/content.css | 41 | ||||
-rw-r--r-- | public/options/options.css | 5 | ||||
-rw-r--r-- | public/options/options.html | 15 | ||||
-rw-r--r-- | src/components/CategoryPillComponent.tsx | 25 | ||||
-rw-r--r-- | src/config.ts | 2 | ||||
-rw-r--r-- | src/content.ts | 43 | ||||
-rw-r--r-- | src/js-components/previewBar.ts | 3 | ||||
-rw-r--r-- | src/utils/thumbnails.ts | 131 | ||||
-rw-r--r-- | src/utils/videoLabels.ts | 65 | ||||
-rw-r--r-- | webpack/webpack.common.js | 243 |
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 |