First Commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
48
extension/background.js
Normal file
48
extension/background.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const activeTabs = {};
|
||||
let contentScriptRerunner = null;
|
||||
|
||||
function runContentScript(tabId) {
|
||||
chrome.tabs.executeScript(tabId, {
|
||||
allFrames: true,
|
||||
file: 'run.js',
|
||||
});
|
||||
}
|
||||
|
||||
function rerunContentScripts() {
|
||||
// unfortunately we need to rerun periodically to handle iframes changing..
|
||||
// eventually fixed when we require the new host permission or chrome releases a content script api
|
||||
for (const tabId of Object.keys(activeTabs)) {
|
||||
runContentScript(parseInt(tabId, 10));
|
||||
}
|
||||
}
|
||||
|
||||
function registerDynamicContentScript() {
|
||||
chrome.tabs.onUpdated.addListener((tabId, {status}, {url}) => {
|
||||
if (!status || !url) {
|
||||
return;
|
||||
}
|
||||
|
||||
runContentScript(tabId);
|
||||
activeTabs[tabId] = true;
|
||||
|
||||
if (contentScriptRerunner == null) {
|
||||
contentScriptRerunner = setInterval(rerunContentScripts, 5000);
|
||||
}
|
||||
});
|
||||
chrome.tabs.onRemoved.addListener((tabId) => {
|
||||
delete activeTabs[tabId];
|
||||
|
||||
if (contentScriptRerunner != null && Object.keys(activeTabs).length === 0) {
|
||||
clearInterval(contentScriptRerunner);
|
||||
contentScriptRerunner = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
chrome.runtime.onInstalled.addListener(({reason}) => {
|
||||
if (reason !== 'install') {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
registerDynamicContentScript();
|
||||
BIN
extension/icon.png
Normal file
BIN
extension/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
34
extension/manifest.json
Normal file
34
extension/manifest.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"background": {
|
||||
"scripts": ["background.js"]
|
||||
},
|
||||
"browser_action": {
|
||||
"default_popup": "settings.html"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"all_frames": true,
|
||||
"exclude_matches": [
|
||||
"*://*.twitch.tv/*.html",
|
||||
"*://*.twitch.tv/*.html?*",
|
||||
"*://*.twitch.tv/*.htm",
|
||||
"*://*.twitch.tv/*.htm?*"
|
||||
],
|
||||
"js": ["run.js"],
|
||||
"matches": ["*://*.twitch.tv/*"]
|
||||
}
|
||||
],
|
||||
"description": "Add notes to twitch users",
|
||||
"externally_connectable": {
|
||||
"matches": ["*://*.twitch.tv/*"]
|
||||
},
|
||||
"homepage_url": "https://rdarius.lt/projects/chrome-extensions/twitch-notes",
|
||||
"icons": {
|
||||
"128": "icon.png"
|
||||
},
|
||||
"manifest_version": 2,
|
||||
"name": "Twitch Notes",
|
||||
"permissions": ["*://*.twitch.tv/*"],
|
||||
"version": "0.0.1",
|
||||
"web_accessible_resources": ["twitchnotes.js", "twitchnotes.css"]
|
||||
}
|
||||
46
extension/notes-notepad-svgrepo-com.svg
Normal file
46
extension/notes-notepad-svgrepo-com.svg
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 508.1 508.1" style="enable-background:new 0 0 508.1 508.1;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M440.85,55.3h-168.3v-0.7c0-30.1-24.5-54.6-54.6-54.6c-30.1,0-54.6,24.5-54.6,54.6c0,0.2,0.1,0.5,0.1,0.7h-31.7
|
||||
c-7.8,0-14.1,6.3-14.1,14.1v43.4h-50.4c-7.8,0-14.1,6.3-14.1,14.1v262.4c0,3.7,1.5,7.3,4.1,10L161.85,504c2.6,2.6,6.2,4.1,10,4.1
|
||||
h204.5c7.8,0,14.1-6.3,14.1-14.1v-37.5h50.4c7.8,0,14.1-6.3,14.1-14.1v-373C454.95,61.6,448.65,55.3,440.85,55.3z M191.55,54.6
|
||||
c0-14.5,11.8-26.4,26.4-26.4c14.6,0,26.4,11.8,26.4,26.4v0.7h-52.8C191.55,55.1,191.55,54.8,191.55,54.6z M145.85,83.5h98.5v29.3
|
||||
h-98.5V83.5z M244.35,141v31c0,14.5-11.8,26.4-26.4,26.4s-26.4-11.8-26.4-26.4v-31H244.35z M157.75,459.8l-56.5-56.5h56.5V459.8z
|
||||
M362.25,479.8h-176.2v-90.5c0-7.8-6.3-14.1-14.1-14.1h-90.6V141h82v31c0,30.1,24.5,54.6,54.6,54.6c30.1,0,54.6-24.5,54.6-54.6
|
||||
v-31h89.7V479.8z M426.75,428.1h-36.3V126.9c0-7.8-6.3-14.1-14.1-14.1h-103.8V83.5h154.2V428.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
110
extension/run.js
Normal file
110
extension/run.js
Normal file
@@ -0,0 +1,110 @@
|
||||
(function twitchnotes() {
|
||||
if (document.querySelector("script#twitchnotes")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.createElement("script");
|
||||
script.type = "text/javascript";
|
||||
script.src = chrome.runtime.getURL("twitchnotes.js");
|
||||
script.id = "twitchnotes";
|
||||
const head = document.getElementsByTagName("head")[0];
|
||||
if (!head) {
|
||||
return;
|
||||
}
|
||||
|
||||
head.appendChild(script);
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.innerHTML = `
|
||||
:root {
|
||||
--window-close: #f77669;
|
||||
--window-minify: #ffcb6b;
|
||||
--window-expand: #c3e88d;
|
||||
|
||||
--background-header: #393d3e;
|
||||
--background-code: #1f1e1e;
|
||||
}
|
||||
|
||||
.twitchnote img {
|
||||
height: 18px;
|
||||
padding: 0 4px 0;
|
||||
}
|
||||
|
||||
.twitch-note-container {
|
||||
position: fixed;
|
||||
z-index: 1000000;
|
||||
min-width: 420px;
|
||||
max-width: 95vw;
|
||||
min-height: 240px;
|
||||
max-height: 50vh;
|
||||
background: var(--color-background-base);
|
||||
border: var(--border-width-default) solid var(--color-border-base) !important;
|
||||
}
|
||||
|
||||
.twitch-note-header {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border-bottom: var(--border-width-default) solid var(--color-border-base) !important;
|
||||
position: relative;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.twitch-note-title {
|
||||
color: var(--color-text-alt) !important;
|
||||
font-size: var(--font-size-6) !important;
|
||||
font-weight: var(--font-weight-semibold) !important;
|
||||
text-transform: uppercase !important;
|
||||
padding-left: calc((32px - var(--font-size-6)) / 2);
|
||||
}
|
||||
|
||||
.twitch-note-close-button {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
aspect-ration: 1;
|
||||
color: var(--color-text-alt) !important;
|
||||
font-size: var(--font-size-6) !important;
|
||||
font-weight: var(--font-weight-semibold) !important;
|
||||
text-transform: uppercase !important;
|
||||
border: var(--border-width-default) solid var(--color-border-base) !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.twitch-note-content {
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 32px;
|
||||
background: transparent;
|
||||
color: var(--color-text-base) !important;
|
||||
font-family: var(--font-base);
|
||||
vertical-align: baseline;
|
||||
outline: none;
|
||||
overflow: auto;
|
||||
padding: calc((32px - var(--font-size-6)) / 2);
|
||||
}
|
||||
|
||||
.twitch-note-save-button {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 32px;
|
||||
border-top: var(--border-width-default) solid var(--color-border-base) !important;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-alt) !important;
|
||||
font-size: var(--font-size-6) !important;
|
||||
font-weight: var(--font-weight-semibold) !important;
|
||||
text-transform: uppercase !important;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
}
|
||||
`;
|
||||
head.appendChild(style);
|
||||
})();
|
||||
3
extension/settings.html
Normal file
3
extension/settings.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div style="width: 250px">
|
||||
Exporting, Importing and Data clearing will be available in the future
|
||||
</div>
|
||||
200
extension/twitchnotes.js
Normal file
200
extension/twitchnotes.js
Normal file
@@ -0,0 +1,200 @@
|
||||
let mousePosition = { x: 0, y: 0 };
|
||||
let lastMousePosition = { x: 0, y: 0 };
|
||||
let openContainers = {};
|
||||
let isMouseDown = false;
|
||||
let activeContainer = null;
|
||||
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
|
||||
function saveNote(username, note) {
|
||||
let userList = JSON.parse(
|
||||
localStorage.getItem("twitch-note-all-users-list") || "[]"
|
||||
);
|
||||
|
||||
if (!userList.includes(username)) {
|
||||
userList.push(username);
|
||||
localStorage.setItem(
|
||||
"twitch-note-all-users-list",
|
||||
JSON.stringify(userList)
|
||||
);
|
||||
}
|
||||
|
||||
localStorage.setItem("twitch-note-" + username, note);
|
||||
}
|
||||
function getNote(username) {
|
||||
return localStorage.getItem("twitch-note-" + username) || "";
|
||||
}
|
||||
|
||||
function handleMouseMove(event) {
|
||||
event = event || window.event;
|
||||
if (isMouseDown) {
|
||||
if (activeContainer) {
|
||||
const dx = mousePosition.x - lastMousePosition.x;
|
||||
const dy = mousePosition.y - lastMousePosition.y;
|
||||
openContainers[activeContainer].style.top =
|
||||
parseInt(openContainers[activeContainer].style.top) + dy + "px";
|
||||
openContainers[activeContainer].style.left =
|
||||
parseInt(openContainers[activeContainer].style.left) + dx + "px";
|
||||
}
|
||||
}
|
||||
updateMousePosition(event.clientX, event.clientY);
|
||||
}
|
||||
|
||||
function handleMouseUp(event) {
|
||||
activeContainer = null;
|
||||
isMouseDown = false;
|
||||
}
|
||||
|
||||
function updateMousePosition(x, y) {
|
||||
lastMousePosition = { ...mousePosition };
|
||||
mousePosition.x = x;
|
||||
mousePosition.y = y;
|
||||
}
|
||||
|
||||
function removeContainer(username) {
|
||||
if (!openContainers[username]) return;
|
||||
openContainers[username].remove();
|
||||
delete openContainers[username];
|
||||
}
|
||||
|
||||
function addContainer(username, container) {
|
||||
if (openContainers[username]) return;
|
||||
openContainers[username] = container;
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
function openTwitchNote(username) {
|
||||
// CONTAINER
|
||||
const container = document.createElement("div");
|
||||
container.style.left = mousePosition.x + "px";
|
||||
container.style.top = mousePosition.y + 10 + "px";
|
||||
container.classList.add("twitch-note-container");
|
||||
|
||||
// HEADER
|
||||
const header = document.createElement("div");
|
||||
header.classList.add("twitch-note-header");
|
||||
header.addEventListener("mousedown", function close() {
|
||||
activeContainer = username;
|
||||
isMouseDown = true;
|
||||
});
|
||||
const closeButton = document.createElement("span");
|
||||
closeButton.classList.add("twitch-note-close-button");
|
||||
closeButton.innerText = "X";
|
||||
closeButton.addEventListener("click", function close() {
|
||||
removeContainer(username);
|
||||
});
|
||||
header.appendChild(closeButton);
|
||||
const title = document.createElement("span");
|
||||
title.classList.add("twitch-note-title");
|
||||
title.innerHTML = username;
|
||||
header.appendChild(title);
|
||||
container.appendChild(header);
|
||||
|
||||
// BODY
|
||||
const content = document.createElement("div");
|
||||
content.classList.add("twitch-note-content");
|
||||
content.setAttribute("contentEditable", true);
|
||||
content.innerHTML = getNote(username);
|
||||
|
||||
container.appendChild(content);
|
||||
|
||||
const saveButton = document.createElement("div");
|
||||
saveButton.classList.add("twitch-note-save-button");
|
||||
saveButton.innerText = "SAVE";
|
||||
saveButton.addEventListener("click", function () {
|
||||
saveNote(username, content.innerHTML);
|
||||
removeContainer(username);
|
||||
});
|
||||
|
||||
container.appendChild(saveButton);
|
||||
|
||||
addContainer(username, container);
|
||||
}
|
||||
|
||||
(function runTwitchNotes() {
|
||||
// Select the node that will be observed for mutations
|
||||
|
||||
let giveUpAt = Date.now() + 20000;
|
||||
|
||||
let changeInProgress = false;
|
||||
|
||||
let targetNode;
|
||||
let buttonContainer;
|
||||
|
||||
do {
|
||||
targetNode = document.getElementsByClassName(
|
||||
"chat-scrollable-area__message-container"
|
||||
)[0];
|
||||
} while (!targetNode || giveUpAt < Date.now());
|
||||
|
||||
do {
|
||||
buttonContainer = document.getElementsByClassName(
|
||||
"chat-input__buttons-container"
|
||||
)[0];
|
||||
} while (!buttonContainer || giveUpAt < Date.now());
|
||||
|
||||
const settingsButton = document.createElement("div");
|
||||
settingsButton.innerHTML = "<b>Notes</b>";
|
||||
|
||||
buttonContainer.lastChild.insertBefore(
|
||||
settingsButton,
|
||||
buttonContainer.lastChild.firstChild
|
||||
);
|
||||
|
||||
// Options for the observer (which mutations to observe)
|
||||
const config = { attributes: true, childList: true, subtree: true };
|
||||
|
||||
// Callback function to execute when mutations are observed
|
||||
const callback = (mutationList, observer) => {
|
||||
if (!changeInProgress) {
|
||||
for (const mutation of mutationList) {
|
||||
if (mutation.type === "childList") {
|
||||
changeInProgress = true;
|
||||
let nodes = document.getElementsByClassName("chat-line__message");
|
||||
let users = [];
|
||||
for (let node of nodes) {
|
||||
const n = node.querySelector(".chat-author__display-name");
|
||||
if (!n) continue;
|
||||
const usernameContainer = node.querySelector(
|
||||
".chat-line__username-container"
|
||||
);
|
||||
|
||||
const username = n.attributes["data-a-user"].value;
|
||||
if (username) {
|
||||
let noteButton = usernameContainer.querySelector(".twitchnote");
|
||||
if (!noteButton) {
|
||||
const twitchNote = document.createElement("span");
|
||||
twitchNote.classList.add("twitchnote");
|
||||
twitchNote.style.cursor = "pointer";
|
||||
twitchNote.addEventListener("click", () => {
|
||||
openTwitchNote(username);
|
||||
});
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.src = "https://cdn.rdarius.lt/icons/32-id-card.png";
|
||||
twitchNote.appendChild(img);
|
||||
|
||||
usernameContainer.insertBefore(
|
||||
twitchNote,
|
||||
usernameContainer.firstChild
|
||||
);
|
||||
}
|
||||
if (!users[username]) {
|
||||
users[username] = 0;
|
||||
}
|
||||
users[username]++;
|
||||
}
|
||||
}
|
||||
changeInProgress = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create an observer instance linked to the callback function
|
||||
const observer = new MutationObserver(callback);
|
||||
|
||||
// Start observing the target node for configured mutations
|
||||
observer.observe(targetNode, config);
|
||||
})();
|
||||
Reference in New Issue
Block a user