Skip to content

Commit

Permalink
Date Customization, Word Drop option, cleaned up styling (#15)
Browse files Browse the repository at this point in the history
* Added date customization, word drop options, adjusted div styling a bit

* Added README and cleaned up settings window
  • Loading branch information
TomCasavant authored Oct 24, 2024
1 parent 399e2f2 commit 26840ba
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 30 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
19 changes: 10 additions & 9 deletions background.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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.' });
Expand All @@ -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 => {
Expand Down Expand Up @@ -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) {
Expand All @@ -164,4 +165,4 @@ async function validate(redirectUrl, domain, appRegistration) {
console.error('Validation error:', error);
throw error;
}
}
}
63 changes: 55 additions & 8 deletions content.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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 <hr> 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');
Expand All @@ -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');
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
19 changes: 16 additions & 3 deletions options.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -29,6 +35,7 @@ document.addEventListener('DOMContentLoaded', function() {
return;
}

console.log('Connecting to Mastodon: ', domain);
browser.runtime.sendMessage({
action: 'authorize',
domain: domain
Expand All @@ -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 => {
Expand All @@ -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);
Expand Down
28 changes: 27 additions & 1 deletion settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}



</style>
</head>
<body>
Expand All @@ -30,11 +45,22 @@ <h1>DuckDuckSocial</h1>

<label for="numPosts">Number of posts to display:</label>
<input type="number" id="numPosts" name="numPosts" min="1" value="5" />


<div class="dateConfigGroup">
<label for="dateConfig">Date Configuration:</label>
<select id="dateConfig" name="dateConfig">
<option value="absolute">Absolute</option>
<option value="relative">Relative</option>
</select>
</div>

<div class="advanced-settings">
<label for="apiKey">API Key:</label>
<input type="password" id="apiKey" name="apiKey" placeholder="Enter your API key">

<!-- setting for list of words that will be dropped before performing search -->
<label for="dropWords">Words to drop:</label>
<input type="text" id="dropWords" name="dropWords" placeholder="Enter words separated by commas">
</div>

<button type="submit">Save Settings</button>
Expand Down
42 changes: 34 additions & 8 deletions styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
Expand All @@ -59,6 +64,7 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 15px;
}

.duckducksocial-content {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -91,7 +106,7 @@
justify-content: center;
align-items: center;
flex-shrink: 0;
margin: 5px 0;
margin: 18px 0;
}

.duckducksocial-media-image,
Expand All @@ -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);
Expand All @@ -128,14 +150,18 @@
.duckducksocial-datetime {
color: var(--datetime-text-color-dark);
}

.duckducksocial-hr {
background-color: #555;
}
}

/* Remove underline from all links and nested elements */
.duckducksocial-results a {
text-decoration: none;
}

.content a {
.duckducksocial-content a {
color: lightblue;
text-decoration-line: underline;
}

0 comments on commit 26840ba

Please sign in to comment.