Mercurial > addons > firefox-addons > feed-preview
view options/options.js @ 10:ff5e5e3eba32
Implement feed subscription for web-based feed readers
Add options page for configuring web-based feed readers which allow for
subscribing to feeds via GET requests.
Track tabs containing feed previews and inject a content script which
retrieves the configured feed readers and keeps them in sync.
author | Guido Berhoerster <guido+feed-preview@berhoerster.name> |
---|---|
date | Fri, 07 Dec 2018 23:00:41 +0100 |
parents | |
children | 688d75e554e0 |
line wrap: on
line source
/* * Copyright (C) 2018 Guido Berhoerster <guido+feed-preview@berhoerster.name> * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 'use strict'; function normalizeURL(text) { return new URL(text).toString(); } class OptionsPage { constructor() { this.selectedFeedReader = -1; document.querySelector('#feed-readers-title').textContent = browser.i18n.getMessage('feedReadersTitle'); let feedReadersForm = document.forms['feed-readers']; feedReadersForm.elements['move-up'].textContent = browser.i18n.getMessage('feedReaderMoveUpButton'); feedReadersForm.elements['move-down'].textContent = browser.i18n.getMessage('feedReaderMoveDownButton'); feedReadersForm.elements['remove'].textContent = browser.i18n.getMessage('feedReaderRemoveButton'); feedReadersForm.addEventListener('change', this); let addFeedReaderForm = document.forms['add-feed-reader']; addFeedReaderForm.elements['add'].textContent = browser.i18n.getMessage('feedReaderAddButton'); let titleElement = addFeedReaderForm.elements['title']; titleElement.labels[0].textContent = browser.i18n.getMessage('feedReaderTitleLabel'); titleElement.placeholder = browser.i18n.getMessage('feedReaderTitlePlaceholder'); let urlTemplateElement = addFeedReaderForm.elements['url-template']; urlTemplateElement.labels[0].textContent = browser.i18n.getMessage('feedReaderUrlTemplateLabel'); urlTemplateElement.placeholder = browser.i18n.getMessage('feedReaderUrlTemplatePlaceholder'); document.querySelector('#feed-reader-url-caption').textContent = browser.i18n.getMessage('feedReaderUrlTemplateCaption'); addFeedReaderForm.addEventListener('focusout', this); document.addEventListener('submit', this); this.initOptions(); } async initOptions() { let {feedReaders} = await browser.storage.sync.get('feedReaders'); if (Array.isArray(feedReaders)) { console.log('initialized feedReaders from storage', feedReaders); this.updateFeedReaders(feedReaders); } browser.storage.onChanged.addListener(this.onStorageChanged.bind(this)); } validateURLTemplate(text) { let url; try { url = new URL(text); } catch(e) { if (e instanceof TypeError) { return browser.i18n.getMessage('invalidURLError'); } throw e; } if (url.protocol !== 'http:' && url.protocol !== 'https:') { return browser.i18n.getMessage('invalidProtocolError'); } if (!(url.pathname.includes('%s') || url.search.includes('%s'))) { return browser.i18n.getMessage('missingPlaceholderError'); } return ''; } updateFeedReaders(feedReaders) { let feedReadersForm = document.forms['feed-readers']; let feedReaderItemElements = feedReadersForm.querySelectorAll('.feed-reader-item'); for (let feedReaderItemElement of feedReaderItemElements) { feedReaderItemElement.remove(); } let feedReaderItemTemplateElement = document.querySelector('#feed-reader-item-template'); let feedReaderSelectionElement = feedReadersForm.querySelector('#feed-reader-selection') for (let feedReader of feedReaders) { let feedReaderItemNode = document.importNode(feedReaderItemTemplateElement.content, true); let feedReaderInputElement = feedReaderItemNode.querySelector('input[name=feed-reader]'); feedReaderInputElement.dataset.title = feedReader.title; feedReaderInputElement.value = feedReader.urlTemplate; feedReaderItemNode.querySelector('.feed-reader-title') .textContent = feedReader.title; feedReaderItemNode.querySelector('.feed-reader-url-template') .textContent = feedReader.urlTemplate; feedReaderSelectionElement.append(feedReaderItemNode); } feedReadersForm.elements['buttons'].disabled = true; } getFeedReaders() { let feedReaderInput = document.forms['feed-readers'].elements['feed-reader']; if (feedReaderInput instanceof RadioNodeList) { return Array.from(feedReaderInput); } else if (typeof feedReaderInput === 'undefined') { return []; } return Array.from([feedReaderInput]); } selectFeedReader() { console.debug('selected:', this.selectedFeedReader); if (this.selectedFeedReader < 0) { return; } let feedReadersForm = document.forms['feed-readers']; let feedReaderElements = this.getFeedReaders(); feedReaderElements[this.selectedFeedReader].checked = true; // ensure that the checked element will also be the focused one the // next time the radio input group receives focus let activeElement = document.activeElement; feedReaderElements[this.selectedFeedReader].focus(); activeElement.focus(); feedReadersForm.elements['buttons'].disabled = false; } serializeFeedReaders() { return this.getFeedReaders().map(element => ({ title: element.dataset.title, urlTemplate: element.value })); } onStorageChanged(changes, areaName) { if (areaName !== 'sync' || typeof changes.feedReaders === 'undefined') { return; } let feedReaders; if (typeof changes.feedReaders.newValue !== 'undefined' && Array.isArray(changes.feedReaders.newValue)) { feedReaders = changes.feedReaders.newValue; console.log('feedReaders changed to', feedReaders); } else { // list of feed readers was removed or set to nonsensical value feedReaders = []; console.log('feedReaders was removed'); } if (this.selectedFeedReader >= feedReaders.length) { // save selected feed reader is no longer valid this.selectedFeedReader = -1; } this.updateFeedReaders(feedReaders); this.selectFeedReader(); } handleEvent(ev) { console.log('previously selected:', this.selectedFeedReader); if (ev.type === 'change' && ev.target.name === 'feed-reader') { // feed reader was selected by user interaction console.debug(ev); this.selectedFeedReader = this.getFeedReaders().indexOf(ev.target); console.log('now selected:', this.selectedFeedReader); document.forms['feed-readers'].elements['buttons'].disabled = false; } else if (ev.type === 'submit' && ev.target.id === 'feed-readers') { // remove feed reader or move feed reader up or down ev.preventDefault(); let feedReaders = this.serializeFeedReaders(); if (ev.explicitOriginalTarget.name === 'move-up') { if (this.selectedFeedReader - 1 < 0) { // the first feed reader is selected return; } [feedReaders[this.selectedFeedReader - 1], feedReaders[this.selectedFeedReader]] = [feedReaders[this.selectedFeedReader], feedReaders[this.selectedFeedReader - 1]]; this.selectedFeedReader--; } else if (ev.explicitOriginalTarget.name === 'move-down') { if (this.selectedFeedReader + 1 === feedReaders.length) { // the last feed reader is selected return; } [feedReaders[this.selectedFeedReader + 1], feedReaders[this.selectedFeedReader]] = [feedReaders[this.selectedFeedReader], feedReaders[this.selectedFeedReader + 1]]; this.selectedFeedReader++; } else if (ev.explicitOriginalTarget.name === 'remove') { feedReaders.splice(this.selectedFeedReader, 1); this.selectedFeedReader--; } browser.storage.sync.set({feedReaders}); console.log('set feedReaders to ', feedReaders); } else if (ev.type === 'focusout' && ev.target.name === 'url-template') { // url template was changed let validity = this.validateURLTemplate(ev.target.value); ev.target.setCustomValidity(validity); } else if (ev.type === 'submit' && ev.target.id === 'add-feed-reader') { // feed reader added ev.preventDefault(); let urlTemplate = ev.target.elements['url-template'].value; let isValid = this.validateURLTemplate(urlTemplate); ev.target.elements['url-template'].setCustomValidity(isValid); if (!ev.target.reportValidity()) { return; } let feedReaders = this.serializeFeedReaders(); feedReaders.push({ title: ev.target.elements['title'].value, urlTemplate: normalizeURL(urlTemplate) }); browser.storage.sync.set({feedReaders}); console.log('set feedReaders to', feedReaders); document.forms['add-feed-reader'].reset(); } } } var page = new OptionsPage();