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

Running paperbits on arbitrary subpath #211

Closed
joshua-mng opened this issue Dec 1, 2022 · 6 comments
Closed

Running paperbits on arbitrary subpath #211

joshua-mng opened this issue Dec 1, 2022 · 6 comments

Comments

@joshua-mng
Copy link

Im trying to use this wonderful library in my project. Basically my app is running on following url.

https://mydomain.com/

I want to do following 2 things

  1. Run published website on sub path, like https://mydomain.com/mysite1
  2. Run designer on sub path, like https://mydomain.com/sites/mysite1/designer

I replaced default PermalinkResolver and MediaPermalinkResolver to prepend /mysite1 in front of urls during publishing so now published site works partially on sub path /mysite1

I'm serving designer assets on https://mydomain.com/sites/mysite1/designer which works partially

I can replace, permalink resolver, blob storage, object storage to use my custom backend.

But problem is:

  • Event some components are replaced to handle the extra subpath, still designer is unable to load some data, like block-snippets.json, etc because those are hard coded in paperbits/constants.ts file, so what Im doing is replacing all occurences of those constant url path strings in paperbits.js file dynamically.
  • I configured /data/demo.json path to be relative to the subpath correctly, and designer loads, but it says page not found error on initial load. I suspect something has to do with the path, like /mysite1/wiki/getting-started is not being resolved to /wik/getting-started
  • When I click on individual pages in the pages widget, page are shown, but their url is relative to root path, like /wiki/getting-started instead of /mysite1/wiki/getting-started

So the question is what is the best way to achieve this? (Running published website and designer or arbitrary dynamic subpath)

Thank you

@msrshahrukh100
Copy link

This is very similar to my issue #208

@azaslonov
Copy link
Member

azaslonov commented Dec 1, 2022

@joshua-mng, for the first issue, which is indeed similar to what @msrshahrukh100 mentioned, we'll expose respective settings to override hardcoded values this week. For the rest two issues, let me get back to you (also this week).

@joshua-mng
Copy link
Author

joshua-mng commented Dec 2, 2022

Thank you for your quick response. And this is indeed very similar to issue #208, but there is a subtle difference.

In my case, subpath to run the website and designer is actually not a fixed sub path known at build time compared to using webpack's output.publicPath which is a path known at build time.

What would be so helpful in my case is if I can inject these subpath information to paperbits in the index.html or page.html file via attributes. Like,

<html data-base-path="/mysite1/computed/at/runtime">

reason for this is that, I can compute the subpath at runtime. And also entry point html files are so small so it's very efficient to add html attributes to it.

It needs to be computed at runtime, so it needs to be passed to paperbits designer or publisher rather than webpack.

When designing, paperbits/core/ko/bindingHandlers/bindingHandlers.host.ts -> createIFrame method creates iframe with constant url too, '/page.html?designtime=true', which should be configurable too.

For publisher, I think we can pass this basePath info via settingsProvider or via html attributes like above. Overriding settingsProvider data via command line arguments would be super great for this purpose, but that's another issue i guess.

As for the last 2 issues that you said you would look into, It is because of the paperbits/core/routing/DefaultRouter.ts -> getRouteFromLocation and getRoute methods which does not remove basePath, so currently I have created custom router to replace those methods in order to do what I'm trying to do. And for the last issue, workshop/pages is showing list of PageItems, and their permalink is /page/url without prefix, which is correct, but updating actual history entry, Im using custom history route handler to prepend basePath to route url. Only issue now is that in design mode, when clicking on hyperlinks with Ctrl+Click, entire absolute path is appended to current url, dont know where in the code base, that functionality is implemented. This is how I got everything working for now.

As FYI, here are the constant string values that I think needs to be configurable (I am replacing them in index.html, page.html, paperbits.js files)

/styles/theme.css
/page.html
/config.json
/search-index.json
/sitemap.xml
/data/icon-fonts.json
/data/grid-snippets.json
/data/block-snippets.json
/data/style-snippets.json

Above values are all written as constant strings, so unable to change them.

Any other solution would be appreciated, thank you.

@azaslonov
Copy link
Member

azaslonov commented Dec 3, 2022

Hi @joshua-mng, wow, you've got pretty deep into the code, amazing! :). I remember we had a question like this and we suggested a router below. It's a bit outdated, but should still work. Let me know if you still have an issue.

Also, I have exposed some settings (see design.config.json) in version 0.1.534. Let me check the rest resources you suggested a bit later.

Here's the router:

import { EventManager } from "@paperbits/common/events";
import { Router, RouterEvents, Route, RouteGuard } from "@paperbits/common/routing";

export class RelativePathRouter implements Router {
    public currentRoute: Route;
    public notifyListeners: boolean;
    private readonly basePath: string;

    constructor(
        private readonly eventManager: EventManager,
        private readonly routeGuards: RouteGuard[],
    ) {
        // setting up...
        this.notifyListeners = true;

        const segments = window.location.pathname.split("/");
        this.basePath = "/" + segments[1] + "/" + segments[2];

        this.currentRoute = this.getRouteFromLocation();
    }

    public getRouteFromLocation(): Route {
        const path = "/" + location.pathname.substr(this.basePath.length + 1);
        const hash = location.hash.startsWith("#") ? location.hash.slice(1) : location.hash;
        const url = location.pathname + hash;

        const route: Route = {
            url: url,
            path: path,
            metadata: {},
            hash: hash,
            previous: null
        };

        return route;
    }

    public addRouteChangeListener(eventHandler: (args?: any) => void): void {
        this.eventManager.addEventListener(RouterEvents.onRouteChange, eventHandler);
    }

    public removeRouteChangeListener(eventHandler: (args?: any) => void): void {
        this.eventManager.removeEventListener(RouterEvents.onRouteChange, eventHandler);
    }

    /**
     * Navigates to specified URL.
     * @param url Absolute or relative path, i.e. https://paperbits.io or /about
     * @param title Destination title
     * @param metadata Associated metadata
     */
    public async navigateTo(url: string, title: string = null, metadata: Object = {}): Promise<void> {
        if (!url) {
            return;
        }

        const isFullUrl = url && (url.startsWith("http://") || url.startsWith("https://"));
        const isLocalUrl = url.startsWith(location.origin);

        if (isFullUrl && !isLocalUrl) {
            window.open(url, "_blank"); // navigating external link
            return;
        }

        url = isFullUrl
            ? url.substring(location.origin.length)
            : url;

        const parts = url.split("#");

        const route: Route = {
            url: this.appendBasePath(url),
            path: parts.length > 1 ? parts[0] || location.pathname : parts[0],
            title: title,
            metadata: metadata,
            hash: parts.length > 1 ? parts[1] : "",
            previous: this.currentRoute
        };

        const canActivate = await this.canActivate(route);

        if (canActivate) {
            this.currentRoute = route;

            if (this.notifyListeners) {
                this.eventManager.dispatchEvent(RouterEvents.onRouteChange, route);
            }
        }
    }

    protected async canActivate(route: Route): Promise<boolean> {
        for (const routeGuard of this.routeGuards) {
            try {
                const canActivate = await routeGuard.canActivate(route);

                if (!canActivate) {
                    return false;
                }
            }
            catch (error) {
                throw new Error(`Unable to invoke route a guard: ${error}`);
                return false;
            }
        }

        return true;
    }

    public getCurrentUrl(): string {
        let permalink = this.currentRoute.path;

        const hash = this.getHash();

        if (this.currentRoute.hash) {
            permalink += "#" + hash;
        }

        return permalink;
    }

    public getCurrentUrlMetadata(): Object {
        return this.currentRoute.metadata;
    }

    public getPath(): string {
        return this.currentRoute.path;
    }

    public getHash(): string {
        return this.currentRoute.hash;
    }

    public getCurrentRoute(): Route {
        return this.currentRoute;
    }

    private appendBasePath(url: string): string {
        return url.startsWith("/") ? this.basePath + url : this.basePath + "/" + url;
    }

    public addHistoryUpdateListener(eventHandler: (args?: any) => void): void {
        // Not supported
    }

    public removeHistoryUpdateListener(eventHandler: (args?: any) => void): void {
        // Not supported
    }

    public updateHistory(url: string, title?: string): void {
        // Not supported
    }
}

@joshua-mng
Copy link
Author

joshua-mng commented Dec 5, 2022

I finally got everything working now.

  • Paperbits designer working on arbitrary computed sub path
  • Published site also works on arbitrary computed sub path
  • Custom blob and object storage for my back end service, custom config file provider etc

Im using the provided extensibility points as much as possible, and also using some hackish ways to workaround some issues (mentioned in my previous comment, bunch of magic strings). So it would be great if all those magic strings would be set in configuration file OR even better, just in some helper file where we can set them directly (setAssetConfig etc ..).

Thank you very much for the support.

@azaslonov
Copy link
Member

Great to hear that! Agree on magic strings, we'll get rid of them.

Closing the issue since questions resolved. Please feel free to reopen shall you need further assistance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants