Skip to content

Commit

Permalink
Merge branch 'improve-browser-extension'
Browse files Browse the repository at this point in the history
  • Loading branch information
Jenselme committed Jan 26, 2025
2 parents 5e44f30 + 15115af commit 105c058
Show file tree
Hide file tree
Showing 38 changed files with 494 additions and 156 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@

## Unreleased

## 25.01.1

- Correct footer background on dark mode.
- Prevent articles to be read on scroll before initial scroll to top on page load.
- Force re-authentication before managing tokens.
- Allow users to change their passwords.
- Browser extension:
- Ask before resetting options.
- Can open articles and feeds details.
- Prevent extension popup to become too wide.
- Can test the options on the settings page.
- Support tag hierarchy.

## 24.12.6

Expand Down
2 changes: 1 addition & 1 deletion browser-extension/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "legadilo-browser-extension",
"version": "0.1.0",
"version": "0.2.0",
"dependencies": {
"bootstrap": "^5.3.3",
"bootstrap5-tags": "^1.7.5",
Expand Down
5 changes: 5 additions & 0 deletions browser-extension/src/action.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
padding: 0.5rem;
}

#article-container,
#feed-container {
max-width: 300px;
}

#action-selector-container {
max-width: 300px;
padding: 0.5rem;
Expand Down
12 changes: 12 additions & 0 deletions browser-extension/src/action.html
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@
src="./vendor/bs-icons/bookmark-fill.svg"
alt="Unmark as for later" />
</button>
<a id="open-article-details"
class="btn btn-outline-secondary btn-sm"
role="button"
href="">
<img class="bi" src="./vendor/bs-icons/eye.svg" alt="Open article details" />
</a>
</div>
</div>
<div id="feed-container">
Expand Down Expand Up @@ -143,6 +149,12 @@ <h2 id="feed-title"></h2>
</select>
</div>
<button class="btn btn-primary btn-sm">Update</button>
<a id="open-feed-details"
class="btn btn-outline-secondary btn-sm"
role="button"
href="">
<img class="bi" src="./vendor/bs-icons/eye.svg" alt="Open feed" />
</a>
</form>
</div>
<div id="error-container" class="alert alert-danger">
Expand Down
39 changes: 27 additions & 12 deletions browser-extension/src/action.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,19 +124,35 @@ const hideLoader = () => {
document.querySelector("#loading-indicator-container").style.display = "none";
};

const createTagInstance = (element, tags, selectedTags) => {
const tagsHierarchy = tags.reduce((acc, tag) => ({ ...acc, [tag.slug]: tag.sub_tags }), {});

return Tags.init(element, {
allowNew: true,
allowClear: true,
items: tags.reduce((acc, tag) => ({ ...acc, [tag.slug]: tag.title }), {}),
selected: selectedTags.map((tag) => tag.slug),
onSelectItem(item, instance) {
if (!Array.isArray(tagsHierarchy[item.value])) {
return;
}

const alreadyAddedItems = instance.getSelectedValues();
tagsHierarchy[item.value]
.filter((tag) => !alreadyAddedItems.includes(tag.slug))
.forEach((tag) => instance.addItem(tag.title, tag.slug));
},
});
};

const displayArticle = (article, tags) => {
document.querySelector("#article-container").style.display = "block";

document.querySelector("#saved-article-title").value = article.title;
document.querySelector("#saved-article-reading-time").value = article.reading_time;

if (articleTagsInstance === null) {
articleTagsInstance = Tags.init("#saved-article-tags", {
allowNew: true,
allowClear: true,
items: tags.reduce((acc, tag) => ({ ...acc, [tag.slug]: tag.title }), {}),
selected: article.tags.map((tag) => tag.slug),
});
articleTagsInstance = createTagInstance("#saved-article-tags", tags, article.tags);
}

if (article.is_read) {
Expand All @@ -160,6 +176,8 @@ const displayArticle = (article, tags) => {
document.querySelector("#mark-article-as-for-later").style.display = "block";
document.querySelector("#unmark-article-as-for-later").style.display = "none";
}

document.querySelector("#open-article-details").href = article.details_url;
};

const hideArticle = () => {
Expand All @@ -173,6 +191,8 @@ const displayFeed = (feed, tags, categories) => {
document.querySelector("#feed-refresh-delay").value = feed.refresh_delay;
document.querySelector("#feed-article-retention-time").value = feed.article_retention_time;

document.querySelector("#open-feed-details").href = feed.details_url;

const categorySelector = document.querySelector("#feed-category");
// Clean all existing choices.
categorySelector.innerHTML = "";
Expand All @@ -189,12 +209,7 @@ const displayFeed = (feed, tags, categories) => {
categorySelector.value = feed.category ? feed.category.id : "";

if (feedTagsInstance === null) {
feedTagsInstance = Tags.init("#feed-tags", {
allowNew: true,
allowClear: true,
items: tags.reduce((acc, tag) => ({ ...acc, [tag.slug]: tag.title }), {}),
selected: feed.tags.map((tag) => tag.slug),
});
feedTagsInstance = createTagInstance("#feed-tags", tags, feed.tags);
}

document.querySelector("#update-feed-form").addEventListener("submit", (event) => {
Expand Down
21 changes: 21 additions & 0 deletions browser-extension/src/legadilo.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,27 @@ export const DEFAULT_OPTIONS = {
accessToken: "",
};

export const testCredentials = async ({ instanceUrl, userEmail, tokenId, tokenSecret }) => {
try {
const resp = await fetch(`${instanceUrl}/api/users/tokens/`, {
"Content-Type": "application/json",
method: "POST",
body: JSON.stringify({
email: userEmail,
application_token_uuid: tokenId,
application_token_secret: tokenSecret,
}),
});
await resp.json();

return resp.status === 200;
} catch (error) {
console.error(error);

return false;
}
};

export const saveArticle = async ({ link, title, content }) => {
if (!/^https?:\/\//.test(link)) {
throw new Error("Invalid url");
Expand Down
2 changes: 1 addition & 1 deletion browser-extension/src/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Legadilo",
"version": "0.1.0",
"version": "0.2.0",

"description": "Open the legadilo browser extension to easily subscribe to feeds & save articles in Legadilo",

Expand Down
21 changes: 18 additions & 3 deletions browser-extension/src/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<meta charset="utf-8" />
<title>Legadilo Options</title>
<link rel="stylesheet" type="text/css" href="./vendor/bootstrap.css" />
<script src="./vendor/bootstrap.js"></script>
</head>
<body>
<form id="options-form">
Expand Down Expand Up @@ -48,10 +47,26 @@
type="text"
required />
</div>
<button id="reset-options" class="btn btn-danger" type="button">Reset to default</button>
<button type="submit" class="btn btn-primary">Save</button>
<div class="d-flex justify-content-between">
<div>
<button id="test-options" class="btn btn-outline-secondary" type="button">Test</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
<div>
<button id="reset-options" class="btn btn-danger" type="button">Reset to default</button>
</div>
</div>
</form>
<p id="status"></p>
<dialog id="confirm-dialog">
<div class="modal-header">
<h2 class="modal-title">Do you confirm?</h2>
</div>
<button id="confirm-dialog-cancel-btn"
class="btn btn-outline-secondary"
type="button">No</button>
<button id="confirm-dialog-confirm-btn" class="btn btn-danger" type="button">Yes</button>
</dialog>
<script src="./options.js" type="module"></script>
</body>
</html>
60 changes: 56 additions & 4 deletions browser-extension/src/options.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DEFAULT_OPTIONS, loadOptions, storeOptions } from "./legadilo.js";
import { DEFAULT_OPTIONS, loadOptions, storeOptions, testCredentials } from "./legadilo.js";

/**
* @param event {SubmitEvent}
Expand All @@ -15,8 +15,12 @@ const saveOptions = async (event) => {
});

// Update status to let user know options were saved.
displayMessage("Options saved.");
};

const displayMessage = (text) => {
const status = document.getElementById("status");
status.textContent = "Options saved.";
status.textContent = text;
setTimeout(() => {
status.textContent = "";
}, 750);
Expand All @@ -35,10 +39,58 @@ const setOptions = (options = {}) => {
document.getElementById("token-secret").value = options.tokenSecret;
};

const resetOptions = () => {
setOptions(DEFAULT_OPTIONS);
const resetOptions = async () => {
if (await askConfirmation()) {
setOptions(DEFAULT_OPTIONS);
}
};

const testOptions = async () => {
const data = new FormData(document.getElementById("options-form"));

if (
await testCredentials({
instanceUrl: data.get("instance-url"),
userEmail: data.get("user-email"),
tokenId: data.get("token-id"),
tokenSecret: data.get("token-secret"),
})
) {
displayMessage("Instance URL and token are valid");
} else {
displayMessage("Failed to connect with supplied URL and tokens");
}
};

const askConfirmation = () => {
const confirmDialog = document.getElementById("confirm-dialog");

let resolveDeferred;
const deferred = new Promise((resolve) => (resolveDeferred = resolve));
const cancelBtn = document.getElementById("confirm-dialog-cancel-btn");
const confirmBtn = document.getElementById("confirm-dialog-confirm-btn");
const cancel = () => {
confirmDialog.close();
resolveDeferred(false);
cancelBtn.removeEventListener("click", cancel);
confirmBtn.removeEventListener("click", confirm);
};
const confirm = () => {
confirmDialog.close();
resolveDeferred(true);
cancelBtn.removeEventListener("click", cancel);
confirmBtn.removeEventListener("click", confirm);
};

cancelBtn.addEventListener("click", cancel);
confirmBtn.addEventListener("click", confirm);

confirmDialog.showModal();

return deferred;
};

document.addEventListener("DOMContentLoaded", restoreOptions);
document.getElementById("options-form").addEventListener("submit", saveOptions);
document.getElementById("reset-options").addEventListener("click", resetOptions);
document.getElementById("test-options").addEventListener("click", testOptions);
10 changes: 10 additions & 0 deletions browser-extension/src/vendor/bs-icons/eye.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions legadilo/feeds/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from asgiref.sync import sync_to_async
from django.db import IntegrityError, transaction
from django.shortcuts import aget_object_or_404
from django.urls import reverse
from ninja import ModelSchema, Router, Schema
from ninja.errors import ValidationError as NinjaValidationError
from ninja.pagination import paginate
Expand Down Expand Up @@ -61,6 +62,12 @@ class Meta:
class OutFeedSchema(ModelSchema):
category: OutFeedCategorySchema | None
tags: list[OutTagSchema]
details_url: str

@staticmethod
def resolve_details_url(obj, context) -> str:
url = reverse("feeds:feed_articles", kwargs={"feed_id": obj.id, "feed_slug": obj.slug})
return context["request"].build_absolute_uri(url)

class Meta:
model = Feed
Expand Down
4 changes: 2 additions & 2 deletions legadilo/feeds/models/feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ def update_feed(self, feed: Feed, feed_metadata: FeedData):
articles = [
article for article in feed_metadata.articles if article.link not in deleted_feed_links
]
created_articles = Article.objects.update_or_create_from_articles_list(
save_result = Article.objects.save_from_list_of_data(
feed.user,
articles,
feed.tags.all(),
Expand All @@ -298,7 +298,7 @@ def update_feed(self, feed: Feed, feed_metadata: FeedData):
feed=feed,
)
FeedArticle.objects.bulk_create(
[FeedArticle(article=article, feed=feed) for article in created_articles],
[FeedArticle(article=result.article, feed=feed) for result in save_result],
ignore_conflicts=True,
)

Expand Down
1 change: 1 addition & 0 deletions legadilo/feeds/tests/snapshots/test_api/test_get/feed.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"article_retention_time": 0,
"category": null,
"description": "",
"details_url": "https://example.com/feeds/articles/1-feed-slug/",
"disabled_at": null,
"disabled_reason": "",
"enabled": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"article_retention_time": 0,
"category": null,
"description": "",
"details_url": "https://example.com/feeds/articles/1-feed-slug/",
"disabled_at": null,
"disabled_reason": "",
"enabled": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"article_retention_time": 0,
"category": null,
"description": "",
"details_url": "https://example.com/feeds/articles/1-feed-slug/",
"disabled_at": null,
"disabled_reason": "",
"enabled": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"title": "Category title"
},
"description": "Some feed description",
"details_url": "https://example.com/feeds/articles/1-feed-slug/",
"disabled_at": null,
"disabled_reason": "",
"enabled": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"article_retention_time": 0,
"category": null,
"description": "Some feed description",
"details_url": "https://example.com/feeds/articles/1-feed-slug/",
"disabled_at": null,
"disabled_reason": "",
"enabled": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"article_retention_time": 0,
"category": null,
"description": "",
"details_url": "https://example.com/feeds/articles/1-feed-slug/",
"disabled_at": null,
"disabled_reason": "",
"enabled": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"title": "Category title"
},
"description": "",
"details_url": "https://example.com/feeds/articles/1-feed-slug/",
"disabled_at": null,
"disabled_reason": "",
"enabled": true,
Expand Down
Loading

0 comments on commit 105c058

Please sign in to comment.