diff --git a/background.js b/background.js index d573fa7..678f1e4 100644 --- a/background.js +++ b/background.js @@ -1,25 +1,160 @@ -// add context menu on select -function setupContextMenu(){ +/** Helpful type definitions + * @typedef {Object} Note + * @property {string|number} noteID - Unique ID identifying the note + * @property {string} title - Title of the note + * @property {string} content - Content of the note + * @property {Date} createdDate - The date the note was created + * @property {number} folderID - unique ID of the folder + * + * @typedef {Object} Folder + * @property {string} folderName + * @property {number} folderID + * + * @typedef AppData + * @property {Note[]} notes + * @property {number[]} folders + * @property {{theme: number}} settings + */ + +/** @type AppData - Stores the app's state */ +const appData = { + notes: [], + folders: [], + settings: { + theme: "light" + } +} + + +// appData.settings.theme + +const saveStorage = async () => chrome.storage.local.set(appData); +const getStorage = async () => chrome.storage.local.get(appData); + +chrome.runtime.onInstalled.addListener(async () => { + // setup chrome context menus chrome.contextMenus.create({ id: "addnote", title: "Add to Harbor", contexts: ["selection"] }) -} -chrome.runtime.onInstalled.addListener(() => { setupContextMenu() }) + + // initialize storage + const initAppData = await getStorage(); + Object.assign(appData, initAppData); + chrome.storage.local.set(appData); +}); // handle context menu click chrome.contextMenus.onClicked.addListener((info, tab) => { - console.log(info, tab); if (info.menuItemId === "addnote") { - console.log(`Adding the note "${info.selectionText}"`); - - // we don't need a response, don't bother waiting for one - chrome.runtime.sendMessage({content: info.selectionText}); + processCommand({ + command: "addNote", + data: { /** @type Note */ + title: "", + content: info.selectionText, + createdDate: Date.now(), + folderID: -1 + } + }); } -}) +}); // open panel onclick chrome.sidePanel .setPanelBehavior({ openPanelOnActionClick: true }) - .catch((error) => console.error(error)) \ No newline at end of file + .catch((error) => console.error(error)) + +/** Usage + * chrome.runtime.sendMessage({command: "getData"}) + * chrome.runtime.sendMessage({command: "addNote", data: {title: "test", content: "hello world"}}) + **/ +chrome.runtime.onMessage.addListener(processCommand); + +/** + * The core event loop for the extension + * @param {{command: string, data?: Object}} message + * @param {chrome.runtime.MessageSender} sender + * @param {(response?: any) => void} sendResponse + */ +function processCommand(message, sender, sendResponse) { + if (message === null || message.command === null) return; + const command = message.command; + const data = message.data; + + switch (command) { + case "getData": { + sendResponse(appData); + break; + } + case "getNotes": { + sendResponse(appData.notes); + break; + } + case "getStructure": { + sendResponse(appData.folders); + break; + } + case "getTheme": { + sendResponse(appData.settings.theme); + break; + } + + case "setTheme": { + appData.settings.theme = data.theme; + chrome.runtime.sendMessage({command: "setThemeUI", data: {theme: data.theme}}); + saveStorage(); + break; + } + + case "addNote": { + const { title, content, createdDate, folderID } = data; + // you can replace the noteID with whatever you want, as long as it's a unique string/number + let noteObject = { + noteID: (Math.random()+"").slice(2) + (Math.random()+"").slice(2), + title: title || "", + content: content || "", + createdDate: createdDate || Date.now(), + folderID: folderID || -1 + } + + if (noteObject.title === "" && noteObject.content === "") { + console.error("WARNING, TRYING TO ADD EMPTY NOTE!", noteObject); + return; + } + + appData.notes.push(noteObject); + chrome.runtime.sendMessage({command: "addNoteUI", data: noteObject}); + saveStorage(); + break; + } + + case "deleteNote": { + const noteID = data.noteID; + + let deletedNoteIndex = appData.notes.findIndex(note => note.noteID === noteID); + console.log(`deleting note index ${deletedNoteIndex}`); + if (deletedNoteIndex !== -1) { + let noteObject = appData.notes.splice(deletedNoteIndex, 1)[0]; + chrome.runtime.sendMessage({command: "deleteNoteUI", data: noteObject}); + saveStorage(); + } else { + console.error(`Trying to delete note ${noteID}, which can't be found`); + } + + break; + } + + case "deleteAllNotes": { + appData.notes = []; + chrome.runtime.sendMessage({command: "deleteAllNotesUI"}); + saveStorage(); + break; + } + + default: { + sendResponse("I DONT KNOW WHAT YOU WANT ME TO DO"); + break; + } + } +} \ No newline at end of file diff --git a/js/_main_.js b/js/_main_.js new file mode 100644 index 0000000..13bd237 --- /dev/null +++ b/js/_main_.js @@ -0,0 +1,25 @@ +(async _ => { + // notes are stored as an object + // key: Date.now() + // value: {content: string, tags: string[], title: string} + // this lets us sort the notes by date, and delete by some ID + let notes = {}; + + await loadSettings(); + await loadNotes(); + //insertTag(); + //formatBar.append(createFormatBar()); + + add.addEventListener("click", _ => { addNote(""); }); + document.addEventListener("DOMContentLoaded", _ => { reloadNoteHTML(); loadFolders(); }); + document.addEventListener("visibilitychange", _ => { saveNotesOrder(); saveFolders(); }); + + // context menu --> add new note + chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + const content = request.content; + + // make that new message if it's non-empty + if (content) addNote(content); + }); +})() + diff --git a/js/formatBar.js b/js/formatBar.js index 96638a9..46c8176 100644 --- a/js/formatBar.js +++ b/js/formatBar.js @@ -2,7 +2,7 @@ * Generates "bottom bar" below note boxes to hold text formating buttons * @returns {object} bottomBar */ -function createFormatBar() { +export default function createFormatBar() { const bottomBar = document.createElement("div"); bottomBar.className = "bottom-bar"; diff --git a/js/notes.js b/js/notes.js index 96831df..78a6cec 100644 --- a/js/notes.js +++ b/js/notes.js @@ -14,43 +14,46 @@ function resizeTextarea(textarea) { // this lets us sort the notes by date, and delete by some ID let notes = {}; -// a bunch of helper functions in case we need them later -// tbh we don't really need them but it's nicer to type -function loadNotes() { - const notesText = localStorage.getItem("notesData") || "{}"; - notes = JSON.parse(notesText); -} -function saveNotes() { - localStorage.setItem("notesData", JSON.stringify(notes)); -} -function reloadNoteHTML() { - // delete all the current notes - const currentNotes = Array.from(document.getElementsByClassName("note")); - for (let i = 0; i < currentNotes.length; i++) { - currentNotes[i].remove(); - } - - // add them all back from notes[] - Object.entries(notes).reverse().forEach(([id, {title, content, tags}]) => { - addNoteHTML(title, content, tags, id); - }); -} -function deleteNote(id) { - delete notes[id]; - saveNotes(); -} -function deleteAllNotes() { - notes = {}; - reloadNoteHTML(); - saveNotes(); - reloadFolders(); -} + // a bunch of helper functions in case we need them later + // tbh we don't really need them but it's nicer to type + async loadNotes() { + const { notesData } = await chrome.storage.local.get("notesData"); + data = notesData; + }, + + async saveNotes() { + console.log("should be saved") + chrome.storage.local.set({notesData: notes}); + }, + + reloadNoteHTML() { + // delete all the current notes + const currentNotes = Array.from(document.getElementsByClassName("note")); + for (let i = 0; i < currentNotes.length; i++) { + currentNotes[i].remove(); + } -/** - * this function only creates the note in the notes[] array, then calls addNoteHTML - * @param {string} text - textual/body content of note - * @param {object} insertAfter - the note that precedes the new note you're trying to add - */ + // add them all back from notes[] + Object.entries(notes).reverse().forEach(([id, {title, content, tags}]) => { + addNoteHTML(title, content, tags, id); + }); + }, + async deleteNote(id) { + delete notes[id]; + await saveNotes(); + }, + async deleteAllNotes() { + notes = {}; + reloadNoteHTML(); + await saveNotes(); + reloadFolders(); + }, + + /** + * this function only creates the note in the notes[] array, then calls addNoteHTML + * @param {string} text - textual/body content of note + * @param {object} insertAfter - the note that precedes the new note you're trying to add + */ function eraseNote() { titleInput.value = ""; @@ -63,86 +66,86 @@ function addNote(text, insertAfter) { infoInput.value = ""; // empty out the textbox titleInput.value = ""; - // stop if no text is provided - if (title === "" && content === "") return; + // stop if no text is provided + if (title === "" && content === "") return; - const id = Date.now(); - const tags = []; + const id = Date.now(); + const tags = []; - notes[id] = { title, content, tags }; + notes[id] = { title, content, tags }; - //Move new note to top - const notesArray = Object.entries(notes); - if (notesArray.length > 0) { - newNote = notesArray.pop() - notesArray.unshift(newNote); - - const sortedNotes = {}; - notesArray.forEach(([id, note]) => { - sortedNotes[id] = note; - }); - notes = sortedNotes; - } + //Move new note to top + const notesArray = Object.entries(notes); + if (notesArray.length > 0) { + newNote = notesArray.pop() + notesArray.unshift(newNote); - saveNotes(); - reloadNoteHTML(); - console.log(tags); -} - -/** - * Generates the actual HTML element in the DOM - * don't call directly unless you're reloading - * @param {string} title - title of a note - * @param {string} text - textual/body content of a note - * @param {string[]} tags - list containing all tags of a given note - * parameter id = id - * @param {object} insertAfter - the note that precedes the new note you're trying to add - */ -function addNoteHTML(title, text, tags, id, insertAfter = null) { - if (!id) { - console.log("no ID provided!!!"); - } - // create note elements, then add event listeners - const note = document.createElement("div"); - note.className = "note"; - note.id = id; - note.draggable = true; - - const deleteButton = document.createElement("button"); - deleteButton.className = "del"; - deleteButton.textContent = "X"; - deleteButton.style.display = "none"; - deleteButton.addEventListener("click", function (event) { - event.stopPropagation(); - deleteNote(id); - note.remove(); - customMenu.style.display = "none"; - - // remove overlay - let ove = document.getElementsByClassName("overlay"); - if (ove.length !== 0) document.body.removeChild(ove[0]); - }); - note.appendChild(deleteButton); - - addDraggingEvents(note); - addContextMenuToNote(note); - - note.addEventListener("mouseover", function () { - deleteButton.style.display = "block"; - }); - - note.addEventListener("mouseout", function () { + const sortedNotes = {}; + notesArray.forEach(([id, note]) => { + sortedNotes[id] = note; + }); + notes = sortedNotes; + } + + await saveNotes(); + reloadNoteHTML(); + console.log(tags); + }, + + /** + * Generates the actual HTML element in the DOM + * don't call directly unless you're reloading + * @param {string} title - title of a note + * @param {string} text - textual/body content of a note + * @param {string[]} tags - list containing all tags of a given note + * parameter id = id + * @param {object} insertAfter - the note that precedes the new note you're trying to add + */ + addNoteHTML(title, text, tags, id, insertAfter = null) { + if (!id) { + console.log("no ID provided!!!"); + } + // create note elements, then add event listeners + const note = document.createElement("div"); + note.className = "note"; + note.id = id; + note.draggable = true; + + const deleteButton = document.createElement("button"); + deleteButton.className = "del"; + deleteButton.textContent = "X"; deleteButton.style.display = "none"; - }); + deleteButton.addEventListener("click", function (event) { + event.stopPropagation(); + deleteNote(id); + note.remove(); + customMenu.style.display = "none"; + + // remove overlay + let ove = document.getElementsByClassName("overlay"); + if (ove.length !== 0) document.body.removeChild(ove[0]); + }); + note.appendChild(deleteButton); - note.addEventListener("click", function (event) { - // if the user clicks on a link inside the note, don't change into edit mode - if (event.target.nodeName === "A") return; + addDraggingEvents(note); + addContextMenuToNote(note); - if (!this.classList.contains("overlay-created")) { - const overlay = document.createElement("div"); - overlay.className = "overlay"; - document.body.appendChild(overlay); + note.addEventListener("mouseover", function () { + deleteButton.style.display = "block"; + }); + + note.addEventListener("mouseout", function () { + deleteButton.style.display = "none"; + }); + + note.addEventListener("click", function (event) { + // if the user clicks on a link inside the note, don't change into edit mode + if (event.target.nodeName === "A") return; + + if (!this.classList.contains("overlay-created")) { + const overlay = document.createElement("div"); + overlay.className = "overlay"; + document.body.appendChild(overlay); // show only noteContent const noteTitle = note.getElementsByClassName("note-title")[0]; @@ -167,21 +170,21 @@ function addNoteHTML(title, text, tags, id, insertAfter = null) { note.style.zIndex = null; note.draggable = true; - // update noteDisplay, persist to notes - notes[note.id].title = noteTitle.innerText; - notes[note.id].content = noteContent.value; - //TODO: persist tags as well - noteDisplay.innerHTML = DOMPurify.sanitize(marked.parse(noteContent.value)); + // update noteDisplay, persist to notes + notes[note.id].title = noteTitle.innerText; + notes[note.id].content = noteContent.value; + //TODO: persist tags as well + noteDisplay.innerHTML = DOMPurify.sanitize(marked.parse(noteContent.value)); - // only show noteDisplay - noteContent.classList.add("displayNone"); - noteDisplay.classList.remove("displayNone"); - }); + // only show noteDisplay + noteContent.classList.add("displayNone"); + noteDisplay.classList.remove("displayNone"); + }); - this.classList.add("overlay-created"); - this.style.zIndex = "999"; - } - }); + this.classList.add("overlay-created"); + this.style.zIndex = "999"; + } + }); const noteTitle = document.createElement("div"); noteTitle.contentEditable = "plaintext-only"; @@ -195,25 +198,25 @@ function addNoteHTML(title, text, tags, id, insertAfter = null) { noteDisplay.className = "note-display body"; noteDisplay.innerHTML = DOMPurify.sanitize(marked.parse(text)); - note.appendChild(noteTitle); - note.appendChild(noteContent); - note.appendChild(noteDisplay); + note.appendChild(noteTitle); + note.appendChild(noteContent); + note.appendChild(noteDisplay); - const tagBar = document.createElement("div"); - tagBar.className = "tag-bar"; + const tagBar = document.createElement("div"); + tagBar.className = "tag-bar"; - if(tags) { - tags.forEach((tag) => { - const tagElement = document.createElement("div"); - tagElement.className = "note-tag"; - tagElement.textContent = tag; + if(tags) { + tags.forEach((tag) => { + const tagElement = document.createElement("div"); + tagElement.className = "note-tag"; + tagElement.textContent = tag; - tagBar.appendChild(tagElement); - }); - } + tagBar.appendChild(tagElement); + }); + } - const bottomBar = createFormatBar(); + const bottomBar = createFormatBar(); const timeText = document.createElement("div"); timeText.className = "time-text"; @@ -230,94 +233,95 @@ function addNoteHTML(title, text, tags, id, insertAfter = null) { bottomDiv.appendChild(timeText); note.appendChild(bottomDiv); - if (insertAfter && insertAfter.nextElementSibling) { - container.insertBefore(note, insertAfter.nextElementSibling); - } else { - container.prepend(note); - } -} - -function saveNotesOrder() { - const newNotesOrder = {}; - const noteElements = Array.from(container.getElementsByClassName("note")); + if (insertAfter && insertAfter.nextElementSibling) { + container.insertBefore(note, insertAfter.nextElementSibling); + } else { + container.prepend(note); + } + }, - noteElements.forEach(noteElement => { - const id = noteElement.id; - newNotesOrder[id] = notes[id]; - }); + async saveNotesOrder() { + const newNotesOrder = {}; + const noteElements = Array.from(container.getElementsByClassName("note")); - notes = newNotesOrder; - saveNotes(); -} + noteElements.forEach(noteElement => { + const id = noteElement.id; + newNotesOrder[id] = notes[id]; + }); -infoInput.addEventListener("keydown", evt => { - if (evt.ctrlKey && evt.key === "Enter") { - evt.preventDefault(); - addNote(""); // that was easy - } -}); + notes = newNotesOrder; + await saveNotes(); + }, -function addContextMenuToNote(note) { - console.log(note); - note.addEventListener("contextmenu", function(event) { - event.preventDefault(); + async addContextMenuToNote(note) { + console.log(note); + note.addEventListener("contextmenu", function(event) { + event.preventDefault(); - customMenu.style.display = "block"; - customMenu.style.left = `${event.clientX}px`; - customMenu.style.top = `${event.clientY}px`; + customMenu.style.display = "block"; + customMenu.style.left = `${event.clientX}px`; + customMenu.style.top = `${event.clientY}px`; - document.getElementById("rem").addEventListener("click", function() { - let tagBar = note.querySelector('.tag-bar'); - while (tagBar.firstChild) { - tagBar.removeChild(tagBar.firstChild); - } - notes[note.id].tags = []; - saveNotes(); + document.getElementById("rem").addEventListener("click", async () => { + let tagBar = note.querySelector('.tag-bar'); + while (tagBar.firstChild) { + tagBar.removeChild(tagBar.firstChild); + } + notes[note.id].tags = []; + await saveNotes(); - customMenu.style.display = "none"; - }); + customMenu.style.display = "none"; + }); - document.getElementById("rem").addEventListener("mouseover", function() { - tagMenu.style.display = "none"; - }); - - document.getElementById("addtofolder").addEventListener("mouseover", function(event) { - const tagInputs = document.querySelectorAll('.tag-input'); - - tagMenu.innerHTML = ''; - - tagInputs.forEach(input => { - const menuItem = document.createElement('div'); - menuItem.className = "menu-item"; - menuItem.textContent = input.textContent; - - menuItem.addEventListener("click", () => { - let tagBar = note.querySelector('.tag-bar'); - const tagElement = document.createElement("div"); - tagElement.className = "note-tag"; - tagElement.textContent = menuItem.textContent; - - while (tagBar.firstChild) { - tagBar.removeChild(tagBar.firstChild); - } - tagBar.appendChild(tagElement); - - notes[note.id].tags = []; - notes[note.id].tags.push(menuItem.textContent); - saveNotes(); + document.getElementById("rem").addEventListener("mouseover", function() { + tagMenu.style.display = "none"; + }); + + document.getElementById("addtofolder").addEventListener("mouseover", function(event) { + const tagInputs = document.querySelectorAll('.tag-input'); + + tagMenu.innerHTML = ''; + + tagInputs.forEach(input => { + const menuItem = document.createElement('div'); + menuItem.className = "menu-item"; + menuItem.textContent = input.textContent; + + menuItem.addEventListener("click", async () => { + let tagBar = note.querySelector('.tag-bar'); + const tagElement = document.createElement("div"); + tagElement.className = "note-tag"; + tagElement.textContent = menuItem.textContent; + + while (tagBar.firstChild) { + tagBar.removeChild(tagBar.firstChild); + } + tagBar.appendChild(tagElement); + + notes[note.id].tags = []; + notes[note.id].tags.push(menuItem.textContent); + await saveNotes(); + }); + + tagMenu.appendChild(menuItem); }); - - tagMenu.appendChild(menuItem); + + const customMenuRect = customMenu.getBoundingClientRect(); + tagMenu.style.left = `${customMenuRect.right + 10}px`; + tagMenu.style.top = `${customMenuRect.top}px`; + tagMenu.style.display = "block"; }); - - const customMenuRect = customMenu.getBoundingClientRect(); - tagMenu.style.left = `${customMenuRect.right + 10}px`; - tagMenu.style.top = `${customMenuRect.top}px`; - tagMenu.style.display = "block"; }); - }); + } } +infoInput.addEventListener("keydown", evt => { + if (evt.ctrlKey && evt.key === "Enter") { + evt.preventDefault(); + addNote(""); // that was easy + } +}); + const customMenu = document.createElement("div"); customMenu.className = "custom-context-menu"; diff --git a/js/settings.js b/js/settings.js index 6ef1dde..50e87ca 100644 --- a/js/settings.js +++ b/js/settings.js @@ -6,7 +6,7 @@ const defaultSettings = { Object.assign(settings, defaultSettings); -function loadSettings() { +async function loadSettings() { const settingsObject = JSON.parse(localStorage.getItem("settings")) || {}; const keys = Object.keys(settingsObject); console.log("keys!!", keys); @@ -76,7 +76,7 @@ function saveSettings() { } } -function sortNotesByTag() { +async function sortNotesByTag() { const notesArray = Object.entries(notes); notesArray.sort(([, noteA], [, noteB]) => { @@ -112,10 +112,10 @@ function sortNotesByTag() { notes = sortedNotes; reloadNoteHTML(); - saveNotes(); + await saveNotes(); } -function sortNotesByDate() { +async function sortNotesByDate() { const notesArray = Object.entries(notes); notesArray.sort(([idA], [idB]) => idA - idB); @@ -126,7 +126,7 @@ function sortNotesByDate() { notes = sortedNotes; reloadNoteHTML(); - saveNotes(); + await saveNotes(); } // Get references to elements diff --git a/js/tags.js b/js/tags.js index f9bf4ab..5f11312 100644 --- a/js/tags.js +++ b/js/tags.js @@ -292,7 +292,7 @@ function insertTag(folderName) { notes[draggedNoteId].tags = []; notes[draggedNoteId].tags.push(tagText); - saveNotes(); + await saveNotes(); } } diff --git a/manifest.json b/manifest.json index f48ced1..cba89db 100644 --- a/manifest.json +++ b/manifest.json @@ -7,7 +7,7 @@ "service_worker": "background.js" }, "side_panel":{ - "default_path": "sidepanel.html" + "default_path": "./pages/main.html" }, "icons": { "64": "img/NH_logo_zoom.png", diff --git a/newjs/_main_.js b/newjs/_main_.js new file mode 100644 index 0000000..4f51e1c --- /dev/null +++ b/newjs/_main_.js @@ -0,0 +1,333 @@ +let notes = []; +let folders = []; +let currentFolder = -1; + +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + console.log(request); + + const { command, data } = request; + + if (command === "UpdateUI") { + reloadNoteHTML(); + } else if (command === "addNoteUI") { + let noteObject = data; + addNoteHTML(noteObject) + } else if (command === "deleteNoteUI") { + let noteObject = data; + let target = document.getElementById(noteObject.noteID); + + if (target) target.remove(); + } +}); + + + +add.addEventListener("click", _ => addNote()); +settingsButton.addEventListener("click", _ => { window.location.href = "settings.html" }); +document.addEventListener("DOMContentLoaded", _ => reloadNoteHTML()); +document.addEventListener("visibilitychange", _ => reloadNoteHTML()); + +function reloadData(callback) { + return chrome.runtime.sendMessage({command: "getData"}, (data) => { + notes = data.notes; + folders = data.folders; + callback(); + }); +} + + +const infoInput = document.getElementById("info"); +const titleInput = document.getElementById("title"); +const noteEditor = document.getElementById("noteEditor"); + +const closeButton = document.getElementById("noteEditor_close"); +closeButton.addEventListener("click", _ => { noteEditor.close(); }); + + +function deleteNote(id) { + console.log(`deleting note ${id}`) + chrome.runtime.sendMessage({command: "deleteNote", data: {noteID: id}}); +} +function deleteAllNotes() { + chrome.runtime.sendMessage({command: "deleteAllNotes"}); +} + +function addNote() { + const title = titleInput.value || ""; + const content = infoInput.innerText || ""; + infoInput.innerText = ""; // empty out the textbox + titleInput.value = ""; + + // stop if no text is provided + if (title === "" && content === "") return; + + const noteObject = { title, content, folderID: -1 } + chrome.runtime.sendMessage({command: "addNote", data: noteObject}); +} + +// call on DOM reload and page init +function reloadNoteHTML() { + chrome.runtime.sendMessage({command: "getNotes"}, (notes) => { + // delete all the current notes + Array.from(document.getElementsByClassName("note")).forEach(v => v.remove()); + + // add them all back from notes[] + notes.forEach(noteObject => { + addNoteHTML(noteObject); + }); + }); +} + +/** + * Generates the actual HTML element in the DOM + * don't call directly unless you're reloading + * @param {Note} noteObject - The note we're trying to add + * @param {object} insertAfter - the note that precedes the new note you're trying to add + */ +function addNoteHTML(noteObject, insertAfter) { + let { noteID, title, content, createdDate, folderID } = noteObject; + + if (!noteID) console.log("no ID provided!!!"); + + // create note elements, then add event listeners + const note = document.createElement("div"); + note.className = "note"; + note.id = noteID; + note.draggable = true; + + const deleteButton = document.createElement("button"); + deleteButton.className = "del"; + deleteButton.textContent = "X"; + deleteButton.addEventListener("click", evt => { + evt.stopPropagation(); + deleteNote(noteID); + customMenu.style.display = "none"; + }); + note.appendChild(deleteButton); + + note.addEventListener("mouseover", () => { + deleteButton.style.display = "block"; + }); + note.addEventListener("mouseout", () => { + // deleteButton.style.display = "none"; + }); + note.addEventListener("click", function (event) { + // if the user clicks on a link inside the note, don't change into edit mode + if (event.target.nodeName === "A") return; + + noteEditor.showModal(); + + document.getElementById("noteEditor_title").value = title; + document.getElementById("noteEditor_info").innerText = content; + + // show only noteContent + const noteTitle = note.getElementsByClassName("note-title")[0]; + const noteContent = note.getElementsByClassName("note-content")[0]; + const noteDisplay = note.getElementsByClassName("note-display")[0]; + noteContent.classList.remove("displayNone"); + noteDisplay.classList.add("displayNone"); + + + noteEditor.addEventListener("click", evt => { + + }) + // Disable dragging if note in focused mode + overlay.addEventListener("click", function () { + // remove overlay + document.body.removeChild(overlay); + note.classList.remove("overlay-created"); + note.style.zIndex = null; + note.draggable = true; + + // update noteDisplay, persist to notes + notes[note.id].title = noteTitle.innerText; + notes[note.id].content = noteContent.value; + //TODO: persist tags as well + noteDisplay.innerHTML = DOMPurify.sanitize(marked.parse(noteContent.value)); + + // only show noteDisplay + noteContent.classList.add("displayNone"); + noteDisplay.classList.remove("displayNone"); + }); + + this.classList.add("overlay-created"); + this.style.zIndex = "999"; + }); + + const noteTitle = document.createElement("div"); + noteTitle.className = "note-title"; + noteTitle.innerText = title; + const noteContent = document.createElement("div"); + noteContent.className = "note-content displayNone body"; + noteContent.value = content; + const noteDisplay = document.createElement("div"); + noteDisplay.className = "note-display body"; + noteDisplay.innerHTML = DOMPurify.sanitize(marked.parse(content)); + + note.appendChild(noteTitle); + note.appendChild(noteContent); + note.appendChild(noteDisplay); + + // const tagBar = document.createElement("div"); + // tagBar.className = "tag-bar"; + + // if(tags) { + // tags.forEach((tag) => { + // const tagElement = document.createElement("div"); + // tagElement.className = "note-tag"; + // tagElement.textContent = tag; + + // tagBar.appendChild(tagElement); + // }); + // } + + // note.appendChild(tagBar); + + // const bottomBar = createFormatBar(); + + const timeText = document.createElement("div"); + timeText.className = "time-text"; + timeText.style = "justify-content: right"; + const noteCreatedTime = new Date(+createdDate); + timeText.textContent = `${noteCreatedTime.toLocaleString([], { + timeStyle: "short", + dateStyle: "short" + })}`; + const bottomDiv = document.createElement("div"); + bottomDiv.className = "bottomDiv"; + // bottomDiv.appendChild(bottomBar); + bottomDiv.appendChild(timeText); + note.appendChild(bottomDiv); + + if (insertAfter && insertAfter.nextElementSibling) { + container.insertBefore(note, insertAfter.nextElementSibling); + } else { + container.prepend(note); + } +} + +async function saveNotesOrder() { + const newNotesOrder = {}; + const noteElements = Array.from(container.getElementsByClassName("note")); + + noteElements.forEach(noteElement => { + const id = noteElement.id; + newNotesOrder[id] = notes[id]; + }); + + notes = newNotesOrder; + await saveNotes(); +} + +async function addContextMenuToNote(note) { + note.addEventListener("contextmenu", function(event) { + event.preventDefault(); + + customMenu.style.display = "block"; + customMenu.style.left = `${event.clientX}px`; + customMenu.style.top = `${event.clientY}px`; + + document.getElementById("rem").addEventListener("click", async () => { + let tagBar = note.querySelector('.tag-bar'); + while (tagBar.firstChild) { + tagBar.removeChild(tagBar.firstChild); + } + notes[note.id].tags = []; + await saveNotes(); + + customMenu.style.display = "none"; + }); + + document.getElementById("rem").addEventListener("mouseover", function() { + tagMenu.style.display = "none"; + }); + + document.getElementById("addtofolder").addEventListener("mouseover", function(event) { + const tagInputs = document.querySelectorAll('.tag-input'); + + tagMenu.innerHTML = ''; + + tagInputs.forEach(input => { + const menuItem = document.createElement('div'); + menuItem.className = "menu-item"; + menuItem.textContent = input.textContent; + + menuItem.addEventListener("click", async () => { + let tagBar = note.querySelector('.tag-bar'); + const tagElement = document.createElement("div"); + tagElement.className = "note-tag"; + tagElement.textContent = menuItem.textContent; + + while (tagBar.firstChild) { + tagBar.removeChild(tagBar.firstChild); + } + tagBar.appendChild(tagElement); + + notes[note.id].tags = []; + notes[note.id].tags.push(menuItem.textContent); + await saveNotes(); + }); + + tagMenu.appendChild(menuItem); + }); + + const customMenuRect = customMenu.getBoundingClientRect(); + tagMenu.style.left = `${customMenuRect.right + 10}px`; + tagMenu.style.top = `${customMenuRect.top}px`; + tagMenu.style.display = "block"; + }); + }); +} + + + + + + + + + + + + +titleInput.addEventListener("keydown", evt => { + if (evt.ctrlKey && evt.key === "Enter") { + evt.preventDefault(); + addNote(""); // that was easy + } +}); +infoInput.addEventListener("keydown", evt => { + if (evt.ctrlKey && evt.key === "Enter") { + evt.preventDefault(); + addNote(""); // that was easy + } +}); + +const customMenu = document.createElement("div"); +customMenu.className = "custom-context-menu"; + +customMenu.innerHTML = ` +
+ +`; + +document.body.appendChild(customMenu); + +const tagMenu = document.createElement('div'); +tagMenu.className = "custom-context-menu"; +tagMenu.style.display = "none"; + +tagMenu.addEventListener("mouseleave", () => { + tagMenu.style.display = "none"; +}); + +document.body.appendChild(tagMenu); + +document.addEventListener("click", function () { + customMenu.style.display = "none"; + tagMenu.style.display = "none"; +}); + +document.addEventListener("contextmenu", () => { + tagMenu.style.display = "none"; +}); \ No newline at end of file diff --git a/newjs/_settings_.js b/newjs/_settings_.js new file mode 100644 index 0000000..cb9dc90 --- /dev/null +++ b/newjs/_settings_.js @@ -0,0 +1,90 @@ +// Get references to elements +const settingsMenu = document.getElementById("settingsMenu"); +const sortMenu = document.getElementById("sortMenu"); + +// Comfirm menu items +const deleteConfirmModal = document.getElementById("deleteConfirmModal"); +const confirmDeleteNotes = document.getElementById("confirmDeleteNotes"); +const cancelDeleteNotes = document.getElementById("cancelDeleteNotes"); + +const resetConfirmModal = document.getElementById("resetConfirmModal"); +const confirmResetNotes = document.getElementById("confirmResetNotes"); +const cancelResetNotes = document.getElementById("cancelResetNotes"); + +// Download notes +const downloadButton = document.getElementById("downloadButton"); + +//delete confirm stuff +const delall = document.getElementById("delall"); +delall.addEventListener("click", () => { + deleteConfirmModal.showModal(); +}); + +confirmDeleteNotes.addEventListener("click", () => { + deleteAllNotes(); + deleteConfirmModal.close(); +}); + +cancelDeleteNotes.addEventListener("click", () => { + deleteConfirmModal.close(); +}); + +deleteConfirmModal.addEventListener("click", function(event) { + const modalContent = document.getElementById("delall"); + if (!modalContent.contains(event.target)) { + deleteConfirmModal.close(); + } +}); + +//reset confirm stuff +const resetSettings = document.getElementById("resetSettings"); +resetSettings.addEventListener("click", () => { + resetConfirmModal.showModal(); +}); + +confirmResetNotes.addEventListener("click", function() { + Object.assign(settings, defaultSettings); + saveSettings(); + loadSettings(); + resetConfirmModal.close(); +}); + +cancelResetNotes.addEventListener("click", () => { + resetConfirmModal.close(); +}); + +resetConfirmModal.addEventListener("click", function(event) { + const modalContent = document.getElementById("resetSettings"); + if (!modalContent.contains(event.target)) { + resetConfirmModal.close(); + } +}); + +chrome.runtime.sendMessage({command: "getTheme"}, theme => { + themeDropdown.value = theme; + themeDropdown.addEventListener("change", evt => { + const selectedTheme = evt.target.value; + console.log(`${selectedTheme} theme selected`); + chrome.runtime.sendMessage({command: "setTheme", data: {theme: selectedTheme}}); + }); +}); + +downloadButton.addEventListener("click", () => { + const folders = JSON.parse(localStorage.getItem("folders") || "[]"); + const data = { + folders: folders, + notes: notes + }; + const blob = new Blob([JSON.stringify(data, null, 2)], { + type: "application/json" + }); + + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "notes.json"; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); +}); \ No newline at end of file diff --git a/newjs/events.js b/newjs/events.js new file mode 100644 index 0000000..28ca363 --- /dev/null +++ b/newjs/events.js @@ -0,0 +1,21 @@ +const events = { + get: { + getData: "", + getNotes: "", + getStructure: "" + }, + + update: { + addNote: "", + updateNote: "", + deleteNote: "", + deleteAllNotes: "" + }, + + ui: { + addNoteUI: "", + deleteNoteUI: "", + deleteAllNotesUI: "", + UpdateUI: "" + } +} \ No newline at end of file diff --git a/newjs/themes.js b/newjs/themes.js new file mode 100644 index 0000000..9c0a9d6 --- /dev/null +++ b/newjs/themes.js @@ -0,0 +1,129 @@ +const unimplementedColor = "#F00BA7"; +const themes = { + legacy: { + text: "#000000", + placeholder: "#747474", + background: "#97BCC7", + foreground: "#F2F1EF", + codeblocks: "#CFCFCF", + hover: "#135473", + click: "#053D57", + border: "#053D57", + button: "#006884", + buttonText: "#F2F1EF", + format: "#D9D9D9", + formatText: "#000000", + formatHover: "#C4C4C4", + formatClick: "#B0B0B0", + submenuHover: "#D9D9D9", + submenuClick: "#C4C4C4" + }, + light: { + text: "#000000", + placeholder: "#A0A0A0", + background: "#FFFFFF", + foreground: "#FFFFFF", + codeblocks: "#E0E0E0", + hover: "#DFDFDF", + click: "#C7C7C7", + border: "#C8C8C8", + button: "#006884", + buttonText: "#F2F1EF", + format: "#EDEDED", + formatText: "#000000", + formatHover: "#D9D9D9", + formatClick: "#C4C4C4", + submenuHover: "#DFDFDF", + submenuClick: "#CCCCCC" + }, + dark: { + text: "#E6E6E6", + placeholder: "#A0A0A0", + background: "#181818", + foreground: "#2A2A2A", + codeblocks: "#3A3A3A", + hover: "#444444", + click: "#333333", + border: "#383838", + button: "#505050", + buttonText: "#E0E0E0", + format: "#505050", + formatText: "#E0E0E0", + formatHover: "#464646", + formatClick: "#3C3C3C", + submenuHover: "#404040", + submenuClick: "#363636" + }, + matcha: { + text: "#5A4632", + placeholder: "#84715B", + background: "#EDE3C9", + foreground: "#FFF8E5", + codeblocks: "#DAC3A3", + hover: "#8A9A5B", + click: "#7A8B4B", + border: "#A98467", + button: "#A0B762", + buttonText: "#FFF8E5", + format: "#A0B762", + formatText: "#FFF8E5", + formatHover: "#8FA456", + formatClick: "#7F934D", + submenuHover: "#EDE3C9", + submenuClick: "#D6CBAF" + }, + nebula: { + text: "#e2dbf0", + placeholder: "#A693B0", + background: "#121022", + foreground: "#282143", + codeblocks: "#4A3B6A", + hover: "#66001d", + click: "#4d0016", + border: "#5B4B8A", + button: "#99002b", + buttonText: "#DDD1E3", + format: "#99002b", + formatText: "#DDD1E3", + formatHover: "#870026", + formatClick: "#7A001F", + submenuHover: "#5B4B8A", + submenuClick: "#4F4178" + } +} + +// initialize to correct theme on load +chrome.runtime.sendMessage({command: "getTheme"}, theme => loadTheme(theme)); + +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + const { command, data } = request; + + if (command === "setThemeUI") { + console.log(`Loading theme ${data.theme}`); + loadTheme(data.theme); + } +}); + +function loadTheme(themeName) { + const des = document.body.style; + let selectedTheme = themes[themeName]; + des.setProperty("--theme-text", selectedTheme.text || unimplementedColor) + des.setProperty("--theme-placeholder", selectedTheme.placeholder || unimplementedColor); + des.setProperty("--theme-background", selectedTheme.background || unimplementedColor); + des.setProperty("--theme-foreground", selectedTheme.foreground || unimplementedColor); + des.setProperty("--theme-codeblocks", selectedTheme.codeblocks || unimplementedColor); + des.setProperty("--theme-hover", selectedTheme.hover || unimplementedColor); + des.setProperty("--theme-click", selectedTheme.click || unimplementedColor); + des.setProperty("--theme-border", selectedTheme.border || unimplementedColor); + des.setProperty("--theme-button", selectedTheme.button || unimplementedColor); + des.setProperty("--theme-buttonText", selectedTheme.buttonText || unimplementedColor); + des.setProperty("--theme-format", selectedTheme.format || unimplementedColor); + des.setProperty("--theme-formatText", selectedTheme.formatText || unimplementedColor); + des.setProperty("--theme-formatHover", selectedTheme.formatHover || unimplementedColor); + des.setProperty("--theme-formatClick", selectedTheme.formatClick || unimplementedColor); + des.setProperty("--theme-submenuHover", selectedTheme.submenuHover || unimplementedColor); + des.setProperty("--theme-submenuClick", selectedTheme.submenuClick || unimplementedColor); +} + + + diff --git a/pages/main.css b/pages/main.css new file mode 100644 index 0000000..5fadec1 --- /dev/null +++ b/pages/main.css @@ -0,0 +1,216 @@ +*, *::before, *::after { + box-sizing: border-box; +} + +#draftArea, #noteEditor[open] { + display: flex; + flex-wrap: wrap; + justify-content: end; + gap: 5px; + padding: 5px; + background-color: var(--theme-foreground); + justify-content: end; +} + +#noteEditor { + border-color: black; +} + +.title { + font-weight: bold; +} + +.body { + border-radius: 0px; +} + +.cuteBorder { + border: 2px solid; + border-color: var(--theme-border); + border-radius: 10px; +} + +.body { + min-height: 50px; +} + +[contenteditable="plaintext-only"] { + padding: 5px; + user-select: all; +} + +[contenteditable="plaintext-only"]:empty:before{ + content:attr(placeholder); + color:gray; + font-style:italic; +} + +#container { + display: flex; + gap: 10px; + margin-top: 10px; + flex-wrap: wrap; +} + +.del { + position: absolute; + top: -7px; + right: -7px; + border-radius: 50%; + border: 0px; + display: none; + text-align: center; + width: 25px; + height: 25px; + background-color: var(--theme-button); + color: var(--theme-buttonText); + background-color: var(--theme-button); + color: var(--theme-buttonText); + box-shadow: 0px 3px 4px 0px rgba(0, 0, 0, 0.3); +} + +.del:hover { + background-color: var(--theme-hover); +} + +.del:active { + background-color: var(--theme-click); +} + +.note { + width: 100%; + padding: 10px; + background-color: var(--theme-foreground); + position: relative; + border-radius: 10px; + height: 100%; + outline: 2px solid var(--theme-border); + box-shadow: 0px 3px 4px 0px rgba(0, 0, 0, 0.3); + color: var(--theme-text); + transition: transform .2s ease-in-out; +} + +.note:hover { + transform: scale(1.01); +} + +.note-title { + border-radius: 10px 10px 0px 0px; + color: var(--theme-text); + margin-bottom: 5px; + font-weight: bold; + overflow-x: hidden; +} + +#settingsButton { + text-align: center; + vertical-align: middle; + width: 34px; + height: 34px; + font-size: 20px; + border-radius: 10px; + border: none; +} + +.flex { + display: flex; +} + +.row { + display: flex; + flex-direction: row; + gap: 10px; + margin-bottom: 10px; + align-items: center; +} + +.column { + display: flex; + flex-direction: column; + gap: 10px; +} + + + +.none { + display: none !important; +} + +.topButton { + border-radius: 5px; + border: none; + padding: 4px; +} + +#add { + background-color: var(--theme-button); + color: var(--theme-buttonText); +} + +#erase { + background-color: var(--theme-format); + color: var(--theme-formatText); +} + + + +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + z-index: 2; + /* Make sure it's on top */ +} + +.topInput { + min-width: 100px; + padding: 5px; + width: 100%; + background-color: var(--theme-foreground); + color: var(--theme-text); + border: none; +} +.topInput::placeholder { + color: var(--theme-placeholder); +} +.topInput:focus { + border: 1px solid red; +} + +.time-text { + color: var(--theme-text); + text-align: end; + width: 100%; + margin-right: 10px; +} + + + +#search { + flex: 1; + border-radius: 10px; + height: 34px; + border: 2px solid; + border-color: var(--theme-border); + /*box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.3);*/ +} + +#search:hover { + background-color: var(--theme-submenuHover); +} + +#search:active { + background-color: var(--theme-submenuClick); +} + +#search:focus { + border: 2px solid var(--theme-border); + outline: 0px; +} + +#search::-webkit-calendar-picker-indicator { + display: none !important; +} \ No newline at end of file diff --git a/pages/main.html b/pages/main.html new file mode 100644 index 0000000..b721689 --- /dev/null +++ b/pages/main.html @@ -0,0 +1,77 @@ + + + + + + + + + + + +