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

Improve support for snippets throughout Winter CMS #36

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
max-parallel: 6
matrix:
phpVersion: ['8.0', '8.1']
phpVersion: ['8.0', '8.1', '8.2']
winterRelease: ['develop']
winterReleaseDir: ['develop']
include:
Expand Down
36 changes: 35 additions & 1 deletion Plugin.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<?php namespace Winter\Pages;

use Backend;
use BackendAuth;
use Backend\Classes\Controller as BaseBackendController;
use Backend\FormWidgets\RichEditor as FroalaFormWidget;
use Backend\Models\UserRole;
use Cms\Classes\Controller as CmsController;
use Cms\Classes\Theme;
Expand Down Expand Up @@ -146,7 +149,8 @@ public function registerMarkupTags(): array
{
return [
'filters' => [
'staticPage' => ['Winter\Pages\Classes\Page', 'url']
'staticPage' => [StaticPage::class, 'url'],
'parseSnippets' => [Snippet::class, 'parse'],
]
];
}
Expand Down Expand Up @@ -232,6 +236,36 @@ protected function extendBackendForms(): void
],
]);
}, PHP_INT_MIN + 1);

// Add support for the "snippets" button to all richeditor fields in the backend
BaseBackendController::extend(function ($controller) {
$user = BackendAuth::getUser();
if (!$user || !$user->hasAccess('winter.pages.access_snippets')) {
return;
}

// Add the AJAX handlers required for snippet inspector properties
// to function on all backend controllers
$controller->addDynamicMethod('onGetInspectorConfiguration', function() {
return (new StaticPage)->onGetInspectorConfiguration();
});
$controller->addDynamicMethod('onGetSnippetNames', function() {
return (new StaticPage)->onGetSnippetNames();
});
$controller->addDynamicMethod('onInspectableGetOptions', function() {
return (new StaticPage)->onInspectableGetOptions();
});

FroalaFormWidget::extend(function ($widget) {
// Adds default base CSS/JS for snippets
$widget->addCss('/plugins/winter/pages/assets/css/pages.css', 'Winter.Pages');
$widget->addJs('/plugins/winter/pages/assets/js/pages-page.js', 'Winter.Pages');
$widget->addJs('/plugins/winter/pages/assets/js/pages-snippets.js', 'Winter.Pages');

// Adds custom assets for the Froala snippet button
$widget->addJs('/plugins/winter/pages/assets/js/froala-snippets.js', 'Winter.Pages');
});
});
}

/**
Expand Down
128 changes: 128 additions & 0 deletions assets/js/froala-snippets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
(function ($) {
// Adds snippets to the default Froala buttons
// Places it after the quote button to keep dropdowns together
$.wn.richEditorButtons.splice(3, 0, 'snippets');

// Define a dropdown button.
$.FroalaEditor.RegisterCommand('snippets', {
// Button title.
title: 'Snippets',

// Mark the button as a dropdown.
type: 'dropdown',

// Specify the icon for the button.
// If this option is not specified, the button name will be used.
icon: '<i class="icon-newspaper-o"></i>',

// The dropdown HTML
html: function() {
if (!$.wn.snippets) {
return '<div style="padding:10px;">No snippets are currently defined.</div>';
}

var html = '<ul class="fr-dropdown-list">';

$.each($.wn.snippets, function(i, snippet) {
html += '<li><a class="fr-command" data-cmd="snippets" data-param1="' + snippet.snippet + '" title="' + snippet.name + '">' + snippet.name + '</a></li>';
});

return html + '</ul>';
},

// Save the dropdown action into undo stack.
undo: true,

// Focus inside the editor before callback.
focus: true,

// Refresh the button state after the callback.
refreshAfterCallback: true,

// Callback.
callback: function (cmd, val, params) {
var options = $.wn.snippets[val];

if (options) {
var $editor = this.$el.parents('[data-control="richeditor"]'),

var $snippetNode = $('<figure contenteditable="false" data-inspector-css-class="hero">&nbsp;</figure>');

if (options.component) {
$snippetNode.attr({
'data-component': options.component,
'data-inspector-class': options.component
})

// If a component-based snippet was added, make sure that
// its code is unique, as it will be used as a component
// alias.

/*
// Init a new snippet manager

// Below code reattaches the inspector event, causing duplicate inspector options
// Until I can figure a solution, I have copied the code to this file...

var snippetManager = new $.wn.pages.snippetManager;
options.snippet = snippetManager.generateUniqueComponentSnippetCode(options.component, options.snippet, $editor.parent())
*/

options.snippet = generateUniqueComponentSnippetCode(options.component, options.snippet, $editor.parent());
}

$snippetNode.attr({
'data-snippet': options.snippet,
'data-name': options.name,
'tabindex': '0',
'draggable': 'true',
'data-ui-block': 'true'
})

$snippetNode.addClass('fr-draggable');

// Insert the content
this.figures.insert($snippetNode);
}
}
});

generateUniqueComponentSnippetCode = function(componentClass, originalCode, $pageForm) {
var updatedCode = originalCode,
counter = 1,
snippetFound = false;

do {
snippetFound = false;

$('[data-control="richeditor"] textarea', $pageForm).each(function () {
var $textarea = $(this),
$codeDom = $('<div>' + $textarea.val() + '</div>');

if ($codeDom.find('[data-snippet="'+updatedCode+'"][data-component]').length > 0) {
snippetFound = true;
updatedCode = originalCode + counter;
counter++;

return false;
}
});
} while (snippetFound)

return updatedCode
};


/**
* Because the pages-snippets.js is injected after the richeditor script, it will register its
* initialization hooks too late. Here we need to force initialization in order to work with forms
* that are displayed on page load (i.e. Winter.Blog).
*/
$(document).ready(function() {
var $editor = $('[data-control="richeditor"]');

if ($.wn.pagesPage && !window.location.pathname.includes('winter/pages')) {
$.wn.pagesPage.snippetManager.initSnippets($editor);
}
});
})(jQuery);
Loading