HansSchouten / Laravel-Pagebuilder

A drag and drop pagebuilder to manage pages in any Laravel project
https://www.phpagebuilder.com
MIT License
779 stars 181 forks source link

li tag is beeing removed #15

Closed Dontorpedo closed 4 years ago

Dontorpedo commented 4 years ago

hi again,

1) i added some blocks to the builder (header, footer and so on) and saw a strange behaviour, when i click on a li element in the header, the li is removed and the design broken.. see in the video. what can i do about this?

https://share.getcloudapp.com/P8uemELl

2) Background image which is set in css or inline, is not shown.. and the element can't be editet.. like the hero seen in my video.. i cant edit the text inside..

3)some suggestions:

a custom css/js panel would be great, also the possibility to save ready templates for reuse (not the same as blocks).. how would one do this?

regards

HansSchouten commented 4 years ago

Hi, I thanks for the feedback. I have to look into editing (un)ordered lists, I think it is because they are not editable as a whole or CKEditor changes it into a paragraph tag or something. What I do is I have in my laravel project a simple menu builder (for example with nestable). Then, I use a .php block that loads all items of the menu stored in the database, so I do not have to edit the menu manually from within the pagebuilder (on every page).

As you can see in this image, it should be possible to edit a css background image. afbeelding

I think you mean creating custom layouts from within your application (similar to the master layout) and then add some default blocks. That is indeed very helpful. I have it in my platform that integrates the pagebuilder, so it certainly is already possible to build this on top of the pagebuilder.

I have a separate page_layouts table that has the same structure as the pages table. This is the controller action that I am using:

public function buildLayout($pageLayoutId = null)
{
    $route = $_GET['route'] ?? null;
    $action = $_GET['action'] ?? null;

    // override settings to use the pagebuilder on layouts instead of pages
    config([
        'pagebuilder.pagebuilder.url' => '/admin/website/page-layouts/build',
        'pagebuilder.pagebuilder.actions.back' => '/admin/website/page-layouts',
        'pagebuilder.page.class' => \PHPageBuilder\Page::class,
        'pagebuilder.page.translation.class' => \PHPageBuilder\PageTranslation::class,
        'pagebuilder.page.table' => 'page_layouts',
    ]);

    // refresh the PhpPageBuilder singleton with the updated config
    app()->extend('phpPageBuilder', function ($command, $app) {
        return new PHPageBuilder(config('pagebuilder'));
    });

    // get the page layout as Page object
    $pageLayoutId = is_numeric($pageLayoutId) ? $pageLayoutId : ($_GET['page'] ?? null);
    $pageRepository = new PageRepository;
    /* @var PageContract $pageLayout */
    $pageLayout = $pageRepository->findWithId($pageLayoutId);

    /* @var PHPageBuilder $phpPageBuilder */
    $phpPageBuilder = app()->make('phpPageBuilder');
    $pageBuilder = $phpPageBuilder->getPageBuilder();

    $customScripts = view("website::pagebuilder.scripts")->render();
    $pageBuilder->customScripts('head', $customScripts);
    $pageBuilder->handleRequest($route, $action, $pageLayout);
}

And I have an class that overrides the PageRenderer, in order to render the layout. The code is not fully copy pastable, since I am using certain repositories that I created, but it gives an idea of how I use it.

use Exception;
use Falco\Website\Repositories\PageLayoutRepository;
use PHPageBuilder\Contracts\PageContract;
use PHPageBuilder\Page;
use PHPageBuilder\ThemeBlock;

class PageRenderer extends \PHPageBuilder\Modules\GrapesJS\PageRenderer
{
    /**
     * Return the rendered version of the page.
     *
     * @return string
     * @throws Exception
     */
    public function render()
    {
        // init variables that should be accessible in the view
        $renderer = $this;
        $page = $this->page;
        if ($this->forPageBuilder) {
            $body = '<div phpb-content-container="true"></div>';
        } else {
            $body = $this->renderBody();
        }

        // recursively render page layouts in which the page body will be added
        list($layoutPath, $body) = $this->renderLayouts($page, $this->getPageLayoutPath(), $body);

        if ($layoutPath) {
            ob_start();
            require $layoutPath;
            $html = ob_get_contents();
            ob_end_clean();
        } else {
            $html = $body;
        }

        // parse any shortcodes present in the page layout
        $html = $this->parseShortcodes($html);

        return $html;
    }

    /**
     * Recursively render all nested layouts, starting from the outer layout with finally a configured base layout from the current theme.
     *
     * @param PageContract $parent
     * @param $parentLayoutPath
     * @param $renderedParentBody
     * @return array
     * @throws Exception
     */
    protected function renderLayouts(PageContract $parent, $parentLayoutPath, $renderedParentBody)
    {
        // render the layout if a page_layout_id is passed, or the layout field has a numeric value (so it contains reference to a custom page layout)
        if ((is_null($parent->getLayout()) && ! empty($parent->page_layout_id)) || is_numeric($parent->getLayout())) {
            $pageLayoutId = $parent->page_layout_id ?? $parent->getLayout();
            $pageLayout = (new PageLayoutRepository)->findOneByField('id', $pageLayoutId);

            // create a new page renderer to render the page layout (in an empty parent layout)
            $layoutPage = new Page;
            $layoutPage->setData([
                'layout' => null,
                'data' => json_decode($pageLayout->data, true)
            ]);
            $layoutRenderer = new PageRenderer($this->theme, $layoutPage, $this->forPageBuilder);
            $layoutBody = $layoutRenderer->renderBody();

            // replace the layout main container with the page body or body of a parent layout
            $layoutMainContainerBlock = new ThemeBlock($this->theme, 'layout-main-container');
            $layoutMainContainerBlockViewFile = $layoutMainContainerBlock->getViewFile();
            $layoutMainContainerString = file_get_contents($layoutMainContainerBlockViewFile);
            $body = str_replace($layoutMainContainerString, $renderedParentBody, $layoutBody);

            // render layouts in which this layout or page body is contained
            $layoutPath = $this->theme->getFolder() . '/layouts/' . $pageLayout->layout . '/view.php';
            $layoutPage->setData(['layout' => $pageLayout->layout]);
            list($layoutPath, $body) = $this->renderLayouts($layoutPage, $layoutPath, $body);
        }

        // eventually return the base layout (a layout folder of the current theme) of the inner most page layout, and the final page body that combines all layouts
        return [$layoutPath ?? $parentLayoutPath, $body ?? $renderedParentBody];
    }
}

Next, I replaced the PageRenderer class by specifying a class replacement in the phpagebuiler config file.

'class_replacements' => [
    PHPageBuilder\Modules\GrapesJS\PageRenderer::class => Falco\Website\Libraries\PHPageBuilder\PageRenderer::class
],
Dontorpedo commented 4 years ago

nice, thank you for the code!

what i meant is, i cant edit the text inside the block which has a background image.

i found out its not possible to put a block inside a block or did i miss something?

i created some "general" blocks like column, text, button and so on and tried to put the text or button block inside the colulmn, but thats not possible :(

HansSchouten commented 4 years ago

In my screenshot I have a text-editable block with a background image, so it should be a problem with your specific block html structure. I just used a block with a view.html which contains a <p> tag with some example text.

You cannot just drop blocks everywhere, but you can create a blocks container in which it is possible to drop blocks. You create a blocks container by following these steps.

HansSchouten commented 4 years ago

I reproduced the problem with li tags being removed and I just committed a fix.

billiemead commented 3 years ago

@HansSchouten

I am trying to integrate the layouts table as you detailed above.

I was wondering if you could give an example of what to use inside of PageLayoutRepository and the master layout file view.php? Also, where does the helper method findOneByField come from as Laravel 6 is giving me a hard time with it?

Cheers, Billie

HansSchouten commented 3 years ago

You are correct, I see that method is part of the boilerplate I am working with, the findOneByField does this:

    public function findOneByField($field, $value = null, $columns = ['*'])
    {
        $model = $this->findByField($field, $value, $columns = ['*']);
        return $model->first();
    }

So just a findByField with a first.

PageLayoutRepository is a simple class I use that allows an abstraction for CRUD on page layouts (with some customization for my use cases). In standard Laravel a PageLayout class extending the base eloquent Model class would also work just fine.

This is a simplified version of my current master layout:

<!DOCTYPE html>
<html lang="<?= phpb_current_language() ?>">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title><?= e($page->getTitle()) ?></title>
    <meta name="description" content="<?= e($page->getMetaDescription()) ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <meta name="csrf-token" content="<?= e(csrf_token()) ?>">

    <link rel="stylesheet" href="<?= themeAssetVersion('css/app.css') ?>">
</head>
<body>

<?= $body ?>

<script src="<?= themeAssetVersion('js/app.js') ?>"></script>
<!-- Run block scripts -->
<script type="text/javascript">
    document.querySelectorAll("script").forEach(function(scriptTag) {
        scriptTag.dispatchEvent(new Event('run-script'));
    });
</script>
<?php
foreach ($page->get('custom-scripts-urls') ?? [] as $scriptUrl):
    echo '<script src="' . e($scriptUrl) . '"></script>';
endforeach;
?>
</body>
</html>

themeAssetVersion is a simple helper method I use to bust outdated cached css/js files:

    /**
     * Return the theme asset path with a version GET parameter based on the latest file change.
     *
     * @param $assetPath
     * @return string
     */
    function themeAssetVersion($assetPath)
    {
        $theme = config('pagebuilder.theme.active_theme');
        $themeFolder = config('pagebuilder.theme.folder') . '/' . $theme;
        $fullAssetPath = $themeFolder . '/public/' . $assetPath;

        if (realpath($fullAssetPath)) {
            $modifiedTime = filemtime($fullAssetPath);
            return url(phpb_theme_asset($assetPath) . '?v=' . $modifiedTime);
        }
        return url(phpb_theme_asset($assetPath));
    }

Ignoring this method and using phpb_theme_asset intead would also work just fine.

billiemead commented 3 years ago

@HansSchouten Thank you so much. I will work on this straight away and can't wait to see how it works. Cheers Hans!

Zeeshan-1313 commented 1 year ago

@HansSchouten can i please update CKeditor becouse this dont have option for listi

  • builts poitns etc