Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export const i18n = createI18n({
"An unknown error has occurred. Please close and reopen the extension. If it still doesn’t work, please contact the support.",
"errors.url.presence": "Enter a URL.",
"errors.url.url": "The URL of the current page is not supported by Flus.",
"feeds.autotest.invalid_url":
"Unable to test URLs automatically as the tab URL looks invalid.",
"feeds.autotest.missing_permissions":
"You must authorize the extension to access all websites in the browser settings.",
"feeds.autotest.no_results": "No URL corresponding to a feed was found.",
"feeds.autotest.submit": "Test URLs automatically",
"feeds.count_detected":
"No feeds detected on this page. | 1 feed detected on this page. | {count} feeds detected on this page.",
"feeds.follow": "Follow",
Expand All @@ -41,6 +47,7 @@ export const i18n = createI18n({
"feeds.unfollow": "Unfollow",
"feeds.url_potential_feed": "URL of a potential feed",
"forms.error": "Error:",
"forms.or": "or",
"link.count_collections": "No collections | 1 collection | {count} collections",
"link.invalid_protocol":
"This page cannot be handled by Flus (non-supported protocol).",
Expand All @@ -56,7 +63,6 @@ export const i18n = createI18n({
"login.errors.server_error":
"The server “{server}” cannot be reached, please check its address.",
"login.intro": "Log in to access Flus.",
"login.or": "or",
"login.password.label": "Password",
"login.register": "create an account",
"login.server": "Server:",
Expand Down Expand Up @@ -112,6 +118,12 @@ export const i18n = createI18n({
"Une erreur inconnue est survenue. Veuillez fermer et réouvrir l’extension. Si cela ne suffit pas, veuillez contacter le support.",
"errors.url.presence": "Saisissez une URL.",
"errors.url.url": "L’URL de la page actuelle n’est pas supportée par Flus.",
"feeds.autotest.invalid_url":
"Impossible de tester les URL automatiquement car l’URL de l’onglet semble invalide.",
"feeds.autotest.missing_permissions":
"Vous devez autoriser l’extension à accéder à tous les sites dans les paramètres du navigateur.",
"feeds.autotest.no_results": "Aucune URL correspondante à un flux n’a été trouvée.",
"feeds.autotest.submit": "Tester des URL automatiquement",
"feeds.count_detected":
"Aucun flux détecté sur cette page. | 1 flux détecté sur cette page. | {count} flux détectés sur cette page.",
"feeds.follow": "Suivre",
Expand All @@ -124,6 +136,7 @@ export const i18n = createI18n({
"feeds.unfollow": "Ne plus suivre",
"feeds.url_potential_feed": "URL d’un flux potentiel",
"forms.error": "Erreur :",
"forms.or": "ou",
"link.count_collections": "Aucune collection | 1 collection | {count} collections",
"link.invalid_protocol":
"Cette page ne peut pas être enregistrée dans Flus (protocole non supporté).",
Expand All @@ -139,7 +152,6 @@ export const i18n = createI18n({
"login.errors.server_error":
"Le serveur « {server} » est injoignable, veuillez vérifier son adresse.",
"login.intro": "Identifiez-vous pour accéder à votre veille Flus.",
"login.or": "ou",
"login.password.label": "Mot de passe",
"login.register": "créer un compte",
"login.server": "Serveur :",
Expand Down
2 changes: 2 additions & 0 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

"permissions": ["tabs", "storage"],

"host_permissions": ["http://*/*", "https://*/*"],

"action": {
"default_icon": "icons/icon-32.png",
"default_title": "Flus",
Expand Down
142 changes: 128 additions & 14 deletions src/screens/FeedsScreen.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
</ul>

<div v-else class="flow">
<hr>

<p>
{{ t("feeds.search_tip") }}
</p>
Expand All @@ -65,22 +67,37 @@
{{ form.error('url') }}
</p>

<input
v-model="urlToTest"
type="url"
id="url"
placeholder="https://…"
required
:aria-invalid="form.isInvalid('url')"
:aria-errormessage="form.isInvalid('url') ? 'url-error' : null"
:disabled="form.inProgress()"
/>
<div class="cols cols--always cols--gap-smaller">
<input
v-model="urlToTest"
type="url"
id="url"
placeholder="https://…"
required
:aria-invalid="form.isInvalid('url')"
:aria-errormessage="form.isInvalid('url') ? 'url-error' : null"
:disabled="form.inProgress()"
/>

<button class="button--primary button--big" :disabled="form.inProgress()">
{{ t('feeds.test') }}
</button>
</div>
</div>

<div class="text--center">
<button class="button--primary button--big" :disabled="form.inProgress()">
{{ t('feeds.test') }}
</button>
<div aria-live="polite">
<div v-if="!autotestResult">
{{ t('forms.or') }}

<button type="button" @click="testCommonFeedsPatterns">
{{ t('feeds.autotest.submit') }}
</button>
</div>

<p v-else class="form-group__error">
<Icon name="error"></Icon>
{{ autotestResult }}
</p>
</div>
</form>
</div>
Expand Down Expand Up @@ -138,6 +155,7 @@ const alert = ref({
const feeds = ref([]);

const urlToTest = ref("");
const autotestResult = ref("");

function feedUrl(feed) {
return `${store.auth.server}/collections/${feed.id}`;
Expand All @@ -155,6 +173,8 @@ async function getCurrentTab() {
}

function refreshForUrl(url) {
form.startRequest();

api.search(url)
.then((data) => {
// This array is used to deduplicate feeds with the same name.
Expand All @@ -171,6 +191,7 @@ function refreshForUrl(url) {
}
});
ready.value = true;
form.finishRequest();
})
.catch((error) => {
if (error instanceof http.HttpError) {
Expand All @@ -183,6 +204,7 @@ function refreshForUrl(url) {
}

ready.value = true;
form.finishRequest();
});
}

Expand Down Expand Up @@ -233,5 +255,97 @@ function testUrl() {
refreshForUrl(urlToTest.value);
}

async function testCommonFeedsPatterns() {
// Try not to add too many patterns as they generate several HTTP requests.
const commonFeedsPatterns = [
"/feed",
"/feed.xml",
"/rss.xml",
"/atom.xml",
"/index.xml",
"/rss",
"/rss/",
"/rss/feed.xml",
];

const tabUrl = (await getCurrentTab()).url;
const parsedUrl = URL.parse(tabUrl);

if (!parsedUrl) {
autotestResult.value = t("feeds.autotest.invalid_url");
return;
}

if (!(await hasPermissionsForAutotest())) {
const result = await requestPermissionsForAutotest();
if (!result) {
autotestResult.value = t("feeds.autotest.missing_permissions");
return;
}
}

form.startRequest();

const baseUrl = parsedUrl.origin;
let foundFeed = false;

for (const urlPattern of commonFeedsPatterns) {
const testedFeedUrl = baseUrl + urlPattern;

urlToTest.value = testedFeedUrl;

const response = await fetch(testedFeedUrl);

if (!response.ok) {
continue;
}

const content = await response.text();
foundFeed = looksLikeFeedContent(content);

if (foundFeed) {
break;
}
}

form.finishRequest();

if (foundFeed) {
testUrl();
} else {
urlToTest.value = "";
autotestResult.value = t("feeds.autotest.no_results");
}
}

function looksLikeFeedContent(content) {
const parser = new DOMParser();
const doc = parser.parseFromString(content, "application/xml");

const rootElement = doc.documentElement ? doc.documentElement.tagName : null;

return rootElement === "rss" || rootElement === "feed" || rootElement === "rdf";
}

async function hasPermissionsForAutotest() {
const origins = ["http://*/*", "https://*/*"];

return await browser.permissions.contains({ origins });
}

async function requestPermissionsForAutotest() {
const origins = ["http://*/*", "https://*/*"];

try {
return await browser.permissions.request({ origins });
} catch {
// On Firefox, requesting the permission fails with the error:
// "permissions.request may only be called from a user input handler".
// See https://stackoverflow.com/q/47723297 for a bit more context. I
// wasn't able to make it works though.
return false;
}
}

onMounted(refreshForCurrentTab);
</script>
4 changes: 3 additions & 1 deletion src/screens/LinkScreen.vue
Original file line number Diff line number Diff line change
Expand Up @@ -221,5 +221,7 @@ onMounted(refreshCurrentTabUrl);

browser.tabs.onUpdated.addListener(refreshCurrentTabUrl);
browser.tabs.onActivated.addListener(refreshCurrentTabUrl);
browser.windows.onFocusChanged.addListener(refreshCurrentTabUrl);
if (browser.windows) {
browser.windows.onFocusChanged.addListener(refreshCurrentTabUrl);
}
</script>
2 changes: 1 addition & 1 deletion src/screens/LoginScreen.vue
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
</div>

<p>
{{ t("login.or") }}
{{ t("forms.or") }}

<a @click.prevent="openRegistrationPage" :href="registrationUrl">
{{ t("login.register") }}
Expand Down
6 changes: 4 additions & 2 deletions src/screens/MenuScreen.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ async function openShortcuts() {
const shouldShowShortcuts = ref(false);

onMounted(async () => {
shouldShowShortcuts.value =
(await browserUtils.isChrome()) || browser.commands.openShortcutSettings != null;
const isChrome = await browserUtils.isChrome();
const hasShortcutCommand = browser.commands && browser.commands.openShortcutSettings != null;

shouldShowShortcuts.value = isChrome || hasShortcutCommand;
});
</script>