Implemented notes importing
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
(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) || "[]");
|
||||
},
|
||||
@@ -13,11 +23,225 @@
|
||||
}
|
||||
},
|
||||
saveNote: (username, note) => {
|
||||
console.log({username, note});
|
||||
Notes.addUserToSavedList(username);
|
||||
localStorage.setItem(LS_Prefix + username, note);
|
||||
},
|
||||
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: {},
|
||||
}));
|
||||
},
|
||||
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'
|
||||
};
|
||||
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);
|
||||
|
||||
/*
|
||||
TODO: make popup to allow checking differences between existing notes and ones that would override them
|
||||
TODO: allow to edit notes and select which one to keep after import, if any content was modified after opening comparison window show that record was merged as a status
|
||||
TODO: show visually that records have some data that will be merged or overwritten entirely
|
||||
*/
|
||||
|
||||
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: () => {
|
||||
console.log('TODO: Implement Notes Data Clearing');
|
||||
},
|
||||
openAllNotes: () => {
|
||||
console.log('TODO: Implement Open All Notes functionality');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +271,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function createElement(tag, body, classes, styles, attributes) {
|
||||
function createElement(tag, body, classes, styles, attributes, id) {
|
||||
const element = document.createElement(tag);
|
||||
|
||||
if (body) {
|
||||
@@ -80,6 +304,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
element.setAttribute('id', id ? id : 'element-' + Math.floor((Math.random() * 10000000)))
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -125,7 +351,7 @@
|
||||
isMouseDown = true;
|
||||
});
|
||||
|
||||
const closeButton = createElement('span', "X", 'twitch-note-close-button')
|
||||
const closeButton = createElement('span', xButton, 'twitch-note-close-button')
|
||||
closeButton.addEventListener("click", function close() {
|
||||
removeContainer(username);
|
||||
});
|
||||
@@ -173,7 +399,7 @@
|
||||
});
|
||||
|
||||
const img = createElement('img', null, null, {
|
||||
height: '18px',
|
||||
height: '18px',
|
||||
paddingRight: '4px'
|
||||
}, {
|
||||
src: "https://cdn.rdarius.lt/icons/32-id-card.png"
|
||||
@@ -194,14 +420,95 @@
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
importNotes.addEventListener('click', Notes.importNotes);
|
||||
clearData.addEventListener('click', Notes.clearAllData);
|
||||
openAllNotes.addEventListener('click', Notes.openAllNotes);
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
console.log('toggle settings');
|
||||
}
|
||||
|
||||
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 settingsWindowOpen = false;
|
||||
let settingsWindow = null;
|
||||
|
||||
let LS_UserList = "twitch-note-all-users-list";
|
||||
let LS_Prefix = "twitch-note-";
|
||||
|
||||
Reference in New Issue
Block a user