diff --git a/extension/twitch-notes.bak.js b/extension/twitch-notes.bak.js deleted file mode 100644 index 38563b7..0000000 --- a/extension/twitch-notes.bak.js +++ /dev/null @@ -1,691 +0,0 @@ -(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', 'Select which note to keep for ' + user + '
You can modify notes to merge them and keep updated one

', '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', 'Locally saved note'); - 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 += '
'; - localValueContainer.appendChild(localValueSave); - - const importedValueContainer = createElement('div'); - const iLocal = createElement('p', 'Imported note'); - 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 += '
'; - 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', 'Note conflicts found for:
', 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', 'Note will be added for:
' + willBeAdded.join('
'), null, { - marginTop: '1rem', - color: 'white' - }) - resultsContainer.appendChild(addedContainer); - } - - if (noChanges.length) { - const noChangesContainer = createElement('div', 'No changes for:
' + noChanges.join('
'), 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', 'Import cannot be completed while there are unresolved conflicts', 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', 'WARNING! This action might override existing data!

', 'twitch-notes-center-floating-container'); - const inputBlock = createElement('div', 'Select exported twitch notes file

'); - 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', 'This action will delete all saved notes!

Consider exporting notes before performing this action in case you will need to use notes again', '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 = ``; - 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); -})(); diff --git a/extension/twitch-notes.js b/extension/twitch-notes.js index fed0ad6..c4e9b9e 100644 --- a/extension/twitch-notes.js +++ b/extension/twitch-notes.js @@ -1 +1 @@ -(()=>{"use strict";var t={144:(t,e,o)=>{o.d(e,{Z:()=>s});var r=o(81),n=o.n(r),i=o(645),a=o.n(i)()(n());a.push([t.id,".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:rgba(0,0,0,0);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:.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:.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:.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:.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:.5rem !important;margin-right:.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,.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:rgba(119,44,232,.2)}",""]);const s=a},645:t=>{t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var o="",r=void 0!==e[5];return e[4]&&(o+="@supports (".concat(e[4],") {")),e[2]&&(o+="@media ".concat(e[2]," {")),r&&(o+="@layer".concat(e[5].length>0?" ".concat(e[5]):""," {")),o+=t(e),r&&(o+="}"),e[2]&&(o+="}"),e[4]&&(o+="}"),o})).join("")},e.i=function(t,o,r,n,i){"string"==typeof t&&(t=[[null,t,void 0]]);var a={};if(r)for(var s=0;s0?" ".concat(d[5]):""," {").concat(d[1],"}")),d[5]=i),o&&(d[2]?(d[1]="@media ".concat(d[2]," {").concat(d[1],"}"),d[2]=o):d[2]=o),n&&(d[4]?(d[1]="@supports (".concat(d[4],") {").concat(d[1],"}"),d[4]=n):d[4]="".concat(n)),e.push(d))}},e}},81:t=>{t.exports=function(t){return t[1]}},379:t=>{var e=[];function o(t){for(var o=-1,r=0;r{var e={};t.exports=function(t,o){var r=function(t){if(void 0===e[t]){var o=document.querySelector(t);if(window.HTMLIFrameElement&&o instanceof window.HTMLIFrameElement)try{o=o.contentDocument.head}catch(t){o=null}e[t]=o}return e[t]}(t);if(!r)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");r.appendChild(o)}},216:t=>{t.exports=function(t){var e=document.createElement("style");return t.setAttributes(e,t.attributes),t.insert(e,t.options),e}},565:(t,e,o)=>{t.exports=function(t){var e=o.nc;e&&t.setAttribute("nonce",e)}},795:t=>{t.exports=function(t){var e=t.insertStyleElement(t);return{update:function(o){!function(t,e,o){var r="";o.supports&&(r+="@supports (".concat(o.supports,") {")),o.media&&(r+="@media ".concat(o.media," {"));var n=void 0!==o.layer;n&&(r+="@layer".concat(o.layer.length>0?" ".concat(o.layer):""," {")),r+=o.css,n&&(r+="}"),o.media&&(r+="}"),o.supports&&(r+="}");var i=o.sourceMap;i&&"undefined"!=typeof btoa&&(r+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(i))))," */")),e.styleTagTransform(r,t,e.options)}(e,t,o)},remove:function(){!function(t){if(null===t.parentNode)return!1;t.parentNode.removeChild(t)}(e)}}}},589:t=>{t.exports=function(t,e){if(e.styleSheet)e.styleSheet.cssText=t;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(t))}}}},e={};function o(r){var n=e[r];if(void 0!==n)return n.exports;var i=e[r]={id:r,exports:{}};return t[r](i,i.exports,o),i.exports}o.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return o.d(e,{a:e}),e},o.d=(t,e)=>{for(var r in e)o.o(e,r)&&!o.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},o.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),o.nc=void 0,(()=>{var t=o(379),e=o.n(t),r=o(795),n=o.n(r),i=o(569),a=o.n(i),s=o(565),c=o.n(s),l=o(216),d=o.n(l),p=o(589),u=o.n(p),m=o(144),h={};h.styleTagTransform=u(),h.setAttributes=c(),h.insert=a().bind(null,"head"),h.domAPI=n(),h.insertStyleElement=d(),e()(m.Z,h),m.Z&&m.Z.locals&&m.Z.locals;var b=new(function(){function t(){this.observer=null}return t.prototype.init=function(t){this.chatContainerNode=t,console.log("CHAT CONTAINER INITIATED"),this.startObserver(t),this.addNotesButton()},t.prototype.startObserver=function(t){var e=this;this.observer&&this.observer.disconnect();var o=!1;this.observer=new MutationObserver((function(t){if(!o)for(var r=0,n=t;r{"use strict";var t={144:(t,e,n)=>{n.d(e,{Z:()=>a});var o=n(81),i=n.n(o),r=n(645),s=n.n(r)()(i());s.push([t.id,".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:rgba(0,0,0,0);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:.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:.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:.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:.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:.5rem !important;margin-right:.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,.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:rgba(119,44,232,.2)}",""]);const a=s},645:t=>{t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var n="",o=void 0!==e[5];return e[4]&&(n+="@supports (".concat(e[4],") {")),e[2]&&(n+="@media ".concat(e[2]," {")),o&&(n+="@layer".concat(e[5].length>0?" ".concat(e[5]):""," {")),n+=t(e),o&&(n+="}"),e[2]&&(n+="}"),e[4]&&(n+="}"),n})).join("")},e.i=function(t,n,o,i,r){"string"==typeof t&&(t=[[null,t,void 0]]);var s={};if(o)for(var a=0;a0?" ".concat(c[5]):""," {").concat(c[1],"}")),c[5]=r),n&&(c[2]?(c[1]="@media ".concat(c[2]," {").concat(c[1],"}"),c[2]=n):c[2]=n),i&&(c[4]?(c[1]="@supports (".concat(c[4],") {").concat(c[1],"}"),c[4]=i):c[4]="".concat(i)),e.push(c))}},e}},81:t=>{t.exports=function(t){return t[1]}},379:t=>{var e=[];function n(t){for(var n=-1,o=0;o{var e={};t.exports=function(t,n){var o=function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(t){n=null}e[t]=n}return e[t]}(t);if(!o)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");o.appendChild(n)}},216:t=>{t.exports=function(t){var e=document.createElement("style");return t.setAttributes(e,t.attributes),t.insert(e,t.options),e}},565:(t,e,n)=>{t.exports=function(t){var e=n.nc;e&&t.setAttribute("nonce",e)}},795:t=>{t.exports=function(t){var e=t.insertStyleElement(t);return{update:function(n){!function(t,e,n){var o="";n.supports&&(o+="@supports (".concat(n.supports,") {")),n.media&&(o+="@media ".concat(n.media," {"));var i=void 0!==n.layer;i&&(o+="@layer".concat(n.layer.length>0?" ".concat(n.layer):""," {")),o+=n.css,i&&(o+="}"),n.media&&(o+="}"),n.supports&&(o+="}");var r=n.sourceMap;r&&"undefined"!=typeof btoa&&(o+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(r))))," */")),e.styleTagTransform(o,t,e.options)}(e,t,n)},remove:function(){!function(t){if(null===t.parentNode)return!1;t.parentNode.removeChild(t)}(e)}}}},589:t=>{t.exports=function(t,e){if(e.styleSheet)e.styleSheet.cssText=t;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(t))}}}},e={};function n(o){var i=e[o];if(void 0!==i)return i.exports;var r=e[o]={id:o,exports:{}};return t[o](r,r.exports,n),r.exports}n.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var o in e)n.o(e,o)&&!n.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),n.nc=void 0,(()=>{var t=n(379),e=n.n(t),o=n(795),i=n.n(o),r=n(569),s=n.n(r),a=n(565),l=n.n(a),d=n(216),c=n.n(d),u=n(589),p=n.n(u),h=n(144),m={};m.styleTagTransform=p(),m.setAttributes=l(),m.insert=s().bind(null,"head"),m.domAPI=i(),m.insertStyleElement=c(),e()(h.Z,m),h.Z&&h.Z.locals&&h.Z.locals;var f,v=(f=function(t,e){return f=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},f(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}f(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}),g=function(){function t(t,e){if(this.element=document.createElement(t),e.id?this.setId(e.id):this.setId(t+"-generated-id-"+Math.floor(1e7*Math.random())),e.class)if("string"==typeof e.class)this.addClass(e.class);else for(var n=0,o=e.class;n\n \n \n \n '),c=new w("twitch-notes-settings-scrollable-area"),u=new w("twitch-notes-settings-content"),p=B("Export Notes"),h=B("Import Notes"),m=B("Clear Data"),f=B("View All Notes");return e.appendCustomChild(n),n.appendCustomChild(o),o.appendCustomChild(i),i.appendCustomChild(r),i.appendCustomChild(s),s.appendCustomChild(a),i.appendCustomChild(l),l.appendCustomChild(d),o.appendCustomChild(c),c.appendCustomChild(u),u.appendCustomChild(p),u.appendCustomChild(h),u.appendCustomChild(m),u.appendCustomChild(new w("twitch-note-settings-separator")),u.appendCustomChild(f),d.setClickListener((function(){return z()})),p.setClickListener((function(){N.exportNotes(),z()})),h.setClickListener((function(){!function(){var t=new w("twitch-notes-blurred-background"),e=new w("twitch-notes-center-floating-container","WARNING! This action might override existing data!

"),n=new w("","Select exported twitch notes file

"),o=new b("twitch-notes-file-input");o.addAttribute("type","file").addAttribute("accept","json");var i=new y("twitch-notes-settings-close-button",'\n \n \n \n ');i.setStyle({position:"absolute",top:"1rem",right:"1rem"}),n.appendCustomChild(o),i.setClickListener((function(){t.remove()})),o.setChangeListener((function(){var i;if(null===(i=null==o?void 0:o.getElement())||void 0===i?void 0:i.files){var r=o.getElement().files.item(0);if(r){var s=new FileReader;s.readAsText(r,"UTF-8"),s.onload=function(o){var i;(null===(i=null==o?void 0:o.target)||void 0===i?void 0:i.result)&&(N.inputData=JSON.parse(o.target.result.toString()),N.inputData&&(n.setStyle({display:"none"}),D(e,t)))},s.onerror=function(){console.error("error reading file")}}}})),e.appendCustomChild(i),e.appendCustomChild(n),t.appendCustomChild(e),document.body.appendChild(t.getElement())}(),z()})),m.setClickListener((function(){!function(){var t=new w("twitch-notes-blurred-background"),e=new w("twitch-notes-center-floating-container","This action will delete all saved notes!

Consider exporting notes before performing this action in case you will need to use notes again"),n=new w;n.setStyle({height:"24px"});var o=new y("twitch-notes-settings-button","DELETE ALL NOTES");o.setStyle({background:"red"}),o.setClickListener((function(){N.clearData(),t.remove()}));var i=new y("twitch-notes-settings-button","Cancel");i.setClickListener((function(){t.remove()})),e.appendCustomChild(n),e.appendCustomChild(o),e.appendCustomChild(i),t.appendCustomChild(e),document.body.appendChild(t.getElement())}(),z()})),f.setClickListener((function(){!function(){var t="",e=new w("twitch-notes-blurred-background"),n=new S("","Note saved!");n.setStyle({color:"green",marginLeft:"2rem"}),n.hide();var o=new w("twitch-notes-center-floating-container");o.setStyle({padding:"0",paddingTop:"1rem",width:"100%",maxWidth:"800px",height:"calc(320px + 5rem)",maxHeight:"90vh"});var i=new y("twitch-notes-settings-close-button",'\n \n \n \n ');i.setStyle({position:"absolute",top:"1rem",right:"1rem"});var r=new w("twitch-note-title","Notes:");r.setStyle({borderBottom:"1px solid #FFFFFF19",paddingBottom:"1rem"}),o.appendCustomChild(r);var s=new w;s.setStyle({display:"flex",flexDirection:"row",flexWrap:"no-wrap",width:"100%",height:"320px"}),o.appendCustomChild(s),i.setClickListener((function(){e.remove()})),o.appendCustomChild(i);var a=new w;a.setStyle({borderRight:"1px solid #FFFFFF19",height:"320px",minWidth:"240px",overflowY:"auto",overflowX:"hidden"});var l=new w;l.setStyle({height:"320px",padding:"1rem",width:"100%",opacity:"0"});var d=new w;d.setStyle({marginBottom:"3rem",border:"1px solid #FFFFFF19",height:"calc(320px - 7rem)",width:"100%",padding:"6px",outline:"none",overflowY:"auto",overflowX:"hidden"}),d.addAttribute("contentEditable","true");var c=new y("twitch-notes-settings-button","Save Note");c.setStyle({position:"absolute",bottom:"1rem",left:"1rem"}),c.setClickListener((function(){t&&(N.saveNote(t,d.getElement().innerHTML),n.setStyle({display:"inline-block"}))}));for(var u=function(e){var o=new w("twitch-notes-user-list-user",e);o.setClickListener((function(){for(var i,r=document.getElementsByClassName("twitch-notes-user-list-user"),s=0;sNote conflicts found for:
");p.setStyle({marginTop:"1rem"});for(var h=function(n){var o=new w("",n+" "),r=null===(i=N.inputData.notes.find((function(t){return t.user===n})))||void 0===i?void 0:i.resolved;o.setStyle({color:r?"green":"red"});var s=new x("",r?"[update]":"[resolve]");s.setStyle({cursor:"pointer"}),s.setClickListener((function(){!function(t,e,n,o){var i=new w("twitch-notes-blurred-background"),r=new w("twitch-notes-center-floating-container","Select which note to keep for "+t+"
You can modify notes to merge them and keep updated one

"),s=new w;s.setStyle({display:"flex",flexDirection:"row",flexWrap:"no-wrap",width:"100%"});var a={minWidth:"320px",padding:"6px",border:"1px solid #FFFFFF19",outline:"none"},l=["contentEditable","true"],d=new w,c=new C("","Locally saved note");d.appendCustomChild(c);var u=new w;u.setStyle(a),u.addAttribute.apply(u,l),u.setId("local-value-container");var p=new y("twitch-notes-settings-button","Save this");p.setClickListener((function(){var r,s=(null===(r=document.getElementById("local-value-container"))||void 0===r?void 0:r.innerHTML)||"";N.inputData.notes=N.inputData.notes.map((function(e){return e.user===t?{user:e.user,note:s,resolved:!0}:e})),i.remove(),e.remove(),D(n,o)})),d.appendCustomChild(u),d.appendBody("
"),d.appendCustomChild(p);var h=new w,m=new C("","Imported note");h.appendCustomChild(m);var f=new w("",N.inputData.notes.filter((function(e){return e.user===t}))[0].note||"");f.setId("import-value-container"),f.setStyle(a),f.addAttribute.apply(f,l);var v=new y("twitch-notes-settings-button","Save this");v.setClickListener((function(){var r,s=(null===(r=document.getElementById("import-value-container"))||void 0===r?void 0:r.innerHTML)||"";N.inputData.notes=N.inputData.notes.map((function(e){return e.user===t?{user:e.user,note:s,resolved:!0}:e})),i.remove(),e.remove(),D(n,o)})),h.appendCustomChild(f),h.appendBody("
"),h.appendCustomChild(v),s.appendCustomChild(d),s.appendCustomChild(h),r.appendCustomChild(s),i.appendCustomChild(r),document.body.appendChild(i.getElement())}(n,u,t,e)})),o.appendCustomChild(s),p.appendCustomChild(o)},m=0,f=s;mNote will be added for:
"+r.join("
"));v.setStyle({marginTop:"1rem",color:"white"}),u.appendCustomChild(v)}if(a.length){var g=new w("","No changes for:
"+a.join("
"));g.setStyle({marginTop:"1rem",color:"gray"}),u.appendCustomChild(g)}var b=new w;b.setStyle({height:"24px"}),u.appendCustomChild(b),s=s.filter((function(t){var e;return!(null===(e=N.inputData.notes.find((function(e){return e.user===t})))||void 0===e?void 0:e.resolved)}));var S=new y("twitch-notes-settings-button","Complete import");if(S.setStyle({}),s.length>0&&S.setStyle({background:"gray"}),s.length>0&&S.addAttribute("disabled","true"),u.appendCustomChild(S),s.length>0){var L=new w("","Import cannot be completed while there are unresolved conflicts");L.setStyle({color:"gray"}),u.appendCustomChild(L)}else S.setClickListener((function(){for(var t=0,n=N.inputData.notes;t\n \n \n \n ');o.setClickListener((function(){M.removeContainer(t)}));var i=new S("twitch-note-title",t),r=new w("twitch-note-content",N.getNote(t));r.addAttribute("contentEditable","true");var s=new w("twitch-note-save-button","SAVE");s.setClickListener((function(){N.saveNote(t,r.getElement().innerHTML),M.removeContainer(t)})),n.appendCustomChild(o),n.appendCustomChild(i),e.appendCustomChild(n),e.appendCustomChild(r),e.appendCustomChild(s),document.body.appendChild(e.getElement()),M.addContainer(t,e)}}(r)}));var a=new L;a.setStyle({height:"18px",paddingRight:"4px"}),a.addAttribute("src","https://cdn.rdarius.lt/icons/32-id-card.png"),s.appendCustomChild(a),i.insertBefore(s.getElement(),i.firstChild)}},n=0;n { + + element: HTMLElement; + + constructor(element: string, data: CustomHTMLElementConstructor) { + this.element = document.createElement(element); + + if (data.id) { + this.setId(data.id); + } else { + this.setId(element + '-generated-id-' + Math.floor(Math.random() * 10000000)); + } + + if (data.class) { + if (typeof data.class === "string") { + this.addClass(data.class); + } else { + for (const className of data.class) { + this.addClass(className); + } + } + } + + if (data.attributes) { + for (const key in data.attributes) { + this.addAttribute(key, data.attributes[key]); + } + } + + if (data.css) this.setStyle(data.css); + + if (data.body) typeof data.body === "string" ? this.setBody(data.body) : this.appendChild(data.body); + if (data.customBody) this.appendCustomChild(data.customBody); + } + + setId(id: string): CustomHTMLElement { + this.element.id = id; + return this; + } + + addClass(classes: string): CustomHTMLElement { + this.element.classList.add(classes); + return this; + } + + addAttribute(name: string, value: string): CustomHTMLElement { + this.element.setAttribute(name, value); + return this; + } + + setStyle(attributes: Partial): CustomHTMLElement { + for(let key in attributes) { + this.element.style[key] = attributes[key] || ''; + } + return this; + } + + setBody(body: string): CustomHTMLElement { + this.element.innerHTML = body; + return this; + } + + appendBody(body: string): CustomHTMLElement { + this.element.innerHTML += body; + return this; + } + + appendChild(child: HTMLElement): CustomHTMLElement { + this.element.appendChild(child); + return this; + } + + appendCustomChild(child: CustomHTMLElement): CustomHTMLElement { + this.element.appendChild(child.getElement()); + return this; + } + + getElement(): T { + return this.element as T; + } + + setClickListener(listener: Function): CustomHTMLElement { + this.element.addEventListener('click', () => listener()); + return this; + } + + setChangeListener(listener: Function): CustomHTMLElement { + this.element.addEventListener('change', () => listener()); + return this; + } + + setMouseMoveListener(listener: Function): CustomHTMLElement { + this.element.addEventListener('mousemove', () => listener()); + return this; + } + + setMouseDownListener(listener: Function): CustomHTMLElement { + this.element.addEventListener('mousedown', () => listener()); + return this; + } + + setMouseUpListener(listener: Function): CustomHTMLElement { + this.element.addEventListener('mouseup', () => listener()); + return this; + } + + remove(): void { + this.element.remove(); + } + + show(): CustomHTMLElement { + this.setStyle({ + display: 'unset', + }); + return this; + } + + hide(): CustomHTMLElement { + this.setStyle({ + display: 'none', + }); + return this; + } + +} + +export class CustomDivElement extends CustomHTMLElement { + constructor(className: string = '', content: string = '') { + super('div', {class: className, body: content}); + } +} +export class CustomInputElement extends CustomHTMLElement { + constructor(className: string = '') { + super('input', {class: className}); + } +} + +export class CustomPElement extends CustomHTMLElement { + constructor(className: string = '', content: string = '') { + super('p', {class: className, body: content}); + } +} + +export class CustomButtonElement extends CustomHTMLElement { + constructor(className: string = '', content: string = '') { + super('button', {class: className, body: content}); + } +} + +export class CustomAElement extends CustomHTMLElement { + constructor(className: string = '', content: string = '') { + super('a', {class: className, body: content}); + } +} + +export class CustomSpanElement extends CustomHTMLElement { + constructor(className: string = '', content: string = '') { + super('span', {class: className, body: content}); + } +} + +export class CustomImgElement extends CustomHTMLElement { + constructor(className: string = '', content: string = '') { + super('img', {class: className, body: content}); + } +} \ No newline at end of file diff --git a/src/HTMLTemplates.ts b/src/HTMLTemplates.ts new file mode 100644 index 0000000..95367f5 --- /dev/null +++ b/src/HTMLTemplates.ts @@ -0,0 +1,509 @@ +import { + CustomAElement, + CustomButtonElement, + CustomDivElement, + CustomHTMLElement, + CustomInputElement, + CustomPElement, CustomSpanElement +} from "./CustomHTMLElement"; +import NoteStorage from "./NoteStorage"; +import Mouse from "./Mouse"; +import NoteContainers from "./NoteContainers"; + +export function toggleSettingsList() { + const settingsWindow = document.querySelector('.twitch-notes-settings-container') as HTMLElement; + if (!settingsWindow) { + const settingsContainer = new CustomDivElement('twitch-notes-settings-container'); + const settingsBalloon = new CustomDivElement('twitch-notes-settings-balloon'); + const settingsPopover = new CustomDivElement('twitch-notes-settings-popover'); + const settingsHeader = new CustomDivElement('twitch-notes-settings-header'); + const settingsHeaderLeftElement = new CustomDivElement('twitch-notes-settings-header-left-element'); + const settingsHeaderCenterElement = new CustomDivElement('twitch-notes-settings-header-center-element'); + const settingsHeaderCenterElementContent = new CustomPElement('twitch-notes-settings-header-center-element-content','Twitch Notes Settings'); + const settingsHeaderRightElement = new CustomDivElement('twitch-notes-settings-header-right-element'); + const settingsCloseButton = new CustomButtonElement('twitch-notes-settings-close-button', getCloseButtonSVG()); + const settingsScrollableArea = new CustomDivElement('twitch-notes-settings-scrollable-area'); + const settingsContent = new CustomDivElement('twitch-notes-settings-content'); + const exportNotes = createChatSettingsLine('Export Notes'); + const importNotes = createChatSettingsLine('Import Notes'); + const clearData = createChatSettingsLine('Clear Data'); + const openAllNotes = createChatSettingsLine('View All Notes'); + + settingsContainer.appendCustomChild(settingsBalloon); + settingsBalloon.appendCustomChild(settingsPopover); + settingsPopover.appendCustomChild(settingsHeader); + settingsHeader.appendCustomChild(settingsHeaderLeftElement); + settingsHeader.appendCustomChild(settingsHeaderCenterElement); + settingsHeaderCenterElement.appendCustomChild(settingsHeaderCenterElementContent); + settingsHeader.appendCustomChild(settingsHeaderRightElement); + settingsHeaderRightElement.appendCustomChild(settingsCloseButton); + settingsPopover.appendCustomChild(settingsScrollableArea); + settingsScrollableArea.appendCustomChild(settingsContent); + settingsContent.appendCustomChild(exportNotes); + settingsContent.appendCustomChild(importNotes); + settingsContent.appendCustomChild(clearData); + settingsContent.appendCustomChild(createChatSettingsSeparator()); + settingsContent.appendCustomChild(openAllNotes); + + settingsCloseButton.setClickListener(() => toggleSettingsList()); + exportNotes.setClickListener(() => { + NoteStorage.exportNotes(); + toggleSettingsList(); + }); + importNotes.setClickListener(() => { + openImportNotesWindow(); + toggleSettingsList(); + }); + clearData.setClickListener(() => { + openClearDataWindow(); + toggleSettingsList(); + }); + openAllNotes.setClickListener(() => { + openAllNoteListWindow(); + toggleSettingsList(); + }); + + document.body.appendChild(settingsContainer.getElement()); + return; + } else { + settingsWindow.remove(); + } +} + +export function openAllNoteListWindow() { + let activeNote: string = ''; + const blurredBackground = new CustomDivElement('twitch-notes-blurred-background'); + + const noteSaved = new CustomSpanElement('', 'Note saved!'); + noteSaved.setStyle({ + color: 'green', + marginLeft: '2rem', + }); + noteSaved.hide(); + + const container = new CustomDivElement('twitch-notes-center-floating-container'); + container.setStyle({ + padding: '0', + paddingTop: '1rem', + width: '100%', + maxWidth: '800px', + height: 'calc(320px + 5rem)', + maxHeight: '90vh', + }); + + const closeButton = new CustomButtonElement('twitch-notes-settings-close-button', getCloseButtonSVG()); + closeButton.setStyle({ + position: 'absolute', + top: '1rem', + right: '1rem', + }); + + const title = new CustomDivElement('twitch-note-title', 'Notes:'); + title.setStyle({ + borderBottom: '1px solid #FFFFFF19', + paddingBottom: '1rem', + }); + container.appendCustomChild(title); + + const content = new CustomDivElement(); + content.setStyle({ + display: 'flex', + flexDirection: 'row', + flexWrap: 'no-wrap', + width: '100%', + height: '320px' + }); + container.appendCustomChild(content); + closeButton.setClickListener(() => { + blurredBackground.remove(); + }); + container.appendCustomChild(closeButton); + + const userList = new CustomDivElement(); + userList.setStyle({ + borderRight: '1px solid #FFFFFF19', + height: '320px', + minWidth: '240px', + overflowY: 'auto', + overflowX: 'hidden', + }); + + const noteContainer = new CustomDivElement(); + noteContainer.setStyle({ + height: '320px', + padding: '1rem', + width: '100%', + opacity: '0', + }); + + const note = new CustomDivElement(); + note.setStyle({ + marginBottom: '3rem', + border: '1px solid #FFFFFF19', + height: 'calc(320px - 7rem)', + width: '100%', + padding: '6px', + outline: 'none', + overflowY: 'auto', + overflowX: 'hidden', + }); + note.addAttribute('contentEditable', 'true'); + + const saveNote = new CustomButtonElement('twitch-notes-settings-button','Save Note'); + saveNote.setStyle({ + position: 'absolute', + bottom: '1rem', + left: '1rem' + }); + + saveNote.setClickListener(() => { + if(!activeNote) { + return; + } + NoteStorage.saveNote(activeNote, note.getElement().innerHTML); + noteSaved.setStyle({ + display: 'inline-block', + }); + }); + + for(let user of NoteStorage.getSavedUserList()) { + const userLine = new CustomDivElement('twitch-notes-user-list-user', user); + userLine.setClickListener(() => { + const list = document.getElementsByClassName('twitch-notes-user-list-user'); + for(let i = 0; i < list.length; i++) { + list.item(i)?.removeAttribute('data-active'); + } + noteSaved.hide(); + userLine.addAttribute('data-active', 'true'); + noteContainer.setStyle({opacity: '1'}); + note.getElement().innerHTML = NoteStorage.getNote(user); + activeNote = user; + }); + userList.appendCustomChild(userLine); + } + + noteContainer.appendCustomChild(note); + noteContainer.appendCustomChild(saveNote); + noteContainer.appendCustomChild(noteSaved); + + content.appendCustomChild(userList); + content.appendCustomChild(noteContainer); + + blurredBackground.appendCustomChild(container); + document.body.appendChild(blurredBackground.getElement()); +} + +export function openClearDataWindow() { + const blurredBackground = new CustomDivElement( 'twitch-notes-blurred-background'); + const container = new CustomDivElement('twitch-notes-center-floating-container', 'This action will delete all saved notes!

Consider exporting notes before performing this action in case you will need to use notes again'); + const spacer = new CustomDivElement(); + spacer.setStyle({ + height: '24px' + }); + const deleteAction = new CustomButtonElement('twitch-notes-settings-button', 'DELETE ALL NOTES'); + deleteAction.setStyle({ + background: 'red' + }); + deleteAction.setClickListener(() => { + NoteStorage.clearData(); + blurredBackground.remove(); + }); + const cancelAction = new CustomButtonElement('twitch-notes-settings-button', 'Cancel'); + cancelAction.setClickListener(() => { + blurredBackground.remove(); + }); + container.appendCustomChild(spacer); + container.appendCustomChild(deleteAction); + container.appendCustomChild(cancelAction); + blurredBackground.appendCustomChild(container); + document.body.appendChild(blurredBackground.getElement()); +} + +export function openImportNotesWindow() { + const blurredBackground = new CustomDivElement('twitch-notes-blurred-background'); + const container = new CustomDivElement('twitch-notes-center-floating-container', 'WARNING! This action might override existing data!

'); + const inputBlock = new CustomDivElement('','Select exported twitch notes file

'); + const input = new CustomInputElement('twitch-notes-file-input'); + input.addAttribute('type', 'file').addAttribute('accept', 'json'); + const settingsCloseButton = new CustomButtonElement('twitch-notes-settings-close-button', getCloseButtonSVG()); + settingsCloseButton.setStyle({ + position: 'absolute', + top: '1rem', + right: '1rem', + }); + + inputBlock.appendCustomChild(input); + + settingsCloseButton.setClickListener(() => { + blurredBackground.remove(); + }) + + input.setChangeListener(() => { + if (!input?.getElement()?.files) return; + // @ts-ignore + let file = input.getElement().files.item(0); + if (!file) { + return; + } + let reader = new FileReader(); + reader.readAsText(file, "UTF-8"); + reader.onload = function (evt) { + if (!evt?.target?.result) return; + + NoteStorage.inputData = JSON.parse(evt.target.result.toString()); + if (NoteStorage.inputData) { + inputBlock.setStyle({display: 'none'}); + importNotesResult(container, blurredBackground); + } + } + reader.onerror = function () { + console.error("error reading file"); + } + }); + + container.appendCustomChild(settingsCloseButton); + container.appendCustomChild(inputBlock); + blurredBackground.appendCustomChild(container); + document.body.appendChild(blurredBackground.getElement()); +} + +export function importNotesResult(container: CustomHTMLElement, blurredBackground: CustomHTMLElement) { + const willBeAdded = []; + let willBeOverwritten = []; + const noChanges = []; + + for (let user of NoteStorage.inputData.users) { + if (NoteStorage.getNote(user)) { + if (NoteStorage.inputData.notes.find(note => note.user === user)?.resolved || NoteStorage.getNote(user) !== NoteStorage.inputData.notes.find(note => note.user === user)?.note) { + willBeOverwritten.push(user); + } else { + noChanges.push(user); + } + } else { + willBeAdded.push(user); + } + } + + const resultsContainer = new CustomDivElement(); + + if (willBeOverwritten.length) { + const overwrittenContainer = new CustomDivElement('', 'Note conflicts found for:
'); + overwrittenContainer.setStyle({marginTop: '1rem'}); + + for (let item of willBeOverwritten) { + const row = new CustomDivElement('', item + ' '); + const isResolved = NoteStorage.inputData.notes.find(note => note.user === item)?.resolved; + row.setStyle({ + color: isResolved ? 'green' : 'red', + }); + const resolve = new CustomAElement('', isResolved ? '[update]' : '[resolve]'); + resolve.setStyle({ + cursor: 'pointer' + }); + + resolve.setClickListener(() => { + importNotesConflictResolve(item, resultsContainer, container, blurredBackground); + }) + row.appendCustomChild(resolve); + overwrittenContainer.appendCustomChild(row); + } + resultsContainer.appendCustomChild(overwrittenContainer); + } + + if (willBeAdded.length) { + const addedContainer = new CustomDivElement('', 'Note will be added for:
' + willBeAdded.join('
')); + addedContainer.setStyle({ + marginTop: '1rem', + color: 'white' + }); + resultsContainer.appendCustomChild(addedContainer); + } + + if (noChanges.length) { + const noChangesContainer = new CustomDivElement('', 'No changes for:
' + noChanges.join('
')); + noChangesContainer.setStyle({ + marginTop: '1rem', + color: 'gray' + }); + resultsContainer.appendCustomChild(noChangesContainer); + } + + const spacer = new CustomDivElement(); + spacer.setStyle({height: '24px'}); + resultsContainer.appendCustomChild(spacer); + + willBeOverwritten = willBeOverwritten.filter(x => !NoteStorage.inputData.notes.find(n => n.user === x)?.resolved); + + const saveButton = new CustomButtonElement('twitch-notes-settings-button', 'Complete import'); + saveButton.setStyle({}) + if (willBeOverwritten.length > 0) saveButton.setStyle({background: 'gray'}); + if (willBeOverwritten.length > 0) saveButton.addAttribute('disabled', 'true'); + + resultsContainer.appendCustomChild(saveButton) + + if (willBeOverwritten.length > 0) { + const note = new CustomDivElement('', 'Import cannot be completed while there are unresolved conflicts'); + note.setStyle({ + color: 'gray', + }); + resultsContainer.appendCustomChild(note); + } else { + saveButton.setClickListener(() => { + for (let note of NoteStorage.inputData.notes) { + NoteStorage.saveNote(note.user, note.note); + } + blurredBackground.remove(); + NoteStorage.inputData = { + users: [], + notes: [], + settings: {}, + }; + }); + } + + container.appendCustomChild(resultsContainer); +} + +export function importNotesConflictResolve( + user: string, + resultsContainer: CustomHTMLElement, + container: CustomHTMLElement, + blurredBackground: CustomHTMLElement +) { + const diffBlurredBackground = new CustomDivElement('twitch-notes-blurred-background'); + const diffContainer = new CustomDivElement('twitch-notes-center-floating-container', 'Select which note to keep for ' + user + '
You can modify notes to merge them and keep updated one

'); + + const inputContainer = new CustomDivElement(); + inputContainer.setStyle({ + display: 'flex', + flexDirection: 'row', + flexWrap: 'no-wrap', + width: '100%', + }); + + const valueStyle = { + minWidth: '320px', + padding: '6px', + border: '1px solid #FFFFFF19', + outline: 'none', + }; + const valueAttr: [string, string] = ['contentEditable', 'true']; + + const localValueContainer = new CustomDivElement(); + const pLocal = new CustomPElement('', 'Locally saved note'); + localValueContainer.appendCustomChild(pLocal); + + const localValue = new CustomDivElement(); + localValue.setStyle(valueStyle); + localValue.addAttribute(...valueAttr); + localValue.setId('local-value-container'); + const localValueSave = new CustomButtonElement('twitch-notes-settings-button', 'Save this'); + localValueSave.setClickListener(() => { + const val = document.getElementById('local-value-container')?.innerHTML || ''; + NoteStorage.inputData.notes = NoteStorage.inputData.notes.map(n => n.user === user ? { + user: n.user, + note: val, + resolved: true + } : n); + diffBlurredBackground.remove(); + resultsContainer.remove(); + importNotesResult(container, blurredBackground); + }); + localValueContainer.appendCustomChild(localValue); + localValueContainer.appendBody('
'); + localValueContainer.appendCustomChild(localValueSave); + + const importedValueContainer = new CustomDivElement(); + const iLocal = new CustomPElement('', 'Imported note'); + importedValueContainer.appendCustomChild(iLocal); + const importedValue = new CustomDivElement('', NoteStorage.inputData.notes.filter(n => n.user === user)[0].note || ''); + importedValue.setId('import-value-container'); + importedValue.setStyle(valueStyle); + importedValue.addAttribute(...valueAttr); + const importedValueSave = new CustomButtonElement('twitch-notes-settings-button', 'Save this'); + importedValueSave.setClickListener(() => { + const val = document.getElementById('import-value-container')?.innerHTML || ''; + NoteStorage.inputData.notes = NoteStorage.inputData.notes.map(n => n.user === user ? { + user: n.user, + note: val, + resolved: true + } : n); + diffBlurredBackground.remove(); + resultsContainer.remove(); + importNotesResult(container, blurredBackground); + }); + + importedValueContainer.appendCustomChild(importedValue); + importedValueContainer.appendBody('
'); + importedValueContainer.appendCustomChild(importedValueSave); + + + inputContainer.appendCustomChild(localValueContainer); + inputContainer.appendCustomChild(importedValueContainer); + + diffContainer.appendCustomChild(inputContainer); + diffBlurredBackground.appendCustomChild(diffContainer); + document.body.appendChild(diffBlurredBackground.getElement()); +} + +export function createChatSettingsLine(text: string) { + const element = new CustomDivElement('twitch-notes-settings-option-line'); + const button = new CustomButtonElement('twitch-notes-settings-option-line-button'); + const buttonContainer = new CustomDivElement('twitch-notes-settings-option-line-button-container'); + const buttonContainerContent = new CustomDivElement('twitch-notes-settings-option-line-button-container-content', text); + + buttonContainer.appendCustomChild(buttonContainerContent); + button.appendCustomChild(buttonContainer); + element.appendCustomChild(button); + return element; +} + +export function createChatSettingsSeparator() { + return new CustomDivElement('twitch-note-settings-separator'); +} + +export function openTwitchNote(username: string) { + if (NoteContainers.containers[username]) return; + const container = new CustomDivElement('twitch-note-container'); + container.setStyle({ + left: Mouse.mousePosition.x + 'px', + top: Mouse.mousePosition.y + 10 + 'px' + }); + + const header = new CustomDivElement('twitch-note-header'); + header.setMouseDownListener(() => { + NoteContainers.activeContainer = username; + Mouse.isMouseDown = true; + }); + + const closeButton = new CustomSpanElement('twitch-note-close-button', getCloseButtonSVG()); + closeButton.setClickListener(() => { + NoteContainers.removeContainer(username); + }); + + const title = new CustomSpanElement('twitch-note-title', username); + const content = new CustomDivElement('twitch-note-content', NoteStorage.getNote(username)); + content.addAttribute('contentEditable', 'true'); + + const saveButton = new CustomDivElement('twitch-note-save-button', 'SAVE'); + saveButton.setClickListener(() => { + NoteStorage.saveNote(username, content.getElement().innerHTML); + NoteContainers.removeContainer(username); + }); + + header.appendCustomChild(closeButton); + header.appendCustomChild(title); + container.appendCustomChild(header); + container.appendCustomChild(content); + container.appendCustomChild(saveButton); + document.body.appendChild(container.getElement()); + NoteContainers.addContainer(username, container); +} + +export function getCloseButtonSVG() { + return ` + + + + `; +} \ No newline at end of file diff --git a/src/Mouse.ts b/src/Mouse.ts new file mode 100644 index 0000000..7a11635 --- /dev/null +++ b/src/Mouse.ts @@ -0,0 +1,43 @@ +import NoteContainers from "./NoteContainers"; + + + +export default class Mouse { + + static isMouseDown = false; + static mousePosition = {x: 0, y: 0}; + static lastMousePosition = {x: 0, y: 0}; + static listenersSetUp = false; + + static setupListeners() { + if (!Mouse.listenersSetUp) { + document.addEventListener("mousemove", Mouse.handleMouseMove); + document.addEventListener("mouseup", Mouse.handleMouseUp); + Mouse.listenersSetUp = true; + } + } + + static handleMouseUp() { + NoteContainers.activeContainer = ''; + Mouse.isMouseDown = false; + } + + static updateMousePosition(x: number, y: number) { + Mouse.lastMousePosition = {...this.mousePosition}; + Mouse.mousePosition = {x, y}; + } + + static handleMouseMove(event: MouseEvent) { + if (Mouse.isMouseDown) { + if (NoteContainers.activeContainer) { + const dx = Mouse.mousePosition.x - Mouse.lastMousePosition.x; + const dy = Mouse.mousePosition.y - Mouse.lastMousePosition.y; + NoteContainers.containers[NoteContainers.activeContainer].setStyle({ + top: parseInt(NoteContainers.containers[NoteContainers.activeContainer].getElement().style.top) + dy + "px", + left: parseInt(NoteContainers.containers[NoteContainers.activeContainer].getElement().style.left) + dx + "px", + }); + } + } + Mouse.updateMousePosition(event.clientX, event.clientY); + } +}; \ No newline at end of file diff --git a/src/NoteContainers.ts b/src/NoteContainers.ts new file mode 100644 index 0000000..edb8791 --- /dev/null +++ b/src/NoteContainers.ts @@ -0,0 +1,17 @@ +import {CustomHTMLElement} from "./CustomHTMLElement"; + +const containers: {[key: string]: CustomHTMLElement} = {}; + +export default { + activeContainer: '', + containers, + addContainer(username: string, container: CustomHTMLElement) { + if (this.containers[username]) return; + this.containers[username] = container; + }, + removeContainer(username: string) { + if (!this.containers[username]) return; + this.containers[username].remove(); + delete this.containers[username]; + } +} \ No newline at end of file diff --git a/src/NoteStorage.ts b/src/NoteStorage.ts new file mode 100644 index 0000000..616734a --- /dev/null +++ b/src/NoteStorage.ts @@ -0,0 +1,81 @@ +import {LS_Prefix, LS_UserList} from './const'; + +export default class NoteStorage { + + static inputData: { + users: string[], + notes: { + user: string, + note: string, + resolved?: boolean, + }[], + settings: any, + } = { + users: [], + notes: [], + settings: {}, + }; + + static getSavedUserList(): string[] { + return JSON.parse(localStorage.getItem(LS_UserList) || "[]"); + } + + static getNote(username: string): string { + return localStorage.getItem(LS_Prefix + username) || ""; + } + + static addUserToSavedList(username: string) { + let userList = this.getSavedUserList(); + + if (!userList.includes(username)) { + userList.push(username); + localStorage.setItem(LS_UserList, JSON.stringify(userList)); + } + } + + static removeUserFromSavedList(username: string) { + let userList = this.getSavedUserList(); + userList = userList.filter(u => u !== username); + if (userList.length) { + localStorage.setItem(LS_UserList, JSON.stringify(userList)); + } else { + localStorage.removeItem(LS_UserList); + } + } + + static saveNote(username: string, note: string) { + this.addUserToSavedList(username); + localStorage.setItem(LS_Prefix + username, note); + } + + static deleteNote(username: string) { + this.removeUserFromSavedList(username); + localStorage.removeItem(LS_Prefix + username); + } + + static exportNotes() { + const dataObject = { + users: this.getSavedUserList(), + notes: this.getSavedUserList().map(user => { + return {user: user, note: this.getNote(user)}; + }), + settings: {}, + }; + const data = JSON.stringify(dataObject) + + const blob = new Blob([data], {type: 'text/json'}); + const elem = window.document.createElement('a'); + elem.href = window.URL.createObjectURL(blob); + elem.download = 'twitch-notes-' + (Date.now()) + '.json'; + document.body.appendChild(elem); + elem.click(); + document.body.removeChild(elem); + } + + static clearData() { + const users = this.getSavedUserList(); + for(let user of users) { + this.deleteNote(user); + } + } +} \ No newline at end of file diff --git a/src/TwitchNote.ts b/src/TwitchNote.ts index 8dd99be..8c3bd22 100644 --- a/src/TwitchNote.ts +++ b/src/TwitchNote.ts @@ -1,3 +1,12 @@ +import { + CustomButtonElement, + CustomDivElement, + CustomImgElement, + CustomSpanElement +} from "./CustomHTMLElement"; +import {ELEMENT_ButtonContainer} from "./const"; +import {openTwitchNote, toggleSettingsList} from "./HTMLTemplates"; + export default class TwitchNote { observer: MutationObserver | null = null; @@ -8,10 +17,12 @@ export default class TwitchNote { init(chatContainerNode: Element) { this.chatContainerNode = chatContainerNode; - console.log('CHAT CONTAINER INITIATED'); this.startObserver(chatContainerNode); // add Notes management button this.addNotesButton(); + setTimeout(() => { + this.addUserNoteBadges(); + }, 1000); } startObserver(targetNode: Element) { @@ -42,8 +53,63 @@ export default class TwitchNote { } addNotesButton() { + const buttonContainer = document.querySelector(`.${ELEMENT_ButtonContainer}`); + if (!buttonContainer) { + setTimeout(() => this.addNotesButton(), 100); + return; + } + + const container1 = new CustomDivElement(); + container1.setStyle({marginLeft: "0.5rem !important"}); + const container2 = new CustomDivElement(); + container1.setStyle({display: "inline-flex !important"}); + const settingsButton = new CustomButtonElement('twitch-notes-settings-button', 'Notes'); + + settingsButton.setClickListener(() => { + toggleSettingsList(); + }); + + container2.appendCustomChild(settingsButton); + container1.appendCustomChild(container2); + + if (buttonContainer.lastChild) { + buttonContainer.lastChild.insertBefore( + container1.getElement(), + buttonContainer.lastChild.lastChild + ); + } else { + buttonContainer.appendChild(container1.getElement()); + } } addUserNoteBadges() { + let nodes = document.getElementsByClassName("chat-line__message"); + for (let i = 0; i < nodes.length; i++) { + const node = nodes.item(i); + if (!node) continue; + const n = node.querySelector(".chat-author__display-name"); + if (!n) continue; + const usernameContainer = node.querySelector(".chat-line__username-container"); + if (!usernameContainer) continue; + const username = n.getAttribute('data-a-user'); + if (username) { + let noteButton = usernameContainer.querySelector(".twitch-note"); + if (!noteButton) { + const twitchNote = new CustomSpanElement('twitch-note'); + twitchNote.setStyle({cursor: 'pointer'}); + twitchNote.setClickListener(() => { + openTwitchNote(username); + }); + const img = new CustomImgElement(); + img.setStyle({height: '18px', paddingRight: '4px'}); + img.addAttribute('src', 'https://cdn.rdarius.lt/icons/32-id-card.png'); + twitchNote.appendCustomChild(img); + usernameContainer.insertBefore( + twitchNote.getElement(), + usernameContainer.firstChild + ); + } + } + } } } \ No newline at end of file diff --git a/src/const.ts b/src/const.ts new file mode 100644 index 0000000..6353f9a --- /dev/null +++ b/src/const.ts @@ -0,0 +1,3 @@ +export const LS_UserList = "twitch-note-all-users-list"; +export const LS_Prefix = "twitch-note-"; +export const ELEMENT_ButtonContainer = "chat-input__buttons-container"; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index d97d26a..b67eba6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import './styles/main.scss'; import TwitchNote from "./TwitchNote"; +import Mouse from "./Mouse"; const ChatContainerClass = 'chat-scrollable-area__message-container'; const twitchNote = new TwitchNote(); @@ -24,6 +25,7 @@ function init() { if (!chatExists) { // chat container appeared twitchNote.init(targetNode); + Mouse.setupListeners(); } chatExists = true; } else { diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..314197a --- /dev/null +++ b/src/types.ts @@ -0,0 +1,10 @@ +import {CustomHTMLElement} from "./CustomHTMLElement"; + +export type CustomHTMLElementConstructor = { + class?: string | string[], + id?: string, + css?: Partial, + attributes?: {[key: string]: string}, + body?: string | HTMLElement, + customBody?: CustomHTMLElement, +} \ No newline at end of file