view options/options.js @ 17:48cabd01ef64

Add support for enclosures in Atom feeds
author Guido Berhoerster <guido+feed-preview@berhoerster.name>
date Wed, 12 Dec 2018 22:38:45 +0100
parents ff5e5e3eba32
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();