GetPublii / Publii

The most intuitive Static Site CMS designed for SEO-optimized and privacy-focused websites.
https://getpublii.com
GNU General Public License v3.0
6.06k stars 407 forks source link

[Feature Request]: Plugins should be able to download frontend assets #1352

Closed HowToMeetLadies closed 4 months ago

HowToMeetLadies commented 4 months ago

Feature Description

As a plugin developer, I would like to be provided with an interface through which I can request files to be downloaded.

For every change to a dependency, a new plugin version must also be released, although the functionality of the plugin itself usually does not change.

[...] respecting peoples privacy by not forcing a data-slurping CDN – i wish more developers did this

Ideally, plugins would only be able to register URLs and these would then be displayed collectively for all plugins in a CDN section. In the CDN section, the user would then have the option to select these, as a package or individually, depending on the plugin's specifications. For example, optional components could be individually selectable and a package would be a predefined list of urls (script + style + assets). Plugins could decide whether to include bundled (front-assets) or downloaded assets based on the selection. Alternatively, the direct integration of assets from a CDN can be explicitly set by the user in the plugin configuration (if provided). Bonus: You could also let the user enter their own urls in the CDN section.

A simple addition of a downloadFile function for the plugin API would also be sufficient. The main thing is that I would have an indicator as to whether the respective file was loaded or not. 🙏

dziudek commented 4 months ago

In terms of privacy / security this approach only changes the scope - from website visitors to the site owner computer but with higher impact in this case (in some edge-cases it can lead to serious vulnerabilities). But it can also lead to many issues with compatibility, because updated dependencies can introduce errors or changed functionality.

In the predictable future we have no plans for such functionality.

There is one issue with the current state of Publii - there is no update center, but we have plans to introduce this feature in few steps between v.0.46 and v.0.48 what will solve issue with detecting plugins to update.

HowToMeetLadies commented 4 months ago

An excerpt from my DataTables plugin:

{
    "config": [{
        "name": "integration",
        "label": "Integration",
        "group": "Required options",
        "value": "default",
        "type": "select",
        "note": "",
        "options": [
            {"label": "simple-datatables", "value": "default"},
            {"label": "simple-datatables-classic", "value": "classic"},
            {"label": "custom", "value": "custom"}
        ]
    }, {
        "name": "stylesUrl",
        "label": "Custom styles url",
        "group": "Required options",
        "value": "",
        "placeholder": "",
        "type": "text",
        "note": "",
        "dependencies": [{"field": "integration", "value": "custom"}]
    }, {
        "name": "scriptUrl",
        "label": "Custom script url",
        "group": "Required options",
        "value": "",
        "placeholder": "",
        "type": "text",
        "note": "",
        "dependencies": [{"field": "integration", "value": "custom"}]
    }, {
        "name": "customClass",
        "label": "Custom class",
        "group": "Required options",
        "value": "",
        "placeholder": "",
        "type": "text",
        "note": "",
        "dependencies": [{"field": "integration", "value": "custom"}]
    }, {
        "name": "cdn",
        "label": "CDN",
        "group": "Required options",
        "value": true,
        "type": "checkbox",
        "note": "",
        "dependencies": [{"field": "integration", "value": "default,classic"}]
    }]
}
    addEvents() {
        // I tried to load the files here and save them as script.js/style.css.
        this.API.addEvent('beforeRender', this.placeholder, 1, this);
        this.API.addEvent('afterRender', this.checkDeps, 1, this);
    }
    addInsertions() {
        this.API.addInsertion('publiiHead', this.addHead, 1, this);
        this.API.addInsertion('publiiFooter', this.addInit, 1, this);
    }
    addHead() {
        // [...]
        if (this.config.integration === "custom") {
            stylesUrl = this.config.stylesUrl;
            scriptUrl = this.config.scriptUrl;
        } else {
            const baseUrl = `${rendererInstance.siteConfig.domain}/media/plugins/dataTables`;
            stylesUrl = this.config.cdn ? cdn[this.config.integration]["styles"] : `${baseUrl}/styles.css`;
            scriptUrl = this.config.cdn ? cdn[this.config.integration]["script"] : `${baseUrl}/script.js`;
        }
        // [...]
    }
HowToMeetLadies commented 4 months ago

Didn't see your reply while posting.

But it can also lead to many issues with compatibility, because updated dependencies can introduce errors or changed functionality.

In this case, a new version of the plugin would be released. Take a look at my init function, if the name of the class or the initialization were to change, then it would only be a small adjustment. A loader could also be included with a CDN package.

The install/updates would be initiated by the user; the plugins only provide the URLs. Nothing happens here without action from the user and it's basically the same as installing a ZIP file with unknown content.

Personally, I have no problem with this decision and I am also very grateful for the time and effort you put into Publii. But maybe leave this issue to get other users' opinions?

addInit(rendererInstance) {
    let init = [];
    if (Array.isArray(this.config.additionalDirectives)) {
        this.config.additionalDirectives.forEach((directive) => {
            if (directive.name && directive.value) {
                init.push(`${directive.name}:${directive.value}`);
            }
        });
    }
    const selector = this.config.selector;
    const customClass = this.integration === "custom" ? this.customClass : "simpleDatatables.DataTable";
    const main = `if(!document.querySelector("${selector}"))return;window.dataTables = new ${customClass}("${selector}", {${init.join(',')}});`;
    const wait = `<script>window.addEventListener('DOMContentLoaded',function(){${main}});</script>`;
    return wait;
}