From 26840ba9a96ed795b878f22e3cf3297343a4af15 Mon Sep 17 00:00:00 2001 From: Tom Casavant Date: Wed, 23 Oct 2024 20:07:30 -0400 Subject: [PATCH] Date Customization, Word Drop option, cleaned up styling (#15) * Added date customization, word drop options, adjusted div styling a bit * Added README and cleaned up settings window --- README.md | 12 ++++++++++ background.js | 19 ++++++++-------- content.js | 63 ++++++++++++++++++++++++++++++++++++++++++++------- manifest.json | 2 +- options.js | 19 +++++++++++++--- settings.html | 28 ++++++++++++++++++++++- styles.css | 42 +++++++++++++++++++++++++++------- 7 files changed, 155 insertions(+), 30 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..2fef5ea --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# DuckDuckSocial +A simple Firefox extension that appends Mastodon search result cards to a duckduckgo search result page. + +https://addons.mozilla.org/en-US/firefox/addon/duckducksocial/ + + +## Features +- Appends Mastodon search result cards to a duckduckgo search result page. +- Shows images and videos in the cards. +- Customizable Date and Time format. +- OAuth authentication with Mastodon API (currently web only) +- Word Filter (optionally automatically remove terms from search term) diff --git a/background.js b/background.js index 3f712d4..2ee990b 100644 --- a/background.js +++ b/background.js @@ -1,8 +1,10 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.action === 'searchMastodon') { + console.log('Searching Mastodon for:', message.searchTerm); // Load saved settings from browser.storage.local - browser.storage.local.get(['client_id', 'client_secret', 'access_token', 'apiKey', 'domain', 'numPosts']) - .then(({ client_id, client_secret, access_token, apiKey, domain, numPosts = 5 }) => { + browser.storage.local.get(['client_id', 'client_secret', 'access_token', 'apiKey', 'domain', 'dateType', 'dropWords', 'numPosts']) + .then(({ client_id, client_secret, access_token, apiKey, domain, dateType, dropWords, numPosts = 5 }) => { + console.log('Loaded settings:', client_id, client_secret, access_token, apiKey, domain, dateType, dropWords, numPosts); if ((!access_token && !apiKey) || !domain) { sendResponse({ success: false, error: 'Missing access token, API key, or domain.' }); return; @@ -35,9 +37,9 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => { return true; } else if (message.action === 'getSettings') { - browser.storage.local.get(['domain', 'numPosts']) - .then(({ domain, numPosts = 5 }) => { - sendResponse({ domain, numPosts }); + browser.storage.local.get(['domain', 'numPosts', 'dropWords', 'dateType']) + .then(({ domain, numPosts = 5, dropWords, dateType }) => { + sendResponse({ domain, numPosts, dropWords, dateType }); }) .catch(error => { sendResponse({ success: false, error: 'Failed to retrieve settings.' }); @@ -50,7 +52,7 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => { // Register the app and start the OAuth flow registerApp(domain).then(appRegistration => { - console.log('App registration successful:', appRegistration); + console.log('App registration successful'); appRegistrate = appRegistration; return launchOAuthFlow(appRegistration, domain); }).then(redirectUrl => { @@ -146,9 +148,8 @@ async function exchangeCodeForToken(code, domain, appRegistration) { // Validates the redirect URL async function validate(redirectUrl, domain, appRegistration) { try { - console.log('Redirect URL:', redirectUrl); - if (redirectUrl) { + console.log('Generated Redirect URL'); const code = new URL(redirectUrl).searchParams.get('code'); if (code) { @@ -164,4 +165,4 @@ async function validate(redirectUrl, domain, appRegistration) { console.error('Validation error:', error); throw error; } -} +} \ No newline at end of file diff --git a/content.js b/content.js index 7463e85..5b8283e 100644 --- a/content.js +++ b/content.js @@ -1,16 +1,30 @@ // Extract the search term from DuckDuckGo's URL const urlParams = new URLSearchParams(window.location.search); const searchTerm = urlParams.get('q'); - +console.log("Search Term: ", searchTerm); if (searchTerm) { // Request the settings from the background script browser.runtime.sendMessage({ action: 'getSettings' }) .then(settings => { + console.log("Settings: ", settings); const mastodonDomain = settings.domain; const numPosts = settings.numPosts; - - - browser.runtime.sendMessage({ action: 'searchMastodon', searchTerm: searchTerm }) + const dropWords = settings.dropWords; + const dateType = settings.dateType; + + console.log("All settings: ", settings); + console.log("Mastodon Domain: ", mastodonDomain); + console.log("Number of Posts: ", numPosts); + console.log("Drop Words: ", dropWords); + console.log("Date Type: ", dateType); + + // Parse dropWords as a list, then drop words from the search term that match any of the words in the list + const dropWordsList = dropWords.split(',').map(word => word.trim()); + + // Using Regex instead of a simple split in case someone wants to include the space or have phrases dropped + const replacedSearchTerm = searchTerm.replace(new RegExp(dropWordsList.join('|'), 'gi'), ''); + console.log("Replaced Search Term: ", replacedSearchTerm); + browser.runtime.sendMessage({ action: 'searchMastodon', searchTerm: replacedSearchTerm }) .then(response => { console.log(response); if (response.success) { @@ -24,10 +38,17 @@ if (searchTerm) { var postDiv = document.createElement('div'); postDiv.className = 'duckducksocial-post'; + var usernameWrapper = document.createElement('div'); + usernameWrapper.className = 'duckducksocial-username-wrapper'; + var username = document.createElement('p'); username.textContent = `@${post.account.acct}`; - username.className = 'duckducksocial-username'; - + username.className = 'duckducksocial-username'; + // Add
separator between username and content + var hr = document.createElement('hr'); + hr.className = 'duckducksocial-hr'; + usernameWrapper.appendChild(username); + usernameWrapper.appendChild(hr); var sanitizedContent = DOMPurify.sanitize(post.content); var content = document.createElement('p'); @@ -37,14 +58,40 @@ if (searchTerm) { // Datetime var datetime = document.createElement('p'); - datetime.textContent = new Date(post.created_at).toLocaleString(); + // If the dateType is set to 'relative', use a relative date otherwise use an absolute date + if (dateType === 'relative') { + // Calculate time since post was created + var now = new Date(); + var created = new Date(post.created_at); + var diff = now - created; + var seconds = Math.floor(diff / 1000); + var minutes = Math.floor(seconds / 60); + var hours = Math.floor(minutes / 60); + var days = Math.floor(hours / 24); + var months = Math.floor(days / 30); + var years = Math.floor(months / 12); + // Adjust how date is displayed depending on how long ago the post was created + if (years > 0) { + datetime.textContent = years + 'y'; + } else if (days > 0) { + datetime.textContent = days + 'd'; + } else if (hours > 0) { + datetime.textContent = hours + 'h'; + } else if (minutes > 0) { + datetime.textContent = minutes + 'm'; + } else { + datetime.textContent = seconds + 's'; + } + } else { + datetime.textContent = new Date(post.created_at).toLocaleString(); + } datetime.className = 'duckducksocial-datetime'; var postHref = document.createElement('a'); postHref.href = `https://${mastodonDomain}/@${post.account.acct}/${post.id}`; postHref.appendChild(postDiv); - postDiv.appendChild(username); + postDiv.appendChild(usernameWrapper); postDiv.appendChild(content); if (post.media_attachments && post.media_attachments.length > 0) { var mediaContainer = document.createElement('div'); diff --git a/manifest.json b/manifest.json index d4c5245..2e9fd82 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "DuckDuckSocial", - "version": "0.0.5", + "version": "0.0.6", "description": "Adds a scrollable list of posts from the social web that match your DuckDuckGo search. Mobile support requires API key", "icons": { "512": "icons/border-512.png" diff --git a/options.js b/options.js index 050f2d6..07ed678 100644 --- a/options.js +++ b/options.js @@ -7,6 +7,12 @@ document.addEventListener('DOMContentLoaded', function() { const toggleButton = document.getElementById('toggleAdvancedSettings'); const advancedSettings = document.querySelector('.advanced-settings'); + // Advanced settings + dropWordsInput = document.getElementById('dropWords'); + + const dateConfig = document.getElementById('dateConfig'); + + // Load saved settings loadSettings(); @@ -29,6 +35,7 @@ document.addEventListener('DOMContentLoaded', function() { return; } + console.log('Connecting to Mastodon: ', domain); browser.runtime.sendMessage({ action: 'authorize', domain: domain @@ -48,11 +55,15 @@ document.addEventListener('DOMContentLoaded', function() { const domain = domainInput.value; const numPosts = numPostsInput.value; const apiKey = apiKeyInput.value; + const dropWords = dropWordsInput.value; + const dateType = dateConfig.value; browser.storage.local.set({ domain: domain, numPosts: numPosts, - apiKey: apiKey // Save the API key as well + apiKey: apiKey, + dropWords: dropWords, + dateType: dateType }).then(() => { updateMessage('Settings saved successfully!'); }).catch(error => { @@ -62,14 +73,16 @@ document.addEventListener('DOMContentLoaded', function() { } function loadSettings() { - browser.storage.local.get(['domain', 'numPosts', 'apiKey']) - .then(({ domain, numPosts = 5, apiKey }) => { + browser.storage.local.get(['domain', 'numPosts', 'apiKey', 'dropWords', 'dateType']) + .then(({ domain, numPosts = 5, apiKey, dropWords, dateType }) => { if (domain) domainInput.value = domain; if (numPosts) numPostsInput.value = numPosts; if (apiKey) { apiKeyInput.value = apiKey; advancedSettingsDiv.style.display = 'block'; } + if (dropWords) dropWordsInput.value = dropWords; + if (dateType) dateConfig.value = dateType; }) .catch(error => { console.error('Failed to load settings:', error); diff --git a/settings.html b/settings.html index 52d3409..723d992 100644 --- a/settings.html +++ b/settings.html @@ -20,6 +20,21 @@ .advanced-settings { display: none; } + button { + padding: 10px 20px; + background-color: #007bff; + color: white; + border: none; + cursor: pointer; + } + + .dateConfigGroup { + display: flex; + justify-content: space-between; + } + + + @@ -30,11 +45,22 @@

DuckDuckSocial

- + +
+ + +
+ + + +
diff --git a/styles.css b/styles.css index 6c82b0e..14be44f 100644 --- a/styles.css +++ b/styles.css @@ -36,12 +36,12 @@ border-radius: 8px; box-shadow: 0 2px 8px var(--shadow-light); height: 15em; - width: 12.5em; + width: 12.5em; background-color: var(--post-background-light); transition: transform 0.3s ease, box-shadow 0.3s ease; display: flex; flex-direction: column; - justify-content: space-between; + justify-content: flex-start; padding: 5px; overflow: hidden; } @@ -51,6 +51,11 @@ box-shadow: 0 4px 12px rgba(150, 150, 150, 0.5); } +.duckducksocial-username-wrapper { + position: relative; + height: 20px; +} + .duckducksocial-username { font-weight: bold; margin: 0; @@ -59,6 +64,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + line-height: 15px; } .duckducksocial-content { @@ -68,9 +74,16 @@ text-overflow: ellipsis; display: -webkit-box; -webkit-box-orient: vertical; - -webkit-line-clamp: 3; + -webkit-line-clamp: 3; /* Limits the content to 3 lines when there's media */ margin: 3px 0; - flex-grow: 1; + flex-grow: 1; /* Ensures the content grows to fill available space */ + max-height: 4.5em; /* Adjust this to prevent cut-off */ +} + +.duckducksocial-post:not(.has-media) .duckducksocial-content { + -webkit-line-clamp: 10; /* Remove the line clamp if there's no media */ + max-height: none; + flex-grow: 0.3; } .duckducksocial-datetime { @@ -79,8 +92,10 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + position: absolute; + bottom: 5px; + right: 5px; margin: 0; - align-self: flex-end; } .duckducksocial-media-container { @@ -91,7 +106,7 @@ justify-content: center; align-items: center; flex-shrink: 0; - margin: 5px 0; + margin: 18px 0; } .duckducksocial-media-image, @@ -102,10 +117,17 @@ display: block; } +.duckducksocial-hr { + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin: 0; + height: 0.5px; +} /* Dark mode styles */ @media (prefers-color-scheme: dark) { - .duckducksocial-results { border: 1px solid var(--border-color-dark); background-color: var(--background-color-dark); @@ -128,6 +150,10 @@ .duckducksocial-datetime { color: var(--datetime-text-color-dark); } + + .duckducksocial-hr { + background-color: #555; + } } /* Remove underline from all links and nested elements */ @@ -135,7 +161,7 @@ text-decoration: none; } -.content a { +.duckducksocial-content a { color: lightblue; text-decoration-line: underline; }