diff --git a/submissions/snippetify/README.md b/submissions/snippetify/README.md new file mode 100644 index 00000000..41fe380c --- /dev/null +++ b/submissions/snippetify/README.md @@ -0,0 +1,7 @@ +# snippetify +snippetify is a minimalistic browser extension that lets you highlight text (by selecting & right clicking) on webpages in pastel colors and locally save snippets of exactly what you highlighted with links for future reference. it has an option to edit the heading of each snippet so you can locate your snippet at a glance. + +![image](https://github.com/user-attachments/assets/b0e25dde-d595-44f1-9361-9e783fe760b3) + + +[demo video here](https://hc-cdn.hel1.your-objectstorage.com/s/v3/4cf02f5a36d545e4c30c40afbdf24d6d88c9c763_browserbuddy-demo.mp4) diff --git a/submissions/snippetify/background.js b/submissions/snippetify/background.js new file mode 100644 index 00000000..94ac6bd4 --- /dev/null +++ b/submissions/snippetify/background.js @@ -0,0 +1,50 @@ +chrome.runtime.onInstalled.addListener(() => { + chrome.contextMenus.create({ + id: "saveSnippet", + title: "highlight text to save", + contexts: ["selection"] + }); + + chrome.storage.local.get("snippets", function (data) { + chrome.storage.local.set({ snippets: data.snippets || [] }); + }); + }); + + chrome.contextMenus.onClicked.addListener((info, tab) => { + if (info.menuItemId === "saveSnippet") { + chrome.scripting.executeScript({ + target: { tabId: tab.id }, + function: highlightAndSave, + args: [info.selectionText, tab.url, tab.title] + }); + } + }); + + function highlightAndSave(selectedText, url, pageTitle) { + let range = window.getSelection().getRangeAt(0); + let span = document.createElement("span"); + + let pastelColors = ["#FFDDEE", "#DDF0FF", "#E8DFFF", "#FFF4C2", "#DAF0CC", "#F0D9CC"]; + let randomColor = pastelColors[Math.floor(Math.random() * pastelColors.length)]; + + span.textContent = selectedText; + span.style.backgroundColor = randomColor; + span.style.padding = "2px 4px"; + span.style.borderRadius = "4px"; + span.style.fontWeight = "500"; + + range.deleteContents(); + range.insertNode(span); + + chrome.storage.local.get({ snippets: [] }, function (data) { + let snippets = data.snippets; + snippets.push({ + text: selectedText, + url: url, + color: randomColor, + heading: pageTitle || "New Snippet" + }); + chrome.storage.local.set({ snippets: snippets }); + }); + } + \ No newline at end of file diff --git a/submissions/snippetify/content.js b/submissions/snippetify/content.js new file mode 100644 index 00000000..21aa0e40 --- /dev/null +++ b/submissions/snippetify/content.js @@ -0,0 +1,9 @@ +chrome.storage.local.get("snippets", function (data) { + (data.snippets || []).forEach((snippet) => { + document.body.innerHTML = document.body.innerHTML.replace( + new RegExp(snippet.text, "g"), + `${snippet.text}` + ); + }); + }); + \ No newline at end of file diff --git a/submissions/snippetify/icon.png b/submissions/snippetify/icon.png new file mode 100644 index 00000000..20f77e00 Binary files /dev/null and b/submissions/snippetify/icon.png differ diff --git a/submissions/snippetify/manifest.json b/submissions/snippetify/manifest.json new file mode 100644 index 00000000..ef3c0d48 --- /dev/null +++ b/submissions/snippetify/manifest.json @@ -0,0 +1,20 @@ +{ + "manifest_version": 3, + "name": "snippetify", + "version": "1.2", + "permissions": ["storage", "activeTab", "contextMenus", "scripting"], + "background": { + "service_worker": "background.js" + }, + "action": { + "default_popup": "popup.html", + "default_icon": "icon.png" + }, + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"] + } + ] + } + \ No newline at end of file diff --git a/submissions/snippetify/poppins.ttf b/submissions/snippetify/poppins.ttf new file mode 100644 index 00000000..74c726e3 Binary files /dev/null and b/submissions/snippetify/poppins.ttf differ diff --git a/submissions/snippetify/popup.css b/submissions/snippetify/popup.css new file mode 100644 index 00000000..76731c21 --- /dev/null +++ b/submissions/snippetify/popup.css @@ -0,0 +1,11 @@ +#popup-container { + width: 300px; + height: 400px; + padding: 10px; + margin: 0; + background-color: white; + font-family: "Poppins", sans-serif; + border-radius: 8px; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); + overflow: auto; + } \ No newline at end of file diff --git a/submissions/snippetify/popup.html b/submissions/snippetify/popup.html new file mode 100644 index 00000000..0df0eec7 --- /dev/null +++ b/submissions/snippetify/popup.html @@ -0,0 +1,17 @@ + + + + + + snippetify - saved snippets + + + + + + + + diff --git a/submissions/snippetify/popup.js b/submissions/snippetify/popup.js new file mode 100644 index 00000000..d8658185 --- /dev/null +++ b/submissions/snippetify/popup.js @@ -0,0 +1,90 @@ +document.addEventListener("DOMContentLoaded", function () { + let snippetList = document.getElementById("snippetList"); + + chrome.storage.local.get("snippets", function (data) { + snippetList.innerHTML = ""; + + (data.snippets || []).forEach((snippet, index) => { + let li = document.createElement("li"); + li.style.backgroundColor = snippet.color; + li.classList.add("snippet"); + + let headingInput = document.createElement("input"); + headingInput.type = "text"; + headingInput.value = snippet.heading; + headingInput.classList.add("heading-input"); + + headingInput.addEventListener("change", function () { + chrome.storage.local.get("snippets", function (data) { + let snippets = data.snippets || []; + snippets[index].heading = headingInput.value; + chrome.storage.local.set({ snippets: snippets }); + }); + }); + + // highlighted text container + let textContainer = document.createElement("div"); + textContainer.classList.add("text-container"); + + let text = document.createElement("span"); + text.classList.add("highlighted-text"); + + let fullText = snippet.text; + let truncatedText = fullText.length > 150 ? fullText.substring(0, 140) + "..." : fullText; + text.innerHTML = truncatedText; + + let readMore = document.createElement("a"); + readMore.textContent = " read more"; + readMore.href = "#"; + readMore.classList.add("read-more"); + readMore.style.display = fullText.length > 150 ? "inline" : "none"; + + let isExpanded = false; + readMore.addEventListener("click", function (event) { + event.preventDefault(); + isExpanded = !isExpanded; + text.innerHTML = isExpanded ? fullText : truncatedText; + readMore.textContent = isExpanded ? " read less" : " read more"; + }); + + textContainer.appendChild(text); + // textContainer.appendChild(document.createElement("br")); + textContainer.appendChild(readMore); + + // button container (stacked vertically) + let buttonContainer = document.createElement("div"); + buttonContainer.classList.add("button-container"); + + let link = document.createElement("a"); + link.href = snippet.url; + link.textContent = "🔗"; + link.target = "_blank"; + link.classList.add("icon-button"); + + let removeBtn = document.createElement("button"); + removeBtn.textContent = "×"; + removeBtn.classList.add("delete-btn"); + removeBtn.onclick = function () { + chrome.storage.local.get("snippets", function (data) { + let snippets = data.snippets || []; + snippets.splice(index, 1); + chrome.storage.local.set({ snippets: snippets }, function () { + location.reload(); + }); + }); + }; + + buttonContainer.appendChild(link); + buttonContainer.appendChild(removeBtn); + + let snippetRow = document.createElement("div"); + snippetRow.classList.add("snippet-row"); + snippetRow.appendChild(textContainer); + snippetRow.appendChild(buttonContainer); + + li.appendChild(headingInput); + li.appendChild(snippetRow); + snippetList.appendChild(li); + }); + }); +}); diff --git a/submissions/snippetify/styles.css b/submissions/snippetify/styles.css new file mode 100644 index 00000000..fb9ebf33 --- /dev/null +++ b/submissions/snippetify/styles.css @@ -0,0 +1,121 @@ +@font-face { + font-family: "Poppins"; + src: url("poppins.ttf") format("truetype"); +} + +body { + font-family: "Poppins", sans-serif; + background: #dad7cd; + text-align: left; + padding: 20px; + border-radius: 10px; +} + +.container { + background: white; + border-radius: 12px; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); + padding: 15px; +} + +h2 { + font-size: 18px; + color: #333; + margin-bottom: 10px; +} + +ul { + list-style: none; + padding: 0; + margin: 0; +} + +.snippet { + font-weight: 500; + border-radius: 6px; + padding: 8px; + margin-bottom: 8px; + display: flex; + flex-direction: column; + align-items: flex-start; + position: relative; +} + +.heading-input { + font-family: "Poppins", sans-serif; + font-size: 14px; + width: 100%; + border: none; + background: transparent; + font-weight: bold; + text-decoration: underline; + margin-bottom: 2px; + padding: 2px; +} + +.heading-input:focus { + outline: none; + border-bottom: 1px solid #aaa; +} + +.snippet-row { + display: flex; + justify-content: space-between; + width: 100%; + align-items: flex-start; +} + +.text-container { + max-width: 75%; + display: flex; + flex-direction: column; +} + +.highlighted-text { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + padding: 2px 6px; + border-radius: 4px; + font-weight: normal; + text-overflow: ellipsis; +} + +.read-more { + font-size: 12px; + cursor: pointer; + font-weight: bold; + white-space: nowrap; + text-decoration: underline; + color: #555; +} + +.read-more:hover { + text-decoration: underline; + color: #000; +} + +.button-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; +} + +.icon-button { + text-decoration: none; + font-size: 16px; +} + +.delete-btn { + background: transparent; + border: none; + font-size: 16px; + cursor: pointer; + color: #777; +} + +.delete-btn:hover { + color: red; +}