FIX: loading extension after chat popout and url change
WIP: Transforming to TypeScript
This commit is contained in:
@@ -1,48 +0,0 @@
|
||||
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();
|
||||
@@ -1,7 +1,4 @@
|
||||
{
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"all_frames": true,
|
||||
@@ -42,5 +39,6 @@
|
||||
"*://*.twitch.tv/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"permissions": ["webNavigation", "webRequest"]
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,3 +1,5 @@
|
||||
// noinspection SpellCheckingInspection
|
||||
|
||||
(function twitchNotes() {
|
||||
const head = document.getElementsByTagName("head")[0];
|
||||
if (!head) {
|
||||
@@ -12,13 +14,13 @@
|
||||
head.appendChild(script);
|
||||
}
|
||||
|
||||
if (!document.querySelector("style#twitchNotesStyle")) {
|
||||
const style = document.createElement("link");
|
||||
style.setAttribute("href", chrome.runtime.getURL("twitch-notes.css"));
|
||||
style.setAttribute("type", "text/css");
|
||||
style.setAttribute("rel", "stylesheet");
|
||||
style.setAttribute("crossorigin", "anonymous");
|
||||
style.id = "twitchNotesStyle";
|
||||
head.appendChild(style);
|
||||
}
|
||||
// if (!document.querySelector("style#twitchNotesStyle")) {
|
||||
// const style = document.createElement("link");
|
||||
// style.setAttribute("href", chrome.runtime.getURL("twitch-notes.css"));
|
||||
// style.setAttribute("type", "text/css");
|
||||
// style.setAttribute("rel", "stylesheet");
|
||||
// style.setAttribute("crossorigin", "anonymous");
|
||||
// style.id = "twitchNotesStyle";
|
||||
// head.appendChild(style);
|
||||
// }
|
||||
})();
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<div style="width: 250px">
|
||||
Exporting, Importing and Data clearing will be available in the future
|
||||
</div>
|
||||
691
Twitch Notes/twitch-notes.bak.js
Normal file
691
Twitch Notes/twitch-notes.bak.js
Normal file
@@ -0,0 +1,691 @@
|
||||
(function runTwitchNotes() {
|
||||
function save(filename, data) {
|
||||
const blob = new Blob([data], {type: 'text/json'});
|
||||
const elem = window.document.createElement('a');
|
||||
elem.href = window.URL.createObjectURL(blob);
|
||||
elem.download = filename;
|
||||
document.body.appendChild(elem);
|
||||
elem.click();
|
||||
document.body.removeChild(elem);
|
||||
}
|
||||
|
||||
const Notes = {
|
||||
inputData: {},
|
||||
getSavedUserList: () => {
|
||||
return JSON.parse(localStorage.getItem(LS_UserList) || "[]");
|
||||
},
|
||||
addUserToSavedList: (username) => {
|
||||
let userList = Notes.getSavedUserList();
|
||||
|
||||
if (!userList.includes(username)) {
|
||||
userList.push(username);
|
||||
localStorage.setItem(LS_UserList, JSON.stringify(userList));
|
||||
}
|
||||
},
|
||||
removeUserFromSavedList: (username) => {
|
||||
let userList = Notes.getSavedUserList();
|
||||
userList = userList.filter(u => u !== username);
|
||||
if (userList.length) {
|
||||
localStorage.setItem(LS_UserList, JSON.stringify(userList));
|
||||
} else {
|
||||
localStorage.removeItem(LS_UserList);
|
||||
}
|
||||
},
|
||||
saveNote: (username, note) => {
|
||||
Notes.addUserToSavedList(username);
|
||||
localStorage.setItem(LS_Prefix + username, note);
|
||||
},
|
||||
deleteNote: (username) => {
|
||||
Notes.removeUserFromSavedList(username);
|
||||
localStorage.removeItem(LS_Prefix + username);
|
||||
},
|
||||
getNote: (username) => {
|
||||
return localStorage.getItem(LS_Prefix + username) || "";
|
||||
},
|
||||
exportNotes: () => {
|
||||
save('twitch-notes-' + (Date.now()) + '.json', JSON.stringify({
|
||||
users: Notes.getSavedUserList(),
|
||||
notes: Notes.getSavedUserList().map(user => {
|
||||
return {user: user, note: Notes.getNote(user)};
|
||||
}),
|
||||
settings: {},
|
||||
}));
|
||||
},
|
||||
showAllNotes: () => {
|
||||
let activeNote = null;
|
||||
const blurredBackground = createElement('div', null, 'twitch-notes-blurred-background');
|
||||
const container = createElement('div', null, 'twitch-notes-center-floating-container', {
|
||||
padding: '0',
|
||||
paddingTop: '1rem',
|
||||
width: '100%',
|
||||
height: 'calc(320px + 5rem)'
|
||||
});
|
||||
const closeButton = createElement('button', xButton, 'twitch-notes-settings-close-button', {
|
||||
position: 'absolute',
|
||||
top: '1rem',
|
||||
right: '1rem',
|
||||
});
|
||||
|
||||
const title = createElement('div', 'Notes:', 'twitch-note-title', {
|
||||
borderBottom: '1px solid #FFFFFF19',
|
||||
paddingBottom: '1rem',
|
||||
});
|
||||
container.appendChild(title);
|
||||
|
||||
const content = createElement('div', null, null, {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'no-wrap',
|
||||
width: '100%',
|
||||
height: '320px'
|
||||
});
|
||||
container.appendChild(content);
|
||||
closeButton.addEventListener('click', () => {
|
||||
blurredBackground.remove();
|
||||
});
|
||||
container.appendChild(closeButton);
|
||||
|
||||
const userList = createElement('div', null, null, {
|
||||
borderRight: '1px solid #FFFFFF19',
|
||||
height: '320px',
|
||||
minWidth: '240px',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
});
|
||||
|
||||
const noteContainer = createElement('div', null, null, {
|
||||
height: '320px',
|
||||
padding: '1rem',
|
||||
width: '100%',
|
||||
opacity: '0',
|
||||
});
|
||||
|
||||
const note = createElement('div', null, null, {
|
||||
marginBottom: '3rem',
|
||||
border: '1px solid #FFFFFF19',
|
||||
height: 'calc(320px - 7rem)',
|
||||
width: '100%',
|
||||
padding: '6px',
|
||||
outline: 'none',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
}, {
|
||||
contentEditable: 'true',
|
||||
});
|
||||
const saveNote = createElement('button', 'Save Note', 'twitch-notes-settings-button', {
|
||||
position: 'absolute',
|
||||
bottom: '1rem',
|
||||
left: '1rem'
|
||||
});
|
||||
|
||||
saveNote.addEventListener('click', () => {
|
||||
if(!activeNote) {
|
||||
return;
|
||||
}
|
||||
Notes.saveNote(activeNote, note.innerHTML);
|
||||
})
|
||||
|
||||
for(let user of Notes.getSavedUserList()) {
|
||||
const userLine = createElement('div', user, 'twitch-notes-user-list-user');
|
||||
userLine.addEventListener('click', () => {
|
||||
const list = document.getElementsByClassName('twitch-notes-user-list-user');
|
||||
for(let i = 0; i < list.length; i++) {
|
||||
list.item(i).removeAttribute('data-active');
|
||||
}
|
||||
userLine.setAttribute('data-active', 'true');
|
||||
noteContainer.style.opacity = '1';
|
||||
note.innerHTML = Notes.getNote(user);
|
||||
activeNote = user;
|
||||
});
|
||||
userList.appendChild(userLine);
|
||||
}
|
||||
|
||||
noteContainer.appendChild(note);
|
||||
noteContainer.appendChild(saveNote);
|
||||
|
||||
content.appendChild(userList);
|
||||
content.appendChild(noteContainer);
|
||||
|
||||
blurredBackground.appendChild(container);
|
||||
document.body.appendChild(blurredBackground);
|
||||
},
|
||||
importNotesConflictResolve: (user, resultsContainer, container, blurredBackground) => {
|
||||
const diffBlurredBackground = createElement('div', null, 'twitch-notes-blurred-background');
|
||||
const diffContainer = createElement('div', '<strong>Select which note to keep for <em>' + user + '</em></strong><br /><em>You can modify notes to merge them and keep updated one</em><br /><br />', 'twitch-notes-center-floating-container');
|
||||
|
||||
const inputContainer = createElement('div', null, null, {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'no-wrap',
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
const valueStyle = {
|
||||
minWidth: '320px',
|
||||
padding: '6px',
|
||||
border: '1px solid #FFFFFF19',
|
||||
outline: 'none',
|
||||
};
|
||||
const valueAttr = {contentEditable: 'true'};
|
||||
|
||||
const localValueContainer = createElement('div');
|
||||
const pLocal = createElement('p', '<strong>Locally saved note</strong>');
|
||||
localValueContainer.appendChild(pLocal);
|
||||
const localValue = createElement('div', Notes.getNote(user), null, valueStyle, valueAttr, 'local-value-container');
|
||||
const localValueSave = createElement('button', 'Save this', 'twitch-notes-settings-button');
|
||||
localValueSave.addEventListener('click', () => {
|
||||
const val = document.getElementById('local-value-container').innerHTML;
|
||||
Notes.inputData.notes = Notes.inputData.notes.map(n => n.user === user ? {
|
||||
user: n.user,
|
||||
note: val,
|
||||
resolved: true
|
||||
} : n);
|
||||
diffBlurredBackground.remove();
|
||||
resultsContainer.remove();
|
||||
Notes.importNotesResult(container, blurredBackground);
|
||||
});
|
||||
localValueContainer.appendChild(localValue);
|
||||
localValueContainer.innerHTML += '<br />';
|
||||
localValueContainer.appendChild(localValueSave);
|
||||
|
||||
const importedValueContainer = createElement('div');
|
||||
const iLocal = createElement('p', '<strong>Imported note</strong>');
|
||||
importedValueContainer.appendChild(iLocal);
|
||||
const importedValue = createElement('div', Notes.inputData.notes.filter(n => n.user === user)[0].note || '', null, valueStyle, valueAttr, 'import-value-container');
|
||||
const importedValueSave = createElement('button', 'Save this', 'twitch-notes-settings-button');
|
||||
importedValueSave.addEventListener('click', () => {
|
||||
const val = document.getElementById('import-value-container').innerHTML;
|
||||
Notes.inputData.notes = Notes.inputData.notes.map(n => n.user === user ? {
|
||||
user: n.user,
|
||||
note: val,
|
||||
resolved: true
|
||||
} : n);
|
||||
diffBlurredBackground.remove();
|
||||
resultsContainer.remove();
|
||||
Notes.importNotesResult(container, blurredBackground);
|
||||
});
|
||||
|
||||
importedValueContainer.appendChild(importedValue);
|
||||
importedValueContainer.innerHTML += '<br />';
|
||||
importedValueContainer.appendChild(importedValueSave);
|
||||
|
||||
|
||||
inputContainer.appendChild(localValueContainer);
|
||||
inputContainer.appendChild(importedValueContainer);
|
||||
|
||||
diffContainer.appendChild(inputContainer);
|
||||
diffBlurredBackground.appendChild(diffContainer);
|
||||
document.body.appendChild(diffBlurredBackground);
|
||||
},
|
||||
importNotesResult: (container, blurredBackground) => {
|
||||
|
||||
const willBeAdded = [];
|
||||
let willBeOverwritten = [];
|
||||
const noChanges = [];
|
||||
|
||||
for (let user of Notes.inputData.users) {
|
||||
if (Notes.getNote(user)) {
|
||||
if (Notes.inputData.notes.find(note => note.user === user).resolved || Notes.getNote(user) !== Notes.inputData.notes.find(note => note.user === user).note) {
|
||||
willBeOverwritten.push(user);
|
||||
} else {
|
||||
noChanges.push(user);
|
||||
}
|
||||
} else {
|
||||
willBeAdded.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
const resultsContainer = createElement('div', null);
|
||||
|
||||
if (willBeOverwritten.length) {
|
||||
const overwrittenContainer = createElement('div', '<strong>Note conflicts found for:</strong><br />', null, {
|
||||
marginTop: '1rem',
|
||||
});
|
||||
for (let item of willBeOverwritten) {
|
||||
const row = createElement('div', item + ' ', null, {
|
||||
color: Notes.inputData.notes.find(note => note.user === item).resolved ? 'green' : 'red',
|
||||
});
|
||||
const resolve = createElement('a', Notes.inputData.notes.find(note => note.user === item).resolved ? '[update]' : '[resolve]', null, {
|
||||
cursor: 'pointer',
|
||||
});
|
||||
resolve.addEventListener('click', () => {
|
||||
Notes.importNotesConflictResolve(item, resultsContainer, container, blurredBackground);
|
||||
})
|
||||
row.appendChild(resolve);
|
||||
overwrittenContainer.appendChild(row);
|
||||
}
|
||||
resultsContainer.appendChild(overwrittenContainer);
|
||||
}
|
||||
|
||||
if (willBeAdded.length) {
|
||||
const addedContainer = createElement('div', '<strong>Note will be added for:</strong><br />' + willBeAdded.join('<br />'), null, {
|
||||
marginTop: '1rem',
|
||||
color: 'white'
|
||||
})
|
||||
resultsContainer.appendChild(addedContainer);
|
||||
}
|
||||
|
||||
if (noChanges.length) {
|
||||
const noChangesContainer = createElement('div', '<strong>No changes for:</strong><br />' + noChanges.join('<br />'), null, {
|
||||
marginTop: '1rem',
|
||||
color: 'gray'
|
||||
})
|
||||
resultsContainer.appendChild(noChangesContainer);
|
||||
}
|
||||
|
||||
const spacer = createElement('div', null, null, {height: '24px'});
|
||||
resultsContainer.appendChild(spacer);
|
||||
|
||||
willBeOverwritten = willBeOverwritten.filter(x => !Notes.inputData.notes.find(n => n.user === x).resolved);
|
||||
|
||||
const saveButton = createElement('button', 'Complete import', 'twitch-notes-settings-button',
|
||||
willBeOverwritten.length > 0 ? {background: 'gray'} : null,
|
||||
willBeOverwritten.length > 0 ? {disabled: 'true'} : null)
|
||||
|
||||
resultsContainer.appendChild(saveButton)
|
||||
|
||||
if (willBeOverwritten.length > 0) {
|
||||
const note = createElement('div', '<em>Import cannot be completed while there are unresolved conflicts</em>', null, {
|
||||
color: 'gray',
|
||||
});
|
||||
resultsContainer.appendChild(note);
|
||||
} else {
|
||||
saveButton.addEventListener('click', () => {
|
||||
for (let note of Notes.inputData.notes) {
|
||||
Notes.saveNote(note.user, note.note);
|
||||
}
|
||||
blurredBackground.remove();
|
||||
Notes.inputData = {};
|
||||
});
|
||||
}
|
||||
|
||||
container.appendChild(resultsContainer);
|
||||
},
|
||||
importNotes: () => {
|
||||
const blurredBackground = createElement('div', null, 'twitch-notes-blurred-background');
|
||||
const container = createElement('div', '<strong>WARNING!</strong> <em>This action might override existing data!</em><br /><br />', 'twitch-notes-center-floating-container');
|
||||
const inputBlock = createElement('div', 'Select exported twitch notes file<br /><br />');
|
||||
const input = createElement('input', null, 'twitch-notes-file-input', null, {
|
||||
type: 'file',
|
||||
accept: '.json'
|
||||
});
|
||||
const settingsCloseButton = createElement('button', xButton, 'twitch-notes-settings-close-button', {
|
||||
position: 'absolute',
|
||||
top: '1rem',
|
||||
right: '1rem',
|
||||
});
|
||||
inputBlock.appendChild(input);
|
||||
settingsCloseButton.addEventListener('click', () => {
|
||||
blurredBackground.remove();
|
||||
});
|
||||
input.addEventListener('change', () => {
|
||||
let file = input.files[0];
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
let reader = new FileReader();
|
||||
reader.readAsText(file, "UTF-8");
|
||||
reader.onload = function (evt) {
|
||||
Notes.inputData = JSON.parse(evt.target.result.toString());
|
||||
if (Notes.inputData) {
|
||||
inputBlock.style.display = 'none';
|
||||
Notes.importNotesResult(container, blurredBackground);
|
||||
}
|
||||
}
|
||||
reader.onerror = function () {
|
||||
console.error("error reading file");
|
||||
}
|
||||
});
|
||||
|
||||
container.appendChild(settingsCloseButton);
|
||||
container.appendChild(inputBlock);
|
||||
blurredBackground.appendChild(container);
|
||||
document.body.appendChild(blurredBackground);
|
||||
},
|
||||
clearAllData: () => {
|
||||
const blurredBackground = createElement('div', null, 'twitch-notes-blurred-background');
|
||||
const container = createElement('div', '<strong>This action will delete all saved notes!</strong><br /><br /><em>Consider exporting notes before performing this action in case you will need to use notes again</em>', 'twitch-notes-center-floating-container');
|
||||
|
||||
const spacer = createElement('div', null, null, {
|
||||
height: '24px'
|
||||
});
|
||||
const deleteAction = createElement('button', 'DELETE ALL NOTES', 'twitch-notes-settings-button', {
|
||||
background: 'red'
|
||||
});
|
||||
deleteAction.addEventListener('click', () => {
|
||||
const users = Notes.getSavedUserList();
|
||||
for(let user of users) {
|
||||
Notes.deleteNote(user);
|
||||
}
|
||||
blurredBackground.remove();
|
||||
})
|
||||
const cancelAction = createElement('button', 'Cancel', 'twitch-notes-settings-button');
|
||||
cancelAction.addEventListener('click', () => {
|
||||
blurredBackground.remove();
|
||||
})
|
||||
|
||||
container.appendChild(spacer);
|
||||
container.appendChild(deleteAction);
|
||||
container.appendChild(cancelAction);
|
||||
|
||||
blurredBackground.appendChild(container);
|
||||
document.body.appendChild(blurredBackground);
|
||||
},
|
||||
openAllNotes: () => {
|
||||
Notes.showAllNotes();
|
||||
}
|
||||
}
|
||||
|
||||
const Mouse = {
|
||||
mousePosition: {x: 0, y: 0},
|
||||
lastMousePosition: {x: 0, y: 0},
|
||||
handleMouseMove: (event) => {
|
||||
if (isMouseDown) {
|
||||
if (activeContainer) {
|
||||
const dx = Mouse.mousePosition.x - Mouse.lastMousePosition.x;
|
||||
const dy = Mouse.mousePosition.y - Mouse.lastMousePosition.y;
|
||||
openContainers[activeContainer].style.top =
|
||||
parseInt(openContainers[activeContainer].style.top) + dy + "px";
|
||||
openContainers[activeContainer].style.left =
|
||||
parseInt(openContainers[activeContainer].style.left) + dx + "px";
|
||||
}
|
||||
}
|
||||
Mouse.updateMousePosition(event.clientX, event.clientY);
|
||||
},
|
||||
handleMouseUp: () => {
|
||||
activeContainer = null;
|
||||
isMouseDown = false;
|
||||
},
|
||||
updateMousePosition: (x, y) => {
|
||||
Mouse.lastMousePosition = {...Mouse.mousePosition};
|
||||
Mouse.mousePosition = {x, y};
|
||||
}
|
||||
}
|
||||
|
||||
function createElement(tag, body, classes, styles, attributes, id) {
|
||||
const element = document.createElement(tag);
|
||||
|
||||
if (body) {
|
||||
element.innerHTML = body;
|
||||
}
|
||||
|
||||
if (classes) {
|
||||
if (typeof classes === 'object') {
|
||||
for (let className of classes) {
|
||||
element.classList.add((className));
|
||||
}
|
||||
} else {
|
||||
element.classList.add(classes);
|
||||
}
|
||||
}
|
||||
|
||||
if (styles) {
|
||||
if (typeof styles === 'object') {
|
||||
for (let style in styles) {
|
||||
element.style[style] = styles[style];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attributes) {
|
||||
if (typeof attributes === 'object') {
|
||||
for (let attribute in attributes) {
|
||||
element.setAttribute(attribute, attributes[attribute]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
element.setAttribute('id', id ? id : 'element-' + Math.floor((Math.random() * 10000000)))
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
function createSettingsButton(buttonContainer) {
|
||||
|
||||
const c1 = createElement('div', null, null, {marginLeft: "0.5rem !important"});
|
||||
const c2 = createElement('div', null, null, {display: "inline-flex !important"});
|
||||
const settingsButton = createElement('button', 'Notes', 'twitch-notes-settings-button');
|
||||
|
||||
settingsButton.addEventListener("click", toggleSettings);
|
||||
|
||||
c2.appendChild(settingsButton);
|
||||
c1.appendChild(c2);
|
||||
|
||||
buttonContainer.lastChild.insertBefore(
|
||||
c1,
|
||||
buttonContainer.lastChild.lastChild
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
const container = createElement('div', null, 'twitch-note-container', {
|
||||
left: Mouse.mousePosition.x + 'px',
|
||||
top: Mouse.mousePosition.y + 10 + 'px'
|
||||
});
|
||||
|
||||
const header = createElement('div', null, 'twitch-note-header');
|
||||
header.addEventListener("mousedown", function close() {
|
||||
activeContainer = username;
|
||||
isMouseDown = true;
|
||||
});
|
||||
|
||||
const closeButton = createElement('span', xButton, 'twitch-note-close-button')
|
||||
closeButton.addEventListener("click", function close() {
|
||||
removeContainer(username);
|
||||
});
|
||||
|
||||
const title = createElement('span', username, 'twitch-note-title');
|
||||
const content = createElement('div', Notes.getNote(username), 'twitch-note-content', [], {
|
||||
contentEditable: 'true'
|
||||
});
|
||||
|
||||
const saveButton = createElement('div', 'SAVE', 'twitch-note-save-button')
|
||||
saveButton.addEventListener("click", function () {
|
||||
Notes.saveNote(username, content.innerHTML);
|
||||
removeContainer(username);
|
||||
});
|
||||
|
||||
header.appendChild(closeButton);
|
||||
header.appendChild(title);
|
||||
container.appendChild(header);
|
||||
container.appendChild(content);
|
||||
container.appendChild(saveButton);
|
||||
|
||||
addContainer(username, container);
|
||||
}
|
||||
|
||||
function addNoteBadges() {
|
||||
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(".twitch-note");
|
||||
if (!noteButton) {
|
||||
|
||||
const twitchNote = createElement('span', null, 'twitch-note', {
|
||||
cursor: 'pointer',
|
||||
})
|
||||
twitchNote.addEventListener("click", () => {
|
||||
openTwitchNote(username);
|
||||
});
|
||||
|
||||
const img = createElement('img', null, null, {
|
||||
height: '18px',
|
||||
paddingRight: '4px'
|
||||
}, {
|
||||
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]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createChatSettingsLine(text) {
|
||||
const element = createElement('div', null, 'twitch-notes-settings-option-line');
|
||||
const button = createElement('button', null, 'twitch-notes-settings-option-line-button');
|
||||
const buttonContainer = createElement('div', null, 'twitch-notes-settings-option-line-button-container');
|
||||
const buttonContainerContent = createElement('div', text, 'twitch-notes-settings-option-line-button-container-content');
|
||||
buttonContainer.appendChild(buttonContainerContent);
|
||||
button.appendChild(buttonContainer);
|
||||
element.appendChild(button);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
function createChatSettingsSeparator() {
|
||||
return createElement('div', null, 'twitch-note-settings-separator');
|
||||
}
|
||||
|
||||
function toggleSettings() {
|
||||
if (!settingsWindow) {
|
||||
const settingsContainer = createElement('div', null, 'twitch-notes-settings-container');
|
||||
const settingsBalloon = createElement('div', null, 'twitch-notes-settings-balloon');
|
||||
const settingsPopover = createElement('div', null, 'twitch-notes-settings-popover');
|
||||
|
||||
// startOf: settings header
|
||||
const settingsHeader = createElement('div', null, 'twitch-notes-settings-header');
|
||||
const settingsHeaderLeftElement = createElement('div', null, 'twitch-notes-settings-header-left-element')
|
||||
settingsHeader.appendChild(settingsHeaderLeftElement);
|
||||
const settingsHeaderCenterElement = createElement('div', null, 'twitch-notes-settings-header-center-element')
|
||||
settingsHeader.appendChild(settingsHeaderCenterElement);
|
||||
const settingsHeaderCenterElementContent = createElement('p', 'Twitch Notes Settings', 'twitch-notes-settings-header-center-element-content')
|
||||
settingsHeaderCenterElement.appendChild(settingsHeaderCenterElementContent);
|
||||
const settingsHeaderRightElement = createElement('div', null, 'twitch-notes-settings-header-right-element')
|
||||
settingsHeader.appendChild(settingsHeaderRightElement);
|
||||
const settingsCloseButton = createElement('button', xButton, 'twitch-notes-settings-close-button');
|
||||
settingsHeaderRightElement.appendChild(settingsCloseButton);
|
||||
settingsPopover.appendChild(settingsHeader);
|
||||
// endOf: settings header
|
||||
|
||||
settingsCloseButton.addEventListener('click', toggleSettings);
|
||||
|
||||
|
||||
// startOf: scrollable area
|
||||
const settingsScrollableArea = createElement('div', null, 'twitch-notes-settings-scrollable-area');
|
||||
const settingsContent = createElement('div', null, 'twitch-notes-settings-content');
|
||||
settingsScrollableArea.appendChild(settingsContent);
|
||||
|
||||
const exportNotes = createChatSettingsLine('Export Notes');
|
||||
settingsContent.appendChild(exportNotes);
|
||||
const importNotes = createChatSettingsLine('Import Notes');
|
||||
settingsContent.appendChild(importNotes);
|
||||
const clearData = createChatSettingsLine('Clear Data');
|
||||
settingsContent.appendChild(clearData);
|
||||
settingsContent.appendChild(createChatSettingsSeparator());
|
||||
const openAllNotes = createChatSettingsLine('View All Notes');
|
||||
settingsContent.appendChild(openAllNotes);
|
||||
|
||||
exportNotes.addEventListener('click', () => {Notes.exportNotes(); toggleSettings()});
|
||||
importNotes.addEventListener('click', () => {Notes.importNotes(); toggleSettings()});
|
||||
clearData.addEventListener('click', () => {Notes.clearAllData(); toggleSettings()});
|
||||
openAllNotes.addEventListener('click', () => {Notes.openAllNotes(); toggleSettings()});
|
||||
|
||||
|
||||
settingsPopover.appendChild(settingsScrollableArea);
|
||||
// endOf: scrollable area
|
||||
|
||||
|
||||
settingsBalloon.appendChild(settingsPopover);
|
||||
settingsContainer.appendChild(settingsBalloon);
|
||||
settingsWindow = settingsContainer;
|
||||
document.body.appendChild(settingsWindow);
|
||||
settingsWindowOpen = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (settingsWindowOpen) {
|
||||
settingsWindow.style.display = 'none';
|
||||
settingsWindowOpen = false;
|
||||
} else {
|
||||
settingsWindow.style.display = 'block';
|
||||
settingsWindowOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
const xButton = `<svg width="100%" height="100%" viewBox="0 0 20 20" x="0px" y="0px" class="twitch-notes-x-button"><g><path d="M8.5 10L4 5.5 5.5 4 10 8.5 14.5 4 16 5.5 11.5 10l4.5 4.5-1.5 1.5-4.5-4.5L5.5 16 4 14.5 8.5 10z"></path></g></svg>`;
|
||||
let openContainers = {};
|
||||
let isMouseDown = false;
|
||||
let activeContainer = null;
|
||||
let settingsWindowOpen = false;
|
||||
let settingsWindow = null;
|
||||
|
||||
let LS_UserList = "twitch-note-all-users-list";
|
||||
let LS_Prefix = "twitch-note-";
|
||||
|
||||
document.addEventListener("mousemove", Mouse.handleMouseMove);
|
||||
document.addEventListener("mouseup", Mouse.handleMouseUp);
|
||||
|
||||
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());
|
||||
|
||||
createSettingsButton(buttonContainer);
|
||||
|
||||
// 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) => {
|
||||
if (!changeInProgress) {
|
||||
for (const mutation of mutationList) {
|
||||
if (mutation.type === "childList") {
|
||||
changeInProgress = true;
|
||||
addNoteBadges();
|
||||
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);
|
||||
})();
|
||||
@@ -1,297 +0,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-ratio: 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;
|
||||
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;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-button {
|
||||
z-index: var(--z-index-default) !important;
|
||||
position: relative !important;
|
||||
background-color: var(--color-background-button-primary-default);
|
||||
color: var(--color-text-button-primary);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
-webkit-box-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
justify-content: center;
|
||||
vertical-align: middle;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
border-radius: var(--border-radius-medium);
|
||||
font-size: var(--button-text-default);
|
||||
height: var(--button-size-default);
|
||||
padding: 0 var(--button-padding-x);
|
||||
}
|
||||
|
||||
.twitch-notes-settings-container {
|
||||
border: 0;
|
||||
font: inherit;
|
||||
padding: 0;
|
||||
vertical-align: baseline;
|
||||
position: fixed;
|
||||
z-index: var(--z-index-balloon);
|
||||
inset: auto 0 0 auto;
|
||||
margin: 0 69px 48px 0;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-balloon {
|
||||
display: inline-block;
|
||||
max-width: 90vw;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
border-radius: 0.6rem !important;
|
||||
background-color: var(--color-background-base) !important;
|
||||
box-shadow: var(--shadow-elevation-2) !important;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-popover {
|
||||
white-space: normal;
|
||||
width: 32rem;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-header {
|
||||
display: flex !important;
|
||||
-webkit-box-align: center !important;
|
||||
align-items: center !important;
|
||||
position: relative !important;
|
||||
padding-left: 1rem !important;
|
||||
padding-right: 1rem !important;
|
||||
background-color: var(--color-background-base) !important;
|
||||
min-height: 4rem;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-header-left-element {
|
||||
left: 0 !important;
|
||||
margin-right: 0.5rem !important;
|
||||
width: 3rem;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-header-center-element {
|
||||
-webkit-box-align: center !important;
|
||||
align-items: center !important;
|
||||
display: flex !important;
|
||||
text-align: center !important;
|
||||
-webkit-box-flex: 1 !important;
|
||||
flex-grow: 1 !important;
|
||||
-webkit-box-pack: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-header-center-element-content {
|
||||
line-height: 1.5;
|
||||
color: var(--color-text-alt) !important;
|
||||
font-size: var(--font-size-5) !important;
|
||||
font-weight: var(--font-weight-semibold) !important;
|
||||
}
|
||||
|
||||
|
||||
.twitch-notes-settings-header-right-element {
|
||||
right: 0 !important;
|
||||
margin-left: 0.5rem !important;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-close-button {
|
||||
cursor: default;
|
||||
text-transform: none;
|
||||
text-indent: 0;
|
||||
text-shadow: none;
|
||||
letter-spacing: normal;
|
||||
text-rendering: auto;
|
||||
appearance: auto;
|
||||
writing-mode: horizontal-tb !important;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
border: none;
|
||||
font-size: var(--button-text-default);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
vertical-align: middle;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
-webkit-box-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
border-radius: var(--border-radius-medium);
|
||||
height: calc(var(--button-size-default) - 1rem);
|
||||
width: calc(var(--button-size-default) - 1rem);
|
||||
background-color: var(--color-background-button-text-default);
|
||||
color: var(--color-fill-button-icon);
|
||||
}
|
||||
|
||||
.twitch-notes-settings-scrollable-area {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
max-height: 478px;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-content {
|
||||
box-sizing: content-box;
|
||||
min-width: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
max-height: inherit!important;
|
||||
margin: 1rem;
|
||||
padding-bottom: 0!important;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-option-line {
|
||||
position: relative !important;
|
||||
width: calc(100% - 2rem)!important;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-option-line-button {
|
||||
border-radius: var(--border-radius-medium);
|
||||
display: block;
|
||||
width: 100%;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-option-line-button:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
background-color: var(--color-background-interactable-hover);
|
||||
}
|
||||
|
||||
.twitch-notes-settings-option-line-button-container {
|
||||
display: flex !important;
|
||||
-webkit-box-align: center !important;
|
||||
align-items: center !important;
|
||||
position: relative !important;
|
||||
padding: 0.5rem !important;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-option-line-button-container-content {
|
||||
-webkit-box-flex: 1 !important;
|
||||
flex-grow: 1 !important;
|
||||
}
|
||||
|
||||
.twitch-note-settings-separator {
|
||||
border-top: 1px solid var(--color-border-base);
|
||||
margin-top: 1rem !important;
|
||||
margin-left: 0.5rem !important;
|
||||
margin-right: 0.5rem !important;
|
||||
padding-bottom: 1rem !important;
|
||||
}
|
||||
|
||||
.twitch-notes-x-button {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
top: 0;
|
||||
fill: currentcolor;
|
||||
}
|
||||
|
||||
.twitch-notes-blurred-background {
|
||||
position: fixed;
|
||||
inset: 0 0 0 0;
|
||||
z-index: 2000000;
|
||||
background: rgba(0,0,0,0.4);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.twitch-notes-center-floating-container {
|
||||
background: var(--color-background-base);
|
||||
border: var(--border-width-default) solid var(--color-border-base) !important;
|
||||
padding: 4rem 2rem 2rem;
|
||||
position: fixed;
|
||||
top: 50vh;
|
||||
left: 50vw;
|
||||
transform: translate(-50%, -50%);
|
||||
max-height: 90vh;
|
||||
max-width: 90vw;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.twitch-notes-user-list-user {
|
||||
padding: 1rem;
|
||||
border-bottom: var(--border-width-default) solid var(--color-border-base) !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.twitch-notes-user-list-user[data-active] {
|
||||
background: #772ce833;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
1
dist/twitch-notes.js
vendored
Normal file
1
dist/twitch-notes.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7063
package-lock.json
generated
Normal file
7063
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
package.json
Normal file
32
package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "twitchnotes",
|
||||
"version": "0.0.1",
|
||||
"description": "Add notes to twitch users (only visible to you)",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "webpack && cp ./dist/twitch-notes.js './Twitch Notes/twitch-notes.js'"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rdarius/TwitchNotes.git"
|
||||
},
|
||||
"author": "Darius Rapalis",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/rdarius/TwitchNotes/issues"
|
||||
},
|
||||
"homepage": "https://github.com/rdarius/TwitchNotes#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19.0",
|
||||
"@babel/preset-env": "^7.19.0",
|
||||
"babel-loader": "^8.2.5",
|
||||
"css-loader": "^6.7.1",
|
||||
"sass": "^1.54.9",
|
||||
"sass-loader": "^13.0.2",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-loader": "^9.3.1",
|
||||
"typescript": "^4.8.3",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
}
|
||||
}
|
||||
49
src/TwitchNote.ts
Normal file
49
src/TwitchNote.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
export default class TwitchNote {
|
||||
|
||||
observer: MutationObserver | null = null;
|
||||
chatContainerNode?: Element;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
init(chatContainerNode: Element) {
|
||||
this.chatContainerNode = chatContainerNode;
|
||||
console.log('CHAT CONTAINER INITIATED');
|
||||
this.startObserver(chatContainerNode);
|
||||
// add Notes management button
|
||||
this.addNotesButton();
|
||||
}
|
||||
|
||||
startObserver(targetNode: Element) {
|
||||
// discard old observer
|
||||
if (this.observer) {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
|
||||
const observerCallback = (mutationList: MutationRecord[]) => {
|
||||
// preventing multiple chat updates at once in case update process
|
||||
// is taking longer or chat is super active
|
||||
if (!changeInProgress) {
|
||||
for (const mutation of mutationList) {
|
||||
if (mutation.type === "childList") {
|
||||
changeInProgress = true;
|
||||
// add badges next to users
|
||||
this.addUserNoteBadges();
|
||||
changeInProgress = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let changeInProgress = false;
|
||||
const observerConfig = {attributes: true, childList: true, subtree: true};
|
||||
this.observer = new MutationObserver(observerCallback);
|
||||
this.observer.observe(targetNode, observerConfig);
|
||||
}
|
||||
|
||||
addNotesButton() {
|
||||
}
|
||||
|
||||
addUserNoteBadges() {
|
||||
}
|
||||
}
|
||||
35
src/index.ts
Normal file
35
src/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import './styles/main.scss';
|
||||
import TwitchNote from "./TwitchNote";
|
||||
|
||||
const ChatContainerClass = 'chat-scrollable-area__message-container';
|
||||
const twitchNote = new TwitchNote();
|
||||
let chatExists = false;
|
||||
let lastUrl = '';
|
||||
|
||||
function init() {
|
||||
|
||||
// check for chat container existence
|
||||
setInterval(() => {
|
||||
// check for URL changes (going through different channels)
|
||||
const currentUrl = window.location.href;
|
||||
if (lastUrl !== currentUrl) {
|
||||
lastUrl = currentUrl;
|
||||
chatExists = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// check for chat container appearing in DOM
|
||||
const targetNode = document.querySelector(`.${ChatContainerClass}`);
|
||||
if (targetNode) {
|
||||
if (!chatExists) {
|
||||
// chat container appeared
|
||||
twitchNote.init(targetNode);
|
||||
}
|
||||
chatExists = true;
|
||||
} else {
|
||||
chatExists = false;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
init();
|
||||
297
src/styles/main.scss
Normal file
297
src/styles/main.scss
Normal file
@@ -0,0 +1,297 @@
|
||||
.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-ratio: 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;
|
||||
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;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-button {
|
||||
z-index: var(--z-index-default) !important;
|
||||
position: relative !important;
|
||||
background-color: var(--color-background-button-primary-default);
|
||||
color: var(--color-text-button-primary);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
-webkit-box-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
justify-content: center;
|
||||
vertical-align: middle;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
border-radius: var(--border-radius-medium);
|
||||
font-size: var(--button-text-default);
|
||||
height: var(--button-size-default);
|
||||
padding: 0 var(--button-padding-x);
|
||||
}
|
||||
|
||||
.twitch-notes-settings-container {
|
||||
border: 0;
|
||||
font: inherit;
|
||||
padding: 0;
|
||||
vertical-align: baseline;
|
||||
position: fixed;
|
||||
z-index: var(--z-index-balloon);
|
||||
inset: auto 0 0 auto;
|
||||
margin: 0 69px 48px 0;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-balloon {
|
||||
display: inline-block;
|
||||
max-width: 90vw;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
border-radius: 0.6rem !important;
|
||||
background-color: var(--color-background-base) !important;
|
||||
box-shadow: var(--shadow-elevation-2) !important;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-popover {
|
||||
white-space: normal;
|
||||
width: 32rem;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-header {
|
||||
display: flex !important;
|
||||
-webkit-box-align: center !important;
|
||||
align-items: center !important;
|
||||
position: relative !important;
|
||||
padding-left: 1rem !important;
|
||||
padding-right: 1rem !important;
|
||||
background-color: var(--color-background-base) !important;
|
||||
min-height: 4rem;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-header-left-element {
|
||||
left: 0 !important;
|
||||
margin-right: 0.5rem !important;
|
||||
width: 3rem;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-header-center-element {
|
||||
-webkit-box-align: center !important;
|
||||
align-items: center !important;
|
||||
display: flex !important;
|
||||
text-align: center !important;
|
||||
-webkit-box-flex: 1 !important;
|
||||
flex-grow: 1 !important;
|
||||
-webkit-box-pack: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-header-center-element-content {
|
||||
line-height: 1.5;
|
||||
color: var(--color-text-alt) !important;
|
||||
font-size: var(--font-size-5) !important;
|
||||
font-weight: var(--font-weight-semibold) !important;
|
||||
}
|
||||
|
||||
|
||||
.twitch-notes-settings-header-right-element {
|
||||
right: 0 !important;
|
||||
margin-left: 0.5rem !important;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-close-button {
|
||||
cursor: default;
|
||||
text-transform: none;
|
||||
text-indent: 0;
|
||||
text-shadow: none;
|
||||
letter-spacing: normal;
|
||||
text-rendering: auto;
|
||||
appearance: auto;
|
||||
writing-mode: horizontal-tb !important;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
border: none;
|
||||
font-size: var(--button-text-default);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
vertical-align: middle;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
-webkit-box-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
border-radius: var(--border-radius-medium);
|
||||
height: calc(var(--button-size-default) - 1rem);
|
||||
width: calc(var(--button-size-default) - 1rem);
|
||||
background-color: var(--color-background-button-text-default);
|
||||
color: var(--color-fill-button-icon);
|
||||
}
|
||||
|
||||
.twitch-notes-settings-scrollable-area {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
max-height: 478px;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-content {
|
||||
box-sizing: content-box;
|
||||
min-width: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
max-height: inherit !important;
|
||||
margin: 1rem;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-option-line {
|
||||
position: relative !important;
|
||||
width: calc(100% - 2rem) !important;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-option-line-button {
|
||||
border-radius: var(--border-radius-medium);
|
||||
display: block;
|
||||
width: 100%;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-option-line-button:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
background-color: var(--color-background-interactable-hover);
|
||||
}
|
||||
|
||||
.twitch-notes-settings-option-line-button-container {
|
||||
display: flex !important;
|
||||
-webkit-box-align: center !important;
|
||||
align-items: center !important;
|
||||
position: relative !important;
|
||||
padding: 0.5rem !important;
|
||||
}
|
||||
|
||||
.twitch-notes-settings-option-line-button-container-content {
|
||||
-webkit-box-flex: 1 !important;
|
||||
flex-grow: 1 !important;
|
||||
}
|
||||
|
||||
.twitch-note-settings-separator {
|
||||
border-top: 1px solid var(--color-border-base);
|
||||
margin-top: 1rem !important;
|
||||
margin-left: 0.5rem !important;
|
||||
margin-right: 0.5rem !important;
|
||||
padding-bottom: 1rem !important;
|
||||
}
|
||||
|
||||
.twitch-notes-x-button {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
top: 0;
|
||||
fill: currentcolor;
|
||||
}
|
||||
|
||||
.twitch-notes-blurred-background {
|
||||
position: fixed;
|
||||
inset: 0 0 0 0;
|
||||
z-index: 2000000;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.twitch-notes-center-floating-container {
|
||||
background: var(--color-background-base);
|
||||
border: var(--border-width-default) solid var(--color-border-base) !important;
|
||||
padding: 4rem 2rem 2rem;
|
||||
position: fixed;
|
||||
top: 50vh;
|
||||
left: 50vw;
|
||||
transform: translate(-50%, -50%);
|
||||
max-height: 90vh;
|
||||
max-width: 90vw;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.twitch-notes-user-list-user {
|
||||
padding: 1rem;
|
||||
border-bottom: var(--border-width-default) solid var(--color-border-base) !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.twitch-notes-user-list-user[data-active] {
|
||||
background: #772ce833;
|
||||
}
|
||||
103
tsconfig.json
Normal file
103
tsconfig.json
Normal file
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "ES5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "ES6", /* Specify what module code is generated. */
|
||||
// "rootDir": "./src", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist/", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
46
webpack.config.js
Normal file
46
webpack.config.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// noinspection SpellCheckingInspection
|
||||
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: {
|
||||
twitchNotes: path.resolve(__dirname, 'src/index.ts'),
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: "twitch-notes.js",
|
||||
clean: true,
|
||||
assetModuleFilename: "[name][ext]"
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader'
|
||||
},
|
||||
// {
|
||||
// loader: 'babel-loader',
|
||||
// options: {
|
||||
// presets: ['@babel/preset-env']
|
||||
// }
|
||||
// }
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
'css-loader',
|
||||
'sass-loader',
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js'],
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user