view options/options.js @ 70:e405ff21ab31 version-14

Release version 14
author Guido Berhoerster <guido+feed-preview@berhoerster.name>
date Sun, 03 Mar 2024 18:12:27 +0100
parents 106d78e26d3c
children
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');
        document.querySelector('#feed-preview-title').textContent =
                browser.i18n.getMessage('feedPreviewTitle');

        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);

        let feedPreviewForm = document.forms['feed-preview'];
        feedPreviewForm.elements['expand-entries'].labels[0].textContent =
                browser.i18n.getMessage('feedPreviewExpandItemLabel');
        feedPreviewForm.addEventListener('change', 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);
        }

        let {feedPreview: feedPreviewOptions} =
                await browser.storage.sync.get('feedPreview');
        if (typeof feedPreviewOptions !== 'undefined' &&
                feedPreviewOptions === Object(feedPreviewOptions)) {
            console.log('initialized feedPreviewOptions from storage',
                    feedPreviewOptions);
            this.updateFeedPreviewOptions({
                    expandEntries: !!feedPreviewOptions.expandEntries
            });
        }

        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') || url.hash.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;
    }

    updateFeedPreviewOptions(feedPreviewOptions) {
        document.forms['feed-preview'].elements['expand-entries'].checked =
                feedPreviewOptions.expandEntries;
    }

    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') {
            return;
        }

        if (typeof changes.feedReaders !== 'undefined') {
            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();
        }
        if (typeof changes.feedPreview !== 'undefined') {
            let feedPreviewOptions;
            let newValue = changes.feedPreview.newValue;
            if (typeof newValue !== 'undefined' &&
                    newValue === Object(newValue)) {
                feedPreviewOptions = {expandEntries: !!newValue.expandEntries};
            } else {
                // feed preview preferences were removed or set to nonsensical
                // value
                feedPreviewOptions = {expandEntries: false};
                console.log('feedPreview was removed');
            }
            this.updateFeedPreviewOptions(feedPreviewOptions);
        }
    }

    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.submitter.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.submitter.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.submitter.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();
        } else if (ev.type === 'change' && ev.target.id === 'expand-entries') {
            console.log('expand entries by default:', ev.target.checked);
            browser.storage.sync.set({
                feedPreview: {expandEntries: ev.target.checked}
            });
        }
    }
}

var page = new OptionsPage();