view js/feed-preview.js @ 14:376a0e415bba

Properly handle non-text content in Atom feed elements The title, subtitle, summary and content elements of Atom feeds can all have non-text content. When parsing title and subtitle elements HTML and XHTML content will be stripped of any markup in order to keep it simple. In summary and content elements markup will be preserved. Element content of any other type as well as remote content in content elements will be ignored.
author Guido Berhoerster <guido+feed-preview@berhoerster.name>
date Mon, 10 Dec 2018 16:38:11 +0100
parents ff5e5e3eba32
children f0c4a458869c
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';

export function renderFeedPreview(feedPreviewDocument, feed) {
    // inject XSL stylesheet which transforms XHTML to HTML allowing the use of
    // the HTML DOM
    let xslFilename = browser.runtime.getURL('web_resources/xhtml-to-html.xsl');
    let xmlStylesheetNode =
            feedPreviewDocument.createProcessingInstruction('xml-stylesheet',
            `type="application/xslt+xml" href="${xslFilename}"`);
    feedPreviewDocument.firstChild.after(xmlStylesheetNode);

    feedPreviewDocument.querySelector('#default-stylesheet').href =
            browser.runtime.getURL('web_resources/style/feed-preview.css');

    feedPreviewDocument.querySelector('title').textContent = feed.title;

    feedPreviewDocument.querySelector('label[for="feed-reader-selection"]')
            .textContent = browser.i18n.getMessage('feedReaderSelectionLabel');
    feedPreviewDocument.querySelector('[name="subscribe"]').textContent =
            browser.i18n.getMessage('subscribeButtonLabel');

    feedPreviewDocument.querySelector('#feed-title').textContent = feed.title;
    feedPreviewDocument.querySelector('#feed-subtitle').textContent =
            feed.subtitle;

    if (typeof feed.logo !== 'undefined') {
        let feedLogoTemplate =
                feedPreviewDocument.querySelector('#feed-logo-template');
        let logoNode = feedPreviewDocument.importNode(feedLogoTemplate.content,
                true);
        let imgElement = logoNode.querySelector('#feed-logo');
        imgElement.setAttribute('src', feed.logo.url);
        imgElement.setAttribute('alt', feed.logo.title);
        feedPreviewDocument.querySelector('#feed-header').prepend(logoNode);
    }

    feedPreviewDocument.querySelector("#no-entries-hint").textContent =
            browser.i18n.getMessage('noEntriesHint');

    let entryTemplateElement =
            feedPreviewDocument.querySelector('#entry-template');
    let entryTitleTemplateElement =
            feedPreviewDocument.querySelector('#entry-title-template');
    let entryTitleLinkedTemplateElement =
            feedPreviewDocument.querySelector('#entry-title-linked-template');
    let entryFileListTemplateElement =
            feedPreviewDocument.querySelector('#entry-files-list-template');
    let entryFileTemplateElement =
            feedPreviewDocument.querySelector('#entry-file-template');
    for (let entry of feed.entries) {
        let entryNode =
                feedPreviewDocument.importNode(entryTemplateElement.content,
                true);
        let titleElement;
        let titleNode;

        if (typeof entry.link !== 'undefined') {
            titleNode = feedPreviewDocument
                    .importNode(entryTitleLinkedTemplateElement.content, true);
            titleElement = titleNode.querySelector('.entry-link');
            titleElement.href = entry.link;
            titleElement.title = entry.title;
        } else {
            titleNode = feedPreviewDocument
                    .importNode(entryTitleTemplateElement.content, true);
            titleElement = titleNode.querySelector('.entry-title');
        }
        titleElement.textContent = entry.title;
        entryNode.querySelector('.entry-header').prepend(titleNode);

        let timeElement = entryNode.querySelector('.entry-date > time');
        timeElement.textContent = entry.date.toLocaleString();

        let contentElement = entryNode.querySelector('.entry-content');
        let contentDocument = new DOMParser().parseFromString(entry.content,
                'text/html');
        let stylesheetElement = contentDocument.createElement('link');
        stylesheetElement.rel = 'stylesheet';
        stylesheetElement.href =
                browser.runtime.getURL('web_resources/style/entry-content.css');
        contentDocument.head.appendChild(stylesheetElement);
        contentElement.srcdoc = new XMLSerializer()
                .serializeToString(contentDocument);
        contentElement.title = entry.title;

        if (entry.files.length > 0) {
            let fileListNode = feedPreviewDocument
                    .importNode(entryFileListTemplateElement.content, true);
            fileListNode.querySelector('.entry-files-title').textContent =
                    browser.i18n.getMessage('filesTitle');
            let fileListElement =
                    fileListNode.querySelector('.entry-files-list');

            for (let file of entry.files) {
                let fileNode = feedPreviewDocument
                        .importNode(entryFileTemplateElement.content, true);

                let fileLinkElement =
                        fileNode.querySelector('.entry-file-link');
                fileLinkElement.href = file.url;
                fileLinkElement.title = file.filename;
                fileLinkElement.textContent = file.filename;

                fileNode.querySelector('.entry-file-info').textContent =
                        `(${file.type}, ${file.size} bytes)`;

                fileListElement.appendChild(fileNode);
            }

            entryNode.querySelector('.entry').append(fileListNode);
        }

        feedPreviewDocument.body.append(entryNode);
    }
}