diff --git a/addon-api/common/Self.js b/addon-api/common/Self.js index af1f08c66a..d214558726 100644 --- a/addon-api/common/Self.js +++ b/addon-api/common/Self.js @@ -1,4 +1,5 @@ import Listenable from "./Listenable.js"; +import { isFirefox } from "../../libraries/common/cs/detect-browser.js"; /** * Represents information about the addon. @@ -13,8 +14,7 @@ export default class Self extends Listenable { this._addonId = info.id; // In order to receive fireEvent messages from background this.id = info.id; this._addonObj = addonObj; - // catches both Chrome and Chromium - this.browser = /Chrom/.test(navigator.userAgent) ? "chrome" : "firefox"; + this.browser = isFirefox() ? "firefox" : "chrome"; this.disabled = false; this.addEventListener("disabled", () => (this.disabled = true)); this.addEventListener("reenabled", () => (this.disabled = false)); diff --git a/addons-l10n/en/discuss-button.json b/addons-l10n/en/discuss-button.json new file mode 100644 index 0000000000..08072180bb --- /dev/null +++ b/addons-l10n/en/discuss-button.json @@ -0,0 +1,3 @@ +{ + "discuss-button/membership": "Membership" +} diff --git a/addons/cat-blocks/userscript.js b/addons/cat-blocks/userscript.js index d492acd69e..04d664722e 100644 --- a/addons/cat-blocks/userscript.js +++ b/addons/cat-blocks/userscript.js @@ -358,26 +358,20 @@ export default async function ({ addon, console }) { } }; } else { - const oldLayout = Blockly.VerticalFlyout.prototype.layout_; - Blockly.VerticalFlyout.prototype.layout_ = function (contents, gaps) { - const oldStartHeight = Blockly.BlockSvg.START_HAT_HEIGHT; - if (!addon.self.disabled) Blockly.BlockSvg.START_HAT_HEIGHT = 31; - oldLayout.call(this, contents, gaps); - Blockly.BlockSvg.START_HAT_HEIGHT = oldStartHeight; + const patchProperty = (object, name, newValue) => { + const oldProperty = Object.getOwnPropertyDescriptor(object, name); + Object.defineProperty(object, name, { + ...oldProperty, + get() { + if (addon.self.disabled) return oldProperty.get(); + return newValue; + }, + }); }; - const oldRenderDrawTop = Blockly.BlockSvg.prototype.renderDrawTop_; - Blockly.BlockSvg.prototype.renderDrawTop_ = function (steps, rightEdge) { - const oldStartHatPath = Blockly.BlockSvg.START_HAT_PATH; - const oldTopCorner = Blockly.BlockSvg.TOP_LEFT_CORNER_DEFINE_HAT; - if (!addon.self.disabled) { - Blockly.BlockSvg.START_HAT_PATH = CAT_PATH; - Blockly.BlockSvg.TOP_LEFT_CORNER_DEFINE_HAT = TOP_LEFT_CORNER_DEFINE_CAT; - } - oldRenderDrawTop.call(this, steps, rightEdge); - Blockly.BlockSvg.START_HAT_PATH = oldStartHatPath; - Blockly.BlockSvg.TOP_LEFT_CORNER_DEFINE_HAT = oldTopCorner; - }; + patchProperty(Blockly.BlockSvg, "START_HAT_PATH", CAT_PATH); + patchProperty(Blockly.BlockSvg, "TOP_LEFT_CORNER_DEFINE_HAT", TOP_LEFT_CORNER_DEFINE_CAT); + patchProperty(Blockly.BlockSvg, "START_HAT_HEIGHT", 31); const originalRenderDraw = Blockly.BlockSvg.prototype.renderDraw_; Blockly.BlockSvg.prototype.renderDraw_ = function (...args) { diff --git a/addons/custom-zoom/userscript.js b/addons/custom-zoom/userscript.js index dca84ec5f1..beb8fdb211 100644 --- a/addons/custom-zoom/userscript.js +++ b/addons/custom-zoom/userscript.js @@ -65,7 +65,8 @@ export default async function ({ addon, console }) { reduxEvents: [ "scratch-gui/mode/SET_PLAYER", "scratch-gui/locales/SELECT_LOCALE", - "scratch-gui/theme/SET_THEME", + "scratch-gui/settings/SET_COLOR_MODE", + "scratch-gui/settings/SET_THEME", "fontsLoaded/SET_FONTS_LOADED", ], reduxCondition: (state) => !state.scratchGui.mode.isPlayerOnly, diff --git a/addons/dark-www/banners/darker.css b/addons/dark-www/banners/darker.css index 4a948dae23..6944e40e6d 100644 --- a/addons/dark-www/banners/darker.css +++ b/addons/dark-www/banners/darker.css @@ -37,6 +37,7 @@ .parents .title-banner.masthead .ted-talk { border-color: black; } +.membership-page .get-involved-section, .educators .title-banner.masthead, .educators #teacher-accounts, .expect .keynote, diff --git a/addons/dark-www/banners/desaturated.css b/addons/dark-www/banners/desaturated.css index 26114ce87f..526a8f911e 100644 --- a/addons/dark-www/banners/desaturated.css +++ b/addons/dark-www/banners/desaturated.css @@ -37,6 +37,7 @@ .parents .title-banner.masthead .ted-talk { border-color: black; } +.membership-page .get-involved-section, .educators .title-banner.masthead, .educators #teacher-accounts, .expect .keynote, diff --git a/addons/dark-www/experimental_scratchwww.css b/addons/dark-www/experimental_scratchwww.css index 573395fe9e..c9b22827f2 100644 --- a/addons/dark-www/experimental_scratchwww.css +++ b/addons/dark-www/experimental_scratchwww.css @@ -372,6 +372,7 @@ body:not(.sa-body-editor) .outer .sort-mode .select select:focus { /* Blue background */ .empty, .intro-banner .intro-subnav, +.membership-page .scratch-membership-section, .social-message.mod-unread, .preview .remix-credit, .preview .project-description, diff --git a/addons/discuss-button/addon.json b/addons/discuss-button/addon.json index 0e8b0f02ac..609f8ec823 100644 --- a/addons/discuss-button/addon.json +++ b/addons/discuss-button/addon.json @@ -3,17 +3,22 @@ "description": "Choose a list of links to be displayed in the navigation bar.", "userscripts": [ { - "url": "userscript.js", - "matches": ["https://scratch.mit.edu/*", "https://scratch.mit.edu/403", "https://scratch.mit.edu/404"], + "url": "items_scratchwww.js", + "matches": ["scratchWWWNoProject", "projects"], "runAtComplete": false }, { - "url": "www.js", + "url": "items_scratchr2.js", + "matches": ["isNotScratchWWW", "https://scratch.mit.edu/403", "https://scratch.mit.edu/404"], + "runAtComplete": false + }, + { + "url": "compact_scratchwww.js", "matches": ["scratchWWWNoProject", "projects"], "runAtComplete": false }, { - "url": "r2.js", + "url": "compact_scratchr2.js", "matches": ["isNotScratchWWW", "https://scratch.mit.edu/403", "https://scratch.mit.edu/404"], "runAtComplete": false } @@ -46,8 +51,7 @@ "default": [ { "name": "Create", "url": "/projects/editor/" }, { "name": "Explore", "url": "/explore/projects/all" }, - { "name": "Ideas", "url": "/ideas" }, - { "name": "About", "url": "/about" } + { "name": "Ideas", "url": "/ideas" } ], "row": [ { @@ -90,6 +94,12 @@ } ] }, + { + "name": "Show Membership link when logged out", + "id": "showMembership", + "type": "boolean", + "default": true + }, { "name": "Compact user dropdown", "id": "compact-nav", diff --git a/addons/discuss-button/r2.js b/addons/discuss-button/compact_scratchr2.js similarity index 100% rename from addons/discuss-button/r2.js rename to addons/discuss-button/compact_scratchr2.js diff --git a/addons/discuss-button/www.js b/addons/discuss-button/compact_scratchwww.js similarity index 100% rename from addons/discuss-button/www.js rename to addons/discuss-button/compact_scratchwww.js diff --git a/addons/discuss-button/items_scratchr2.js b/addons/discuss-button/items_scratchr2.js new file mode 100644 index 0000000000..31f8267917 --- /dev/null +++ b/addons/discuss-button/items_scratchr2.js @@ -0,0 +1,59 @@ +export default async function ({ addon, console, msg }) { + let originalNavbar = []; + await addon.tab.waitForElement(".search", { markAsSeen: true }); + const list = document.querySelector(".site-nav"); + + function createItem({ name, url }, last) { + const li = document.createElement("li"); + li.className = "link"; + if (last) li.classList.add("last"); + + const a = document.createElement("a"); + const absolute = new URL(url, location.origin); + a.href = ["http:", "https:"].includes(absolute.protocol) ? absolute.toString() : ""; + li.append(a); + // scratch's code looks for this id in account-nav.js + a.id = "project-create"; + + const span = document.createElement("span"); + span.innerText = name; + a.append(span); + + return li; + } + function removeAllItems() { + if (originalNavbar.length === 0) { + originalNavbar.push( + ...Array.prototype.map.call(list.children, (item) => ({ + name: item.innerText, + url: item.firstChild.href, + })) + ); + } + while (list.firstChild) { + list.removeChild(list.lastChild); + } + return; + } + async function init() { + const loggedIn = await addon.auth.fetchIsLoggedIn(); + removeAllItems(); + let items = addon.self.disabled ? originalNavbar : addon.settings.get("items"); + if (!addon.self.disabled && !loggedIn && addon.settings.get("showMembership")) { + items = [ + ...items, + { + name: msg("membership"), + url: "/membership", + }, + ]; + } + items.forEach((item, i) => { + list.append(createItem(item, i + 1 === items.length)); + }); + } + init(); + addon.settings.addEventListener("change", init); + addon.self.addEventListener("disabled", init); + addon.self.addEventListener("reenabled", init); +} diff --git a/addons/discuss-button/items_scratchwww.js b/addons/discuss-button/items_scratchwww.js new file mode 100644 index 0000000000..a6181a3327 --- /dev/null +++ b/addons/discuss-button/items_scratchwww.js @@ -0,0 +1,106 @@ +const REACT_ELEMENT = Symbol.for("react.element"); + +export default async function ({ addon, console }) { + const createReactElement = (type, props, ...children) => { + const key = props.key ?? null; + const ref = props.ref ?? null; + delete props.key; + delete props.ref; + if (type.defaultProps) { + props = Object.assign({}, type.defaultProps, props); + } + if (children.length > 1) props.children = children; + else if (children.length === 1) props.children = children[0]; + return { + $$typeof: REACT_ELEMENT, + type, + props, + key, + ref, + }; + }; + + const navbar = await addon.tab.waitForElement("#navigation > .inner", { + markAsSeen: true, + reduxCondition: (state) => { + if (!state.scratchGui) return true; + return state.scratchGui.mode.isPlayerOnly; + }, + }); + let fiberNode = navbar[addon.tab.traps.getInternalKey(navbar)]; + while (!fiberNode.stateNode?.handleSearchSubmit) { + fiberNode = fiberNode.return; + } + const navigationInstance = fiberNode.stateNode; + const Navigation = navigationInstance.constructor; + + const oldRender = Navigation.prototype.render; + Navigation.prototype.render = function () { + if (addon.self.disabled) return oldRender.call(this); + + // The objects returned by oldRender() are immutable in the development version + // of React, but overriding Object.freeze() allows us to change them. This won't + // cause problems because React hasn't seen the objects yet at this point. + const oldFreeze = Object.freeze; + Object.freeze = () => {}; + const result = oldRender.call(this); + Object.freeze = oldFreeze; + + const list = result.props.children[0]; + if (list.type !== "ul") return result; + + let items = addon.settings.get("items"); + const loggedIn = !!this.props.user; + if (!loggedIn && addon.settings.get("showMembership")) { + items = [ + ...items, + { + name: addon.tab.scratchMessage("general.membership"), + url: "/membership", + }, + ]; + } + + list.props.children = [ + list.props.children[0], // logo + + ...items.map(({ name, url }) => { + const absolute = new URL(url, location.origin); + return createReactElement( + "li", + { className: "link" }, + createReactElement( + "a", + { href: ["http:", "https:"].includes(absolute.protocol) ? absolute.toString() : "" }, + createReactElement("span", {}, name) + ) + ); + }), + + ...list.props.children.slice(1).filter( + // remove original links + (child) => + !( + child && + child.type === "li" && + child.props.className.includes("link") && + !child.props.className.includes("right") + ) + ), + ]; + return result; + }; + navigationInstance.render = Navigation.prototype.render.bind(navigationInstance); + + const updateItems = () => { + // force rerender + addon.tab.redux.dispatch({ + type: "SET_STATUS", + status: addon.tab.redux.state.session.status, + }); + }; + updateItems(); + addon.settings.addEventListener("change", updateItems); + addon.self.addEventListener("disabled", updateItems); + addon.self.addEventListener("reenabled", updateItems); +} diff --git a/addons/discuss-button/userscript.js b/addons/discuss-button/userscript.js deleted file mode 100644 index 82b0f3522c..0000000000 --- a/addons/discuss-button/userscript.js +++ /dev/null @@ -1,83 +0,0 @@ -export default async function ({ addon, console }) { - let originalNavbar = []; - while (true) { - const searchItem = await addon.tab.waitForElement(".search", { - markAsSeen: true, - reduxCondition: (state) => { - if (!state.scratchGui) return true; - return state.scratchGui.mode.isPlayerOnly; - }, - }); - const scratchr2List = document.querySelector(".site-nav"); - - function createItem({ name, url, extraClass }, last) { - const li = document.createElement("li"); - li.className = "link"; - if (last) li.classList.add("last"); - if (addon.tab.clientVersion !== "scratchr2" && typeof extraClass === "string") li.classList.add(extraClass); - - const a = document.createElement("a"); - const absolute = new URL(url, location.origin); - a.href = ["http:", "https:"].includes(absolute.protocol) ? absolute.toString() : ""; - li.append(a); - if (scratchr2List) { - // scratch's code looks for this id in account-nav.js (scratchr2 only). - a.id = "project-create"; - } - - const span = document.createElement("span"); - span.innerText = name; - a.append(span); - - return li; - } - function removeAllItems() { - if (scratchr2List) { - // We are on scratchr2! - if (originalNavbar.length < 4) { - originalNavbar.push( - ...Array.prototype.map.call(scratchr2List.children, (item) => ({ - name: item.innerText, - url: item.firstChild.href, - })) - ); - } - while (scratchr2List.firstChild) { - scratchr2List.removeChild(scratchr2List.lastChild); - } - return; - } - while (!searchItem.previousSibling.classList.contains("logo")) { - searchItem.previousSibling.remove(); - } - } - function init() { - removeAllItems(); - const items = addon.self.disabled - ? originalNavbar.length === 0 - ? (originalNavbar = [ - { name: addon.tab.scratchMessage("general.create"), url: "/projects/editor/", extraClass: "create" }, - { - name: addon.tab.scratchMessage("general.explore"), - url: "/explore/projects/all", - extraClass: "explore", - }, - { name: addon.tab.scratchMessage("general.ideas"), url: "/ideas", extraClass: "ideas" }, - { name: addon.tab.scratchMessage("general.about"), url: "/about", extraClass: "about" }, - ]) - : originalNavbar - : addon.settings.get("items"); - items.forEach((item, i) => { - if (scratchr2List) { - scratchr2List.append(createItem(item, i + 1 === items.length)); - return; - } - searchItem.parentElement.insertBefore(createItem(item), searchItem); - }); - } - init(); - addon.settings.addEventListener("change", init); - addon.self.addEventListener("disabled", init); - addon.self.addEventListener("reenabled", init); - } -} diff --git a/addons/drag-drop/userscript.js b/addons/drag-drop/userscript.js index bf5c5e9d5e..0a8cdaaacc 100644 --- a/addons/drag-drop/userscript.js +++ b/addons/drag-drop/userscript.js @@ -57,9 +57,9 @@ export default async function ({ addon, console }) { let el; let callback; if ( - (el = e.target.closest('div[class*="sprite-selector_sprite-selector"]')) || - (el = e.target.closest('div[class*="stage-selector_stage-selector"]')) || - (el = e.target.closest('div[class*="selector_wrapper"]')) + (el = e.target.closest('[class*="sprite-selector_sprite-selector"]')) || + (el = e.target.closest('[class*="stage-selector_stage-selector"]')) || + (el = e.target.closest('[class*="selector_wrapper_"]')) ) { callback = (files) => { const hdFilter = addon.settings.get("use-hd-upload") ? "" : ":not(.sa-better-img-uploads-input)"; @@ -114,9 +114,7 @@ export default async function ({ addon, console }) { // Simulate clicking on the "Import" option contextMenu.children[0].click(); }; - } else if ( - (el = e.target.closest('div[class*="question_question-input"] > input[class*="input_input-form_l9eYg"]')) - ) { + } else if ((el = e.target.closest('[class*="question_question-input"] > [class*="input_input-form_"]'))) { callback = async (files) => { const text = (await Promise.all(Array.from(files, (file) => file.text()))) .join("") diff --git a/addons/editor-buttons-reverse-order/userstyle.css b/addons/editor-buttons-reverse-order/userstyle.css index 4fac92154c..27128324d4 100644 --- a/addons/editor-buttons-reverse-order/userstyle.css +++ b/addons/editor-buttons-reverse-order/userstyle.css @@ -20,7 +20,7 @@ margin-right: 0.2rem; } -[class*="green-flag_green-flag_"] { +[class*="green-flag_green-flag-button_"] { order: -1; } @@ -28,7 +28,7 @@ order: -2; } -[class*="stop-all_stop-all_"] { +[class*="stop-all_stop-all-button_"] { order: -3; } diff --git a/addons/editor-dark-mode/zoom_icons.js b/addons/editor-dark-mode/zoom_icons.js index 4eee498e41..e3f52f5550 100644 --- a/addons/editor-dark-mode/zoom_icons.js +++ b/addons/editor-dark-mode/zoom_icons.js @@ -36,7 +36,8 @@ export default async function ({ addon, console }) { reduxEvents: [ "scratch-gui/mode/SET_PLAYER", "scratch-gui/locales/SELECT_LOCALE", - "scratch-gui/theme/SET_THEME", + "scratch-gui/settings/SET_COLOR_MODE", + "scratch-gui/settings/SET_THEME", "fontsLoaded/SET_FONTS_LOADED", ], reduxCondition: (state) => state.scratchGui.editorTab.activeTabIndex === 0 && !state.scratchGui.mode.isPlayerOnly, diff --git a/addons/editor-stage-left/stageleft.css b/addons/editor-stage-left/stageleft.css index 7fd8a6b233..22a13b7bf7 100644 --- a/addons/editor-stage-left/stageleft.css +++ b/addons/editor-stage-left/stageleft.css @@ -23,7 +23,7 @@ margin-right: calc(0.5rem / 2); margin-left: 0; } -[class^="gui_flex-wrapper"] { +[class*="gui_flex-wrapper_"] { flex-direction: row-reverse; overflow-x: hidden; } diff --git a/addons/editor-theme3/theme3.js b/addons/editor-theme3/theme3.js index 85cf0f9766..72ebda2b26 100644 --- a/addons/editor-theme3/theme3.js +++ b/addons/editor-theme3/theme3.js @@ -237,7 +237,8 @@ export default async function ({ addon, console, msg }) { "scratch-gui/mode/SET_PLAYER", "fontsLoaded/SET_FONTS_LOADED", "scratch-gui/locales/SELECT_LOCALE", - "scratch-gui/theme/SET_THEME", + "scratch-gui/settings/SET_COLOR_MODE", + "scratch-gui/settings/SET_THEME", "scratch-gui/monitors/UPDATE_MONITORS", ].includes(e.detail.action.type) ) { diff --git a/addons/gamepad/gamepadlib.js b/addons/gamepad/gamepadlib.js index bc2732ed0d..6a844da41f 100644 --- a/addons/gamepad/gamepadlib.js +++ b/addons/gamepad/gamepadlib.js @@ -1,3 +1,5 @@ +import { isFirefox } from "../../libraries/common/cs/detect-browser.js"; + let console = window.console; /* @@ -652,7 +654,7 @@ GamepadLib.browserHasBrokenGamepadAPI = () => { // Firefox on Linux before version 123 has a broken gamepad API that results in strange and unusable mappings // https://bugzilla.mozilla.org/show_bug.cgi?id=1680982 // https://bugzilla.mozilla.org/show_bug.cgi?id=1643835 - if (navigator.userAgent.includes("Firefox") && navigator.userAgent.includes("Linux")) { + if (isFirefox() && navigator.userAgent.includes("Linux")) { const agentMatch = navigator.userAgent.match(/Firefox\/(\d+)/); // If we couldn't find the version number, we'll assume that this is some distant future version of // Firefox that we just can't comprehend with the technology of today. Surely gamepad will work well @@ -662,7 +664,7 @@ GamepadLib.browserHasBrokenGamepadAPI = () => { } // Firefox on macOS has other bugs that result in strange and unusable mappings // eg. https://bugzilla.mozilla.org/show_bug.cgi?id=1434408 - if (navigator.userAgent.includes("Firefox") && navigator.userAgent.includes("Mac OS")) { + if (isFirefox() && navigator.userAgent.includes("Mac OS")) { return true; } return false; diff --git a/addons/hide-backpack/userscript.js b/addons/hide-backpack/userscript.js index be27bcf749..ce552316b1 100644 --- a/addons/hide-backpack/userscript.js +++ b/addons/hide-backpack/userscript.js @@ -11,7 +11,10 @@ export default async function ({ addon, console }) { addon.tab.redux.initialize(); addon.tab.redux.addEventListener("statechanged", (e) => { - if (e.detail.action.type === "scratch-gui/theme/SET_THEME" && !addon.self.disabled) { + if ( + ["scratch-gui/settings/SET_COLOR_MODE", "scratch-gui/settings/SET_THEME"].includes(e.detail.action.type) && + !addon.self.disabled + ) { // queueMicrotask isn't enough on new Blockly setTimeout(changeBackpackVisibility, 0); } diff --git a/addons/hide-delete-button/costumes.css b/addons/hide-delete-button/costumes.css index eedb08f8f5..7b0d6a43e1 100644 --- a/addons/hide-delete-button/costumes.css +++ b/addons/hide-delete-button/costumes.css @@ -1,3 +1,3 @@ -[class*="gui_tabs_"] > :nth-child(3) div[class*="delete-button_delete-button_"] { +[class*="gui_tabs_"] > :nth-child(3) [class*="delete-button_delete-button_"] { display: none; } diff --git a/addons/hide-delete-button/show-trash-shortcut.css b/addons/hide-delete-button/show-trash-shortcut.css index 7afcbeca6e..d27d515598 100644 --- a/addons/hide-delete-button/show-trash-shortcut.css +++ b/addons/hide-delete-button/show-trash-shortcut.css @@ -1,4 +1,4 @@ -.sa-show-trash div[class*="target-pane_target-pane_"]:hover div[class*="delete-button_delete-button_"], -.sa-show-trash div[class*="selector_wrapper_"]:hover div[class*="delete-button_delete-button_"] { +.sa-show-trash [class*="target-pane_target-pane_"]:hover [class*="delete-button_delete-button_"], +.sa-show-trash [class*="selector_wrapper_"]:hover [class*="delete-button_delete-button_"] { display: block !important; } diff --git a/addons/hide-delete-button/sounds.css b/addons/hide-delete-button/sounds.css index 5ad00678ad..cc62b11380 100644 --- a/addons/hide-delete-button/sounds.css +++ b/addons/hide-delete-button/sounds.css @@ -1,3 +1,3 @@ -[class*="gui_tabs_"] > :nth-child(4) div[class*="delete-button_delete-button_"] { +[class*="gui_tabs_"] > :nth-child(4) [class*="delete-button_delete-button_"] { display: none; } diff --git a/addons/hide-delete-button/sprites.css b/addons/hide-delete-button/sprites.css index 4a4d0fd299..b747c61d0f 100644 --- a/addons/hide-delete-button/sprites.css +++ b/addons/hide-delete-button/sprites.css @@ -1,3 +1,3 @@ -div[class*="sprite-selector_sprite-wrapper_"] div[class*="delete-button_delete-button_"] { +[class*="sprite-selector_sprite-wrapper_"] [class*="delete-button_delete-button_"] { display: none; } diff --git a/addons/hide-flyout/userscript.js b/addons/hide-flyout/userscript.js index 73335e853a..f0e395f3db 100644 --- a/addons/hide-flyout/userscript.js +++ b/addons/hide-flyout/userscript.js @@ -359,7 +359,8 @@ export default async function ({ addon, console, msg }) { reduxEvents: [ "scratch-gui/mode/SET_PLAYER", "scratch-gui/locales/SELECT_LOCALE", - "scratch-gui/theme/SET_THEME", + "scratch-gui/settings/SET_COLOR_MODE", + "scratch-gui/settings/SET_THEME", "fontsLoaded/SET_FONTS_LOADED", ], reduxCondition: (state) => !state.scratchGui.mode.isPlayerOnly, diff --git a/addons/player-thumb/userscript.js b/addons/player-thumb/userscript.js index 6421723d7f..02653d5834 100644 --- a/addons/player-thumb/userscript.js +++ b/addons/player-thumb/userscript.js @@ -7,7 +7,7 @@ export default async function ({ addon, console }) { thumb.classList = "sa-project-thumb loading"; thumb.draggable = false; - const stageWrapper = await addon.tab.waitForElement('div[class*="stage-wrapper_stage-wrapper_"]'); + const stageWrapper = await addon.tab.waitForElement('[class*="stage-wrapper_stage-wrapper_"]'); addon.tab.redux.initialize(); // It's possible this runs after the project loads even without dynamic enable @@ -16,7 +16,7 @@ export default async function ({ addon, console }) { if (addon.tab.editorMode === "editor") return; const alerts = document.querySelector(".project-info-alerts"); - const controls = stageWrapper.querySelector('div[class^="controls_controls-container_"]'); + const controls = stageWrapper.querySelector('[class*="controls_controls-container_"]'); controls.classList.add("sa-controls-disabled"); const loaderBackground = stageWrapper.querySelector('[class*="loader_background_"]'); diff --git a/addons/scratch3to2/comments.css b/addons/scratch3to2/comments.css index 3564ffa7b0..a1561f63a3 100644 --- a/addons/scratch3to2/comments.css +++ b/addons/scratch3to2/comments.css @@ -23,10 +23,13 @@ .comments-root-reply:empty { padding: 0; } -.comment .avatar { +.comment .avatar-wrapper, +.preview .comment .avatar-wrapper { margin-right: 10px; - width: 45px; - height: 45px; + --avatar-size: 45px; +} +.comment .avatar, +.preview .comment img.avatar { border-radius: 0; box-shadow: none; } diff --git a/addons/scratch3to2/explore_and_search.css b/addons/scratch3to2/explore_and_search.css index e61d3529cc..1760435aa7 100644 --- a/addons/scratch3to2/explore_and_search.css +++ b/addons/scratch3to2/explore_and_search.css @@ -199,8 +199,7 @@ body:not(.sa-body-editor) .outer .sort-mode .select select { border-radius: 0; } .grid .thumbnail .thumbnail-info .creator-image img { - margin-left: -1px; - margin-right: 7px; + box-sizing: border-box; border: 1px solid var(--darkWww-border-15, #ddd); border-radius: 0; } diff --git a/addons/scratch3to2/main.css b/addons/scratch3to2/main.css index d14c278723..69b6faf602 100644 --- a/addons/scratch3to2/main.css +++ b/addons/scratch3to2/main.css @@ -320,6 +320,7 @@ body:not(.sa-body-editor) .select select > option { background-size: auto; } .account-nav .user-info { + display: inline-block; height: 35px; padding: 0 10px; background-clip: padding-box; @@ -327,9 +328,14 @@ body:not(.sa-body-editor) .select select > option { border-right: 1px solid transparent; line-height: 35px; } -.account-nav .user-info .avatar { +.account-nav .user-info .avatar-wrapper { + vertical-align: middle; margin-top: -3px; margin-right: 5px; + --avatar-size: 26px; +} +.account-nav .user-info .avatar { + box-sizing: border-box; border: 1px solid #ccc; border-radius: 1px; } diff --git a/addons/scratch3to2/project.css b/addons/scratch3to2/project.css index 447d197953..66dcc4b412 100644 --- a/addons/scratch3to2/project.css +++ b/addons/scratch3to2/project.css @@ -48,8 +48,9 @@ /* Title */ .preview > .inner > .preview-row:first-child { - min-height: 45px; - padding: 7px 20px 2px; + min-height: 47px; + padding: 2px 20px; + align-items: center; background-color: var(--darkWww-gray-scratchr2, #f7f7f7); border-top: 1px solid var(--darkWww-gray-boxHighlight, white); border-bottom: 1px solid var(--darkWww-box-scratchr2Border, #e0e0e0); @@ -57,10 +58,12 @@ color: var(--darkWww-gray-scratchr2Text, #322f31); text-shadow: var(--darkWww-gray-scratchr2TextShadow, 0 1px white); } -.preview .project-header img.avatar { +.preview .project-header .avatar-wrapper { vertical-align: middle; - width: 40px; - height: 40px; + --avatar-size: 42px; +} +.preview .project-header img.avatar { + box-sizing: border-box; border: 1px solid var(--darkWww-border-15, #ccc); border-radius: 0; } @@ -70,12 +73,14 @@ line-height: 20px; } .preview .project-title { - margin-bottom: -3px; color: var(--darkWww-gray-scratchr2HeaderText, #554747); font-size: 22px; font-weight: bold; line-height: 26px; } +.preview .project-title.no-edit { + margin-bottom: -3px; +} .preview .project-header .inplace-input { height: 38px; padding: 0 10px; @@ -110,7 +115,6 @@ /* Remix, See inside, turbowarp-player */ .preview .project-buttons { - margin-top: 2px; margin-right: -2px; } .preview .remix-button, @@ -390,10 +394,12 @@ body:not(.sa-body-editor) [class*="stage_green-flag-overlay_"] > img { border-radius: 0; border-bottom: 1px solid var(--darkWww-border, #ddd); } -.preview img.avatar.remix { +.preview .remix-credit .avatar-wrapper { margin-right: 5px; - width: 30px; - height: 30px; + --avatar-size: 32px; +} +.preview img.avatar.remix { + box-sizing: border-box; border: 1px solid var(--darkWww-border-15, #ccc); border-radius: 0; } @@ -781,11 +787,6 @@ body:not(.sa-body-editor) [class*="stage_green-flag-overlay_"] > img { .preview .comments-container .comments-allowed-input { margin-right: 0; } -.preview .comment img.avatar { - width: 45px; - height: 45px; - border-radius: 0; -} .compose-comment .compose-bottom-row .compose-limit { color: var(--darkWww-box-scratchr2ButtonText, #666); } diff --git a/addons/scratch3to2/studio.css b/addons/scratch3to2/studio.css index 899690e060..b3972976d8 100644 --- a/addons/scratch3to2/studio.css +++ b/addons/scratch3to2/studio.css @@ -289,6 +289,7 @@ line-height: 0; } .studio-project-tile .studio-project-avatar { + box-sizing: border-box; border: 1px solid var(--darkWww-border-15, #ddd); border-radius: 0; } @@ -336,6 +337,7 @@ line-height: 0; } .studio-member-tile .studio-member-image { + box-sizing: border-box; border: 1px solid var(--darkWww-border-20, #ccc); border-radius: 0; } diff --git a/addons/scratchr2/scratchr2.css b/addons/scratchr2/scratchr2.css index 7c13a8a5c8..f20b751c31 100644 --- a/addons/scratchr2/scratchr2.css +++ b/addons/scratchr2/scratchr2.css @@ -311,6 +311,7 @@ select:focus { background-color: var(--darkWww-navbar-border, rgba(0, 0, 0, 0.1)); } #topnav ul.site-nav li a { + vertical-align: middle; height: 33px; padding: 13px 15px 4px 15px; color: var(--darkWww-navbar-text, white); diff --git a/addons/scratchr2/scratchr2_comments.css b/addons/scratchr2/scratchr2_comments.css index f757cd0fa2..6ce3ab597d 100644 --- a/addons/scratchr2/scratchr2_comments.css +++ b/addons/scratchr2/scratchr2_comments.css @@ -58,6 +58,13 @@ #comments .comment img.avatar { margin-right: 8px; } +#comments .comment .avatar-badge-wrapper { + margin-right: 8px; +} +#comments .comment .avatar-wrapper:not(.avatar-badge-wrapper) { + float: none; + margin-right: 0; +} #comments .comments .info, #comments .comments li ul li .comment .info { width: calc(100% - 54px); diff --git a/addons/zebra-striping/userscript.js b/addons/zebra-striping/userscript.js index 02b985c518..8c1f0e4287 100644 --- a/addons/zebra-striping/userscript.js +++ b/addons/zebra-striping/userscript.js @@ -74,7 +74,8 @@ export default async function ({ addon, msg, console }) { "scratch-gui/mode/SET_PLAYER", "fontsLoaded/SET_FONTS_LOADED", "scratch-gui/locales/SELECT_LOCALE", - "scratch-gui/theme/SET_THEME", + "scratch-gui/settings/SET_COLOR_MODE", + "scratch-gui/settings/SET_THEME", ], reduxCondition: (state) => !state.scratchGui.mode.isPlayerOnly, }); diff --git a/background/firefox-localhost-support.js b/background/firefox-localhost-support.js index 4aca52db9c..e49ec2f13d 100644 --- a/background/firefox-localhost-support.js +++ b/background/firefox-localhost-support.js @@ -1,4 +1,6 @@ -if (typeof browser === "object" && chrome.scripting) { +import { isFirefox } from "../libraries/common/cs/detect-browser.js"; + +if (isFirefox() && chrome.scripting) { const manifest = chrome.runtime.getManifest(); const manifestScripts = manifest.content_scripts.filter((script) => script.matches.includes("http://localhost:8601/*") diff --git a/background/get-addon-settings.js b/background/get-addon-settings.js index 59ffe891e1..d8016ff0f8 100644 --- a/background/get-addon-settings.js +++ b/background/get-addon-settings.js @@ -10,6 +10,7 @@ import { onReady } from "./imports/on-ready.js"; - editor-theme3 4 (last bumped in v1.39) - dark-www 7 (bumped twice in v1.34.0) - forum-quote-code-beautifier 1 (last bumped in v1.34) + - discuss-button 1 (last bumped in v1.44.3) */ // The following three functions are helper functions for the setting migration code @@ -744,6 +745,13 @@ chrome.storage.sync.get([...ADDON_SETTINGS_KEYS, "addonsEnabled"], (storageItems // Respect the value of enabledByDefault (by doing nothing) } } + + if (addonId === "discuss-button" && (settings._version || 0) < 1) { + // Transition v1.44.2 to v1.44.3 + if (settings.items) settings.items = settings.items.filter((i) => i.url !== "/about"); + settings._version = 1; + madeAnyChanges = madeChangesToAddon = true; + } } if (addonsEnabled[addonId] === undefined) addonsEnabled[addonId] = !!manifest.enabledByDefault; diff --git a/background/get-userscripts.js b/background/get-userscripts.js index 17e133e993..b8be148fb0 100644 --- a/background/get-userscripts.js +++ b/background/get-userscripts.js @@ -347,7 +347,7 @@ const WELL_KNOWN_PATTERNS = { // scratch-www routes, not including project pages // Matches /projects (an error page) but not /projects/ scratchWWWNoProject: - /^\/(?:(?:about|annual-report(?:\/\d+)?|camp|conference\/20(?:1[79]|[2-9]\d|18(?:\/(?:[^\/]+\/details|expect|plan|schedule))?)|contact-us|code-of-ethics|credits|developers|DMCA|download(?:\/(?:scratch2|scratch-link))?|educators(?:\/(?:faq|register|waiting))?|explore\/(?:project|studio)s\/\w+(?:\/\w+)?|community_guidelines|faq|ideas|join|messages|parents|privacy_policy(?:\/apps?)?|research|scratch_1\.4|search\/(?:project|studio)s|starter-projects|classes\/(?:complete_registration|[^\/]+\/register\/[^\/]+)|signup\/[^\/]+|terms_of_use|wedo(?:-legacy)?|ev3|microbit|vernier|boost|studios\/\d*(?:\/(?:projects|comments|curators|activity))?|components|become-a-scratcher|projects|cookies|accounts\/bad-username)\/?)?$/, + /^\/(?:(?:about|annual-report(?:\/\d+)?|camp|conference\/20(?:1[79]|[2-9]\d|18(?:\/(?:[^\/]+\/details|expect|plan|schedule))?)|contact-us|code-of-ethics|credits|developers|DMCA|download(?:\/(?:scratch2|scratch-link))?|educators(?:\/(?:faq|register|waiting))?|explore\/(?:project|studio)s\/\w+(?:\/\w+)?|community_guidelines|faq|ideas|join|messages|parents|privacy_policy(?:\/apps?)?|research|scratch_1\.4|search\/(?:project|studio)s|starter-projects|classes\/(?:complete_registration|[^\/]+\/register\/[^\/]+)|signup\/[^\/]+|terms_of_use|wedo(?:-legacy)?|ev3|microbit|vernier|boost|studios\/\d*(?:\/(?:projects|comments|curators|activity))?|components|become-a-scratcher|projects|cookies|accounts\/bad-username|membership)\/?)?$/, }; const WELL_KNOWN_MATCHERS = { diff --git a/background/handle-auth.js b/background/handle-auth.js index 0c0a55e523..64c50aa805 100644 --- a/background/handle-auth.js +++ b/background/handle-auth.js @@ -1,6 +1,7 @@ import { startCache } from "./message-cache.js"; import { openMessageCache } from "../libraries/common/message-cache.js"; import { purgeDatabase } from "../addons/scratch-notifier/notifier.js"; +import { isFirefox } from "../libraries/common/cs/detect-browser.js"; async function getDefaultStoreId() { const CHROME_DEFAULT = "0"; @@ -168,8 +169,7 @@ function notify(cookie) { if (cookie.name === "scratchlanguage") return; const storeId = cookie.storeId; const cond = {}; - if (typeof browser === "object") { - // Firefox-exclusive. + if (isFirefox()) { cond.cookieStoreId = storeId; } // On Chrome this can cause unnecessary session re-fetch, but there should be diff --git a/background/handle-clipboard.js b/background/handle-clipboard.js index 35934660f0..9116d350f7 100644 --- a/background/handle-clipboard.js +++ b/background/handle-clipboard.js @@ -1,3 +1,5 @@ +import { isFirefox } from "../libraries/common/cs/detect-browser.js"; + const dataURLToArrayBuffer = function (dataURL) { const byteString = atob(dataURL.split(",")[1]); const arrayBuffer = new ArrayBuffer(byteString.length); @@ -8,8 +10,7 @@ const dataURLToArrayBuffer = function (dataURL) { return arrayBuffer; }; -if (typeof browser !== "undefined") { - // Firefox +if (isFirefox()) { browser.runtime.onMessage.addListener(function (request) { if (request.clipboardDataURL && browser && browser.clipboard && browser.clipboard.setImageData) { const arrayBuffer = dataURLToArrayBuffer(request.clipboardDataURL); diff --git a/background/imports/util.js b/background/imports/util.js index 766fa25aaa..a6e907b2b5 100644 --- a/background/imports/util.js +++ b/background/imports/util.js @@ -1,7 +1,8 @@ +import { isFirefox } from "../../libraries/common/cs/detect-browser.js"; + // REMINDER: update similar code at /webpages/settings/index.js const browserLevelPermissions = ["notifications"]; -if (typeof browser !== "undefined") { - // Firefox only +if (isFirefox()) { if (typeof Clipboard.prototype.write !== "function") { // Firefox 109-126 only browserLevelPermissions.push("clipboardWrite"); diff --git a/libraries/common/cs/detect-browser.js b/libraries/common/cs/detect-browser.js new file mode 100644 index 0000000000..b94b803747 --- /dev/null +++ b/libraries/common/cs/detect-browser.js @@ -0,0 +1,3 @@ +export function isFirefox() { + return typeof new Error().fileName !== "undefined"; +} diff --git a/libraries/common/notification-util.js b/libraries/common/notification-util.js index ac1ed8d289..69273ced45 100644 --- a/libraries/common/notification-util.js +++ b/libraries/common/notification-util.js @@ -1,3 +1,5 @@ +import { isFirefox } from "./cs/detect-browser.js"; + let id = 0; export default function create(opts) { @@ -7,7 +9,7 @@ export default function create(opts) { if (scratchAddons.muted) return Promise.resolve(null); const notifId = `${opts.base}__${Date.now()}_${id++}`; let newOpts; - if (!/Chrom/.test(navigator.userAgent)) { + if (isFirefox()) { newOpts = JSON.parse(JSON.stringify(opts)); // On Firefox, remove notification properties that throw. delete newOpts.buttons; diff --git a/manifest.json b/manifest.json index e939af5586..5929483e28 100644 --- a/manifest.json +++ b/manifest.json @@ -2,8 +2,8 @@ "manifest_version": 3, "name": "__MSG_extensionName__", "description": "__MSG_extensionDescription__", - "version": "1.44.1", - "version_name": "1.44.1", + "version": "1.44.5", + "version_name": "1.44.5", "default_locale": "en", "background": { "service_worker": "background/background.js", diff --git a/webpages/settings/index.js b/webpages/settings/index.js index 6d350ee95c..64602ae406 100644 --- a/webpages/settings/index.js +++ b/webpages/settings/index.js @@ -9,6 +9,7 @@ import exampleManifest from "./data/example-manifest.js"; import fuseOptions from "./data/fuse-options.js"; import globalTheme from "../../libraries/common/global-theme.js"; import { deserializeSettings, serializeSettings } from "./settings-utils.js"; +import { isFirefox } from "../../libraries/common/cs/detect-browser.js"; let isIframe = false; if (window.parent !== window) { @@ -63,8 +64,7 @@ let fuse; // REMINDER: update similar code at /background/imports/util.js const browserLevelPermissions = ["notifications"]; - if (typeof browser !== "undefined") { - // Firefox only + if (isFirefox()) { if (typeof Clipboard.prototype.write !== "function") { // Firefox 109-126 only browserLevelPermissions.push("clipboardWrite"); @@ -384,8 +384,7 @@ let fuse; // Autofocus search bar in iframe mode for both browsers // autofocus attribute only works in Chrome for us, so // we also manually focus on Firefox, even in fullscreen - if (isIframe || typeof browser !== "undefined") - setTimeout(() => document.getElementById("searchBox")?.focus(), 0); + if (isIframe || isFirefox()) setTimeout(() => document.getElementById("searchBox")?.focus(), 0); const exampleAddonListItem = { // Need to specify all used properties for reactivity!