Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Base URL and iframed editables support #665

Open
Reinmar opened this issue Nov 15, 2017 · 12 comments
Open

Base URL and iframed editables support #665

Reinmar opened this issue Nov 15, 2017 · 12 comments
Labels
domain:v4-compatibility This issue reports a CKEditor 4 feature/option that's missing in CKEditor 5. follow-up:docs package:image package:link status:discussion type:feature This issue reports a feature request (an idea for a new functionality or a missing option).

Comments

@Reinmar
Copy link
Member

Reinmar commented Nov 15, 2017

CKEditor 4 supports config.baseHref which lets you configure the value of the <base> tag used in an <iframe> which the editor creates to wrap the content.

The main use case for this attribute is in CMSes where content is often edited in different places where it's then displayed. If relative paths to e.g. images are used, then those paths wouldn't work in the admin part of the CMS. By defining config.baseHref to a similar path to where the content will be then served to the users you can solve all these issues.

Iframed editables

First of all, we haven't implemented yet an editor creator which would use iframed editable. So far, we're using inlined editables. They are much lighter and creates far fewer issues than iframed editables.

Iframed editables have two important advantages, though:

  • They encapsulate the stylesheets (scope it). So you can load your target page's stylesheet into the editor without breaking your CMS.
  • They allow using the <base> tag.

The first problem can be workarounded using inline editables, so it wasn't that critical. Basically, it all boils down to writing a proper stylesheet which will reset CMSes styles in the editor content. While not trivial, this is doable. And let's not forget that the content in the editor doesn't need to (or even shouldn't) look exactly like the content on a target page. This approach is often proposed to encourage users to think about the content itself, not the styling.

Can base URLs be solved without iframes? I think so. E.g. the image feature can prepend the base URL to srcs of images in the content (although, with srcset this is a bit more tricky). The link feature needs to prepend this base URL to "open link" link. It all seems doable, but will need to be considered by all features which has something to do with URLs.

Would iframes make it easier? Yes, perhaps for the image feature, but not for the link feature (because balloon would be outside the iframe).

Do we want to support iframes? No, if we'll be able to solve other issues using inline editables. Iframes are a mess.

Questions

  • Should we start thinking about incorporating config.baseUrl to existing features?
  • Do we want to keep CKEditor 5 iframe-free, regardless of the fact that iframes make some things easier?

cc @fredck @wwalc

My answers would be maybe and yes.

@Reinmar Reinmar added status:discussion type:feature This issue reports a feature request (an idea for a new functionality or a missing option). labels Nov 15, 2017
@wwalc
Copy link
Member

wwalc commented Nov 15, 2017

IMO: using <iframe> just to solve the <base> tag issue is a no-go. As for config.baseUrl, looks like the way to go, however I don't think it's critical at this stage to think about it. It looks like an option that can be added at any moment in the future.

@fredck
Copy link
Contributor

fredck commented Nov 15, 2017

I have the impression that having an iframe editor is a must-have in many cases.

Although we can try to convince ourselves that the CSS issue can be solved, I doubt that it is the reality in the real world. I'm in fact more worried about the CSS aspect of it than the base URL issue, which WE could solve in other ways, as we can see.

@Reinmar
Copy link
Member Author

Reinmar commented Nov 15, 2017

I agree that CSS issue is a bigger problem because it needs to be resolved by the other developers. Base URL is something that we can do with or without iframes and it's quite transparent to the other developers.

@oleq
Copy link
Member

oleq commented Dec 20, 2018

The feature requested by the user #1401.

@RadekHavelka
Copy link

👍

@CKEditorBot
Copy link
Collaborator

There has been no activity on this issue for the past year. We've marked it as stale and will close it in 30 days. We understand it may be relevant, so if you're interested in the solution, leave a comment or reaction under this issue.

@nilsfr
Copy link

nilsfr commented Oct 3, 2023

I am in the process of upgrading a CMS from CK4 to CK5 and are affected by this issue. The CMS uses different domains for admin and view. As an example we uses www.example.com for the view and www-adm.example.com for the admin. So we are using baseHref so that relative links inside CK uses the view-domain, even if the editing is done on the admin domain.

Just want to ask if this is something that has not been totally ruled out to be included in CK5? The issue has gone stale, so I am not expecting this to be included in the near future.

However I plan to make a plugin that resolves relative URLs based on a supplied view-url base, before preview in the editor. But it must not affect the data that is stored, as I want to keep the relative url. Any tips on how this can be done in CK5 would be appreciated.

@nilsfr
Copy link

nilsfr commented Oct 6, 2023

I first wasted hours on trying to use editorDowncast converters with dispatcher listening on the 'attribute:src' and 'attribute:linkHref' events. Then I discovered that it is possible to register a post fixer for the editor view. An incomplete example is below:

import {Plugin} from '@ckeditor/ckeditor5-core';
import type {
    DowncastWriter,
    ViewNode,
    ViewElement,
} from '@ckeditor/ckeditor5-engine';
import { createLinkElement, ensureSafeUrl } from '@ckeditor/ckeditor5-link/src/utils';

export default class ViewUrlConversion extends Plugin {
    /**
     * @inheritDoc
     */
    public static get pluginName() {
        return 'ViewUrlConversion' as const;
    }

    public init(): void {
        const config = this.editor.config.get('vrtx');
        if (config?.viewUrl) {
            const viewUrl = config.viewUrl;
            this.editor.conversion.for('editingDowncast')
                .attributeToElement({
                    model: 'linkHref',
                    view: (href, conversionApi) => {
                        const resolvedUrl = (href)
                            ? new URL(href, config.viewUrl).href
                            : '';
                        return createLinkElement(
                            ensureSafeUrl(resolvedUrl),
                            conversionApi
                        );
                    },
                    converterPriority: 'high',
                });
            this.editor.editing.view.document.registerPostFixer(
                (writer) => {
                    const children =
                        writer.document.getRoot()?.getChildren();
                    if (children) {
                        return this.fixViewUrls(children, writer, viewUrl);
                    }
                    return false;
                }
            );
        }
    }

    private fixViewUrls(
        children: IterableIterator<ViewNode>,
        writer: DowncastWriter,
        viewUrl: URL,
    ): boolean {
        let changed = false;
        for (const node of children) {
            if (node.is('element')) {
                if (node.name === 'a' && node.hasAttribute('href')) {
                    changed = this.resolveViewUrl(
                        node,
                        'href',
                        viewUrl,
                        writer
                    ) || changed;
                }
                else if (
                    (
                        node.name === 'img'
                        || node.name === 'video'
                        || node.name === 'audio'
                        || node.name === 'source'
                        || node.name === 'track'
                        || node.name === 'iframe'
                    )
                    && node.hasAttribute('src')
                ) {
                    changed = this.resolveViewUrl(
                        node,
                        'src',
                        viewUrl,
                        writer
                    ) || changed;
                }
                else if (node.name === 'form' && node.hasAttribute('action')) {
                    changed = this.resolveViewUrl(
                        node,
                        'action',
                        viewUrl,
                        writer
                    ) || changed;
                }
                if (node.name === 'video' && node.hasAttribute('poster')) {
                    changed = this.resolveViewUrl(
                        node,
                        'poster',
                        viewUrl,
                        writer
                    ) || changed;
                }
                changed = this.fixViewUrls(node.getChildren(), writer, viewUrl)
                    || changed;
            }
        }
        return changed;
    }

    private resolveViewUrl(
        node: ViewElement,
        key: string,
        base: URL,
        writer: DowncastWriter,
    ): boolean {
        const url = node.getAttribute(key);
        const resolvedUrl = (url)
            ? new URL(url, base).href
            : '';
        if (resolvedUrl && resolvedUrl !== url) {
            writer.setAttribute(
                key,
                resolvedUrl,
                node
            );
            return true;
        }
        return false;
    }
}

@Witoso
Copy link
Member

Witoso commented Oct 9, 2023

Thanks for sharing @nilsfr!

@Witoso Witoso added domain:v4-compatibility This issue reports a CKEditor 4 feature/option that's missing in CKEditor 5. follow-up:docs package:image package:link labels Oct 9, 2023
@nilsfr
Copy link

nilsfr commented Oct 10, 2023

Thanks for sharing @nilsfr!

You are welcome.

I did investigate this a bit more and discovered that the post-fixer for the editor view is called on every interaction with the editable area. It is sub-optimal to traverse the the object model for every mouse click and every key-stroke. I would like to limit it so it is only called on structural changes, but have not found a way to do it. Of cause it must not be more expensive to discover that it is a structural change, than to just traverse the object model.

However it looks to me to be fast enough, but I have not tested with really large object models. I will run with this until we experience performance issues.

@nilsfr
Copy link

nilsfr commented Dec 22, 2023

When using this post-fixer for the view I have problems removing links in the editor. Because the href-attribute is changed it looks like the modelRange to viewRange mapping gets messed up. When I click on a link it gets converted into tree links.

<a class="ck-link_selected" href="http://localhost:3333/build/resources/link-to.html" >Some l</a>
<a class="ck-link_selected" href="http://localhost:3333/build/resources/link-to.html">⁠⁠⁠⁠⁠⁠⁠</a>
<a class="ck-link_selected" href="http://localhost:3333/build/resources/link-to.html">ink</a>

@nilsfr
Copy link

nilsfr commented Jan 4, 2024

As Ckeditor5 got confused by the changes made to a-hrefs by the editing post-fixer I ended up overriding the editingDowncast converter for linkHrefs (ckeditor5-link plugin). Now I am able to remove links. I still handle a-hrefs in the post fixer as a fallback.

I have updated the code example above to include the editingDowncast converter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain:v4-compatibility This issue reports a CKEditor 4 feature/option that's missing in CKEditor 5. follow-up:docs package:image package:link status:discussion type:feature This issue reports a feature request (an idea for a new functionality or a missing option).
Projects
None yet
Development

No branches or pull requests

9 participants