Skip to content

Commit abef64c

Browse files
authored
Merge branch 'main' into Picture-in-Picture
2 parents 438d7b4 + 9704f85 commit abef64c

File tree

128 files changed

+3090
-632
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

128 files changed

+3090
-632
lines changed

.github/ISSUE_TEMPLATE/--bug.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: 🐛 Bug
2-
description: Report a bug for ScratchTool.
2+
description: Report a bug in ScratchTools.
33
labels: ["type: bug", "status: needs review"]
44
body:
55

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
4+
const rootDir = path.resolve(__dirname, "../../features");
5+
const outputFile = path.resolve(__dirname, "../../class-names.json");
6+
7+
let collected = new Set();
8+
9+
function traverseDir(dir) {
10+
const files = fs.readdirSync(dir);
11+
for (const file of files) {
12+
const fullPath = path.join(dir, file);
13+
const stat = fs.statSync(fullPath);
14+
15+
if (stat.isDirectory()) {
16+
traverseDir(fullPath);
17+
} else if (stat.isFile() && file.endsWith(".js")) {
18+
const content = fs.readFileSync(fullPath, "utf-8");
19+
const regex = /className\(["'`](.*?)["'`]\)/g;
20+
let match;
21+
while ((match = regex.exec(content)) !== null) {
22+
collected.add({
23+
className: "ste-" + match[1].replaceAll(" ", "-"),
24+
features: [
25+
dir.split("/features/")[1].split("/")[0].replaceAll(".js", ""),
26+
],
27+
});
28+
}
29+
}
30+
}
31+
}
32+
33+
traverseDir(rootDir);
34+
35+
const mergedFeatures = Object.values(
36+
[...collected].reduce((acc, item) => {
37+
if (!acc[item.className]) {
38+
acc[item.className] = { className: item.className, features: new Set() };
39+
}
40+
item.features.forEach(f => acc[item.className].features.add(f));
41+
return acc;
42+
}, {})
43+
).map(obj => ({ className: obj.className, features: [...obj.features] }));
44+
45+
fs.writeFileSync(outputFile, JSON.stringify(mergedFeatures, null, 2));
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Extract Class Names
2+
3+
on:
4+
push:
5+
paths:
6+
- "features/**.js"
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: write
11+
pull-requests: write
12+
13+
jobs:
14+
extract:
15+
if: github.repository == 'STForScratch/ScratchTools'
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Set up Node.js
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: 20
25+
26+
- name: Run extraction script
27+
run: node .github/scripts/extract-classnames.js
28+
29+
- name: Check for changes
30+
id: git-diff
31+
run: |
32+
if git diff --quiet --exit-code class-names.json; then
33+
echo "changed=false" >> $GITHUB_OUTPUT
34+
else
35+
echo "changed=true" >> $GITHUB_OUTPUT
36+
fi
37+
38+
- name: Create or Update Pull Request
39+
if: steps.git-diff.outputs.changed == 'true'
40+
uses: peter-evans/create-pull-request@v6
41+
with:
42+
commit-message: "Update class-names.json"
43+
branch: update-class-names
44+
title: "Update class names"
45+
body: "Automated update of class-names.json"
46+
labels: automated
47+
assignees: rgantzos
48+
delete-branch: true

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ There are multiple ways of installing.
4040
- Chrome: You can download from Chrome's Extension Webstore [here](https://chrome.google.com/webstore/detail/scratchtools/jjnpbalpllpfdpgplpbcbadkgdmleopm). Then just press the Add to Chrome button, and you've downloaded ScratchTools!
4141

4242
> [!NOTE]
43-
> If you are using a browser based on Chromium (eg. Brave), then this is your way of installing unless your browser has it's own extension store.
43+
> If you are using a browser based on Chromium (eg. Brave), then this is your way of installing unless your browser has its own extension store.
4444
4545
- Firefox/Mozilla: You can download from Firefox Addons (Works on Firefox forks) [here](https://addons.mozilla.org/en-US/firefox/addon/scratchtools/). You can then just add it to Firefox, and then you have ScratchTools!
4646

4747
> [!NOTE]
48-
> The Firefox version of Scratchtools is behind compared to the chrome version of Scratchtools due to technical difficulties. Until a solution is found, the Firefox version will remain behind.
48+
> The Firefox version of ScratchTools is behind compared to the Chrome version of ScratchTools due to technical difficulties. Until a solution is found, the Firefox version will remain behind.
4949
5050
- Microsoft Edge: You can download from Edge's addon webstore [here](https://microsoftedge.microsoft.com/addons/detail/scratchtools/aaidjeidbnhpjhblbianjeghjopbimmk). You can then just add it to Edge, and then you have ScratchTools!
5151
- Safari (macOS, iPadOS and iOS): You can build the extension by typing `make` for macOS, and `make ios` for the iOS app (you will have to sign it on Xcode), make sure you have enabled Developer mode and allowed unsigned extensions.
@@ -55,7 +55,7 @@ There are multiple ways of installing.
5555
- GitHub (For Firefox & Firefox Forks): Download from the GitHub repository [here](https://github.com/STForScratch/ScratchTools/zipball/master). After the `.zip` file is downloaded, unpack it. Then, with the folder, go to `about:debugging`, click "This Firefox" and click "Load temporary extension", go into the extension folder and select the `manifest.json`.
5656

5757
> [!WARNING]
58-
> Extensions loaded this way onto Safari indeed temporary. Once you close the window, it will be gone. In addition, ScratchTools is still outdated on Firefox at the time of writing this.
58+
> Extensions loaded this way onto Safari are temporary. Once you close the window, it will be gone.
5959
6060
- Installing with Git: Open the code dropdown on the extension's repository, copy the HTTPS url and then execute `git clone https://github.com/STForScratch/ScratchTools.git -b main` in Git, and you have installed ScratchTools! To pull changes instead of cloning the repository, enter the folder and execute `git remote add upstream https://github.com/STForScratch/ScratchTools.git`. When a commit is made to the repository, you can just run `git pull upstream main` to pull the changes, note that sometimes you may need to refresh ScratchTools.
6161

@@ -77,7 +77,7 @@ Using `ScratchTools.Auth`, you can access the authentication info for the signed
7777
Using `ScratchTools.Scratch.blockly` and `ScratchTools.Scratch.vm`, you can access the Blockly and Virtual Machine from inside the editor (or on the project page with vm). Blockly must wait for the editor to load, but the virtual machine is ready instantly.
7878
##### Blockly Context Menus
7979
If you want to control what appears in a context menu, you easily can with the `ScratchTools.Scratch.waitForContextMenu()` API. The only input you need is JSON, which must include the block ID for the context menu, the ID you want to set for the context menu option (lets you change the context menu option, so don't use the same ID as another feature), and the callback for when the context menu is opened. The callback function will also have an input, which is the context menu itself. That way, you can add the context menu option when the context menu is opened.
80-
##### Sound , GUI and Paint-Mode
80+
##### Sound, GUI and Paint-Mode
8181
Using `ScratchTools.Scratch.scratchSound`and `ScratchTools.Scratch.scratchGui`, you can return sound from the editor as well as access Graphical User Interface inside the editor. `ScratchTools.Scratch.scratchPaint` can be used in selecting the paint editor mode.
8282

8383
#### Logging

_locales/en/messages.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"message": "ScratchTools is fully customizable with tons of features, all for making the Scratch website better and easier to use!"
77
},
88
"supportButton": {
9-
"message": "Get support"
9+
"message": "Get live support"
1010
},
1111
"feedbackButton": {
1212
"message": "Give feedback"

api/feature.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ class Feature {
66
finalFeature = el;
77
}
88
});
9+
this.requestPermissions = async function(...permissions) {
10+
return await ScratchTools.sendMessage("request-perms", permissions)
11+
}
912
this.data = finalFeature;
1013
this.msg = function (string) {
1114
return this.data.localesData[`${this.data.id}/`+string] || `ScratchTools.${this.data.id}.${string}`;
@@ -81,6 +84,15 @@ class Feature {
8184
path: window.location.pathname,
8285
scratch: document.querySelector("#app") ? 3 : 2,
8386
}
87+
this.getInternals = function(element) {
88+
let reactKey = Object.keys(element).find((key) => key.startsWith("__reactInternalInstance"))
89+
if (!reactKey) return null;
90+
91+
return element[reactKey]
92+
}
93+
this.getInternalKey = function(element) {
94+
return Object.keys(element).find((key) => key.startsWith("__reactInternalInstance")) || null
95+
}
8496
this.redux = document.querySelector("#app")?.[
8597
Object.keys(app).find((key) => key.startsWith("__reactContainer"))
8698
].child.stateNode.store

api/main.js

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,24 @@ ScratchTools.Storage = {};
7474
ScratchTools.Resources = {};
7575
ste.console.log("ScratchTools API Created", "ste-main");
7676

77+
ScratchTools.cssFiles = [];
78+
async function updateCSSFiles() {
79+
let activeCSSFiles = Array.from(document.styleSheets)
80+
.filter((sheet) => sheet.href)
81+
.map((sheet) => sheet.href)
82+
.filter((el) => new URL(el).host === "scratch.mit.edu");
83+
activeCSSFiles = activeCSSFiles.filter(
84+
(el) => !ScratchTools.cssFiles.find((e) => e.url === el)
85+
);
86+
87+
for (var i in activeCSSFiles) {
88+
ScratchTools.cssFiles.push({
89+
url: activeCSSFiles[i],
90+
data: await (await fetch(activeCSSFiles[i])).text(),
91+
});
92+
}
93+
}
94+
7795
if (
7896
window.location.href.startsWith("https://scratch.mit.edu/projects/") &&
7997
window.location.href.includes("/editor")
@@ -83,6 +101,23 @@ if (
83101
ScratchTools.type = "Website";
84102
}
85103

104+
ScratchTools.MESSAGES = []
105+
ScratchTools.sendMessage = function(id, content) {
106+
let uuid = UUID()
107+
chrome.runtime.sendMessage(ScratchTools.id, { message: id, content, source: "message-api", uuid });
108+
return new Promise((resolve, reject) => {
109+
ScratchTools.MESSAGES.push({ message: id, source: "message-api", uuid, resolve });
110+
});
111+
}
112+
113+
function UUID() {
114+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (char) {
115+
const random = Math.random() * 16 | 0;
116+
const value = char === 'x' ? random : (random & 0x3 | 0x8);
117+
return value.toString(16);
118+
});
119+
}
120+
86121
var storagePromises = [];
87122
ScratchTools.storage = {
88123
get: async function (key) {
@@ -159,6 +194,7 @@ function enableScratchToolsSelectorsMutationObserver() {
159194
enableScratchToolsSelectorsMutationObserver();
160195

161196
function returnScratchToolsSelectorsMutationObserverCallbacks() {
197+
updateCSSFiles()
162198
Object.keys(allWaitInstances).forEach(function (key) {
163199
var waitInstance = allWaitInstances[key];
164200
if (!waitInstance.removed) {
@@ -340,6 +376,39 @@ ScratchTools.styles = {
340376
},
341377
};
342378

379+
function scratchClass(name) {
380+
let element = document.querySelector(`[class*='${name}']`);
381+
if (element) {
382+
let classes = [...element.classList];
383+
return classes.find((el) => el.includes(name));
384+
} else {
385+
let text = []
386+
387+
for (var i in ScratchTools.cssFiles) {
388+
text.push(ScratchTools.cssFiles[i].data)
389+
}
390+
391+
text = text.join("\n\n")
392+
let classes = ScratchTools.getClassNamesFromCSSText(text)
393+
394+
let relClass = classes.find((el) => el.includes(name))
395+
return relClass
396+
}
397+
}
398+
399+
ScratchTools.getClassNamesFromCSSText = function(cssText) {
400+
const classNames = new Set();
401+
402+
const classRegex = /\.([a-zA-Z0-9_-]+)\b/g;
403+
404+
let match;
405+
while ((match = classRegex.exec(cssText)) !== null) {
406+
classNames.add(match[1]);
407+
}
408+
409+
return Array.from(classNames);
410+
}
411+
343412
ScratchTools.waitForElements(
344413
"ul[class*='menu_menu_'][class*='menu_right_']",
345414
function (ul) {
@@ -351,10 +420,13 @@ ScratchTools.waitForElements(
351420
if (!ul.querySelector(".ste-menu-full-settings")) {
352421
var li = document.createElement("li");
353422
li.className =
354-
"ste-menu-full-settings menu_menu-item_3EwYA menu_hoverable_3u9dt";
423+
"ste-menu-full-settings " +
424+
scratchClass("menu_menu-item_") +
425+
" " +
426+
scratchClass("menu_hoverable_");
355427

356428
var div = document.createElement("div");
357-
div.className = "settings-menu_option_3rMur";
429+
div.className = "settings-menu_option_GGukG";
358430

359431
var icon = document.createElement("img");
360432
icon.src = ScratchTools.icons.main;
@@ -391,6 +463,8 @@ async function blockliveDetection() {
391463
Object.keys(app).find((key) => key.startsWith("__reactContainer"))
392464
].child.stateNode.store.getState()?.scratchGui;
393465
if (!gui?.projectState) return;
394-
let detectBlocklive = await import("./blocklive-detection/blocklive-detect.js");
466+
let detectBlocklive = await import(
467+
"./blocklive-detection/blocklive-detect.js"
468+
);
395469
detectBlocklive.default();
396-
}
470+
}

api/module.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
let allFeatures = []
22
let alreadyInjected = [];
33

4+
function scratchClass(name) {
5+
let element = document.querySelector(`[class*='${name}']`);
6+
if (element) {
7+
let classes = [...element.classList];
8+
return classes.find((el) => el.includes(name));
9+
} else {
10+
let text = []
11+
12+
for (var i in ScratchTools.cssFiles) {
13+
text.push(ScratchTools.cssFiles[i].data)
14+
}
15+
16+
text = text.join("\n\n")
17+
let classes = ScratchTools.getClassNamesFromCSSText(text)
18+
19+
let relClass = classes.find((el) => el.includes(name))
20+
return relClass
21+
}
22+
}
23+
24+
function className(name) {
25+
return "ste-" + name.toLowerCase().replaceAll(" ", "-")
26+
}
27+
428
ScratchTools.modules.forEach(async function (script) {
529
var feature = await import(ScratchTools.dir + "/api/feature/index.js");
630
var shouldBeRun = true;
@@ -20,6 +44,8 @@ ScratchTools.modules.forEach(async function (script) {
2044
allFeatures.push(featureGenerated)
2145
fun.default({
2246
feature: featureGenerated,
47+
scratchClass,
48+
className,
2349
console: {
2450
log: function (content) {
2551
ste.console.log(content, script.feature.id);
@@ -56,6 +82,8 @@ ScratchTools.injectModule = async function (script) {
5682
allFeatures.push(featureGenerated)
5783
fun.default({
5884
feature: featureGenerated,
85+
scratchClass,
86+
className,
5987
console: {
6088
log: function (content) {
6189
ste.console.log(content, script.feature.id);

api/update/changelogs/forum.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"active": false,
3+
"title": "Forum Changes for {{ version }}",
4+
"description": "",
5+
"changes": []
6+
}
7+

0 commit comments

Comments
 (0)