gutenberg-post-submitter/ ├── gutenberg-post-submitter.php # Main plugin file ├── assets/ │ ├── css/ │ │ └── submitter-style.css # CSS for the form │ └── js/ │ └── submitter-script.js # JavaScript for Gutenberg integration
gutenberg-post-submitter.php:
<?php
/**
* Plugin Name: Gutenberg Post Submitter
* Description: Creates a shortcode to display a post submission form with Gutenberg editor for admin users only.
* Version: 1.0.0
* Author: Your Name
* Text Domain: gutenberg-post-submitter
*/
// If this file is called directly, abort.
if (!defined('WPINC')) {
die;
}
// Define plugin constants
define('GUT_POST_SUBMITTER_VERSION', '1.0.0');
define('GUT_POST_SUBMITTER_PLUGIN_DIR', plugin_dir_path(__FILE__));
/**
* The core plugin class
*/
class Gutenberg_Post_Submitter {
/**
* Initialize the plugin
*/
public function __construct() {
// Register shortcode
add_shortcode('gut_post_form', array($this, 'render_post_form'));
// Register scripts and styles
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
// Register REST API endpoints
add_action('rest_api_init', array($this, 'register_rest_routes'));
}
/**
* Enqueue scripts and styles for the frontend
*/
public function enqueue_scripts() {
global $post;
// Only enqueue if the shortcode is present
if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'gut_post_form')) {
// Only enqueue Gutenberg resources for admin users
if (current_user_can('manage_options')) {
// Register and enqueue WordPress packages for Gutenberg
// Core scripts
wp_enqueue_script('wp-element');
wp_enqueue_script('wp-polyfill');
wp_enqueue_script('wp-blocks');
wp_enqueue_script('wp-dom');
wp_enqueue_script('wp-dom-ready');
wp_enqueue_script('wp-components');
wp_enqueue_script('wp-i18n');
wp_enqueue_script('wp-data'); // CRITICAL for editor to function
wp_enqueue_script('wp-compose');
wp_enqueue_script('wp-block-editor');
wp_enqueue_script('wp-format-library');
// Core styles
wp_enqueue_style('wp-edit-blocks');
wp_enqueue_style('wp-components');
wp_enqueue_style('wp-editor');
wp_enqueue_style('wp-block-editor');
wp_enqueue_style('wp-edit-post');
wp_enqueue_style('wp-format-library');
// Add editor styles to match your theme
wp_add_inline_style('wp-edit-blocks', '
.editor-styles-wrapper {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
padding: 20px;
}
.wp-block {
max-width: 100%;
}
/* Fix contenteditable issues */
.block-list-appender {
position: relative !important;
display: flex !important;
pointer-events: all !important;
cursor: text !important;
}
.block-editor-writing-flow {
display: block !important;
pointer-events: auto !important;
}
[contenteditable="false"] {
user-select: auto !important;
-webkit-user-select: auto !important;
}
.block-editor-block-list__layout .block-editor-block-list__block {
pointer-events: all !important;
}
');
// Add debugging message to console
wp_add_inline_script('wp-element', '
console.log("Gutenberg editor scripts loaded");
console.log("wp object available:", typeof wp !== "undefined");
if (typeof wp !== "undefined") {
console.log(" wp.element available:", typeof wp.element !== "undefined");
console.log(" wp.blocks available:", typeof wp.blocks !== "undefined");
console.log(" wp.blockEditor available:", typeof wp.blockEditor !== "undefined");
console.log(" wp.data available:", typeof wp.data !== "undefined");
}
');
// Custom script
wp_enqueue_script(
'gut-post-submitter-script',
plugins_url('assets/js/submitter-script.js', __FILE__),
array(
'wp-element',
'wp-blocks',
'wp-block-editor',
'wp-data',
'wp-components',
'jquery'
),
GUT_POST_SUBMITTER_VERSION,
true // Load in footer
);
// Add inline script to fix contenteditable issues
wp_add_inline_script('gut-post-submitter-script', '
// Fix contenteditable issues on load
document.addEventListener("DOMContentLoaded", function() {
// Wait a moment for the editor to initialize
setTimeout(function() {
// Force all contenteditable=false elements to be editable
const nonEditables = document.querySelectorAll("[contenteditable=\'false\']");
for(let i = 0; i < nonEditables.length; i++) {
nonEditables[i].removeAttribute("contenteditable");
nonEditables[i].style.pointerEvents = "auto";
}
console.log("Fixed " + nonEditables.length + " contenteditable elements");
}, 1000);
});
');
// Custom style
wp_enqueue_style(
'gut-post-submitter-style',
plugins_url('assets/css/submitter-style.css', __FILE__),
array('wp-edit-blocks', 'wp-components', 'wp-editor'),
GUT_POST_SUBMITTER_VERSION
);
// Localize script with REST API info
wp_localize_script(
'gut-post-submitter-script',
'gutPostSubmitter',
array(
'restUrl' => esc_url_raw(rest_url('gut-post-submitter/v1/submit')),
'nonce' => wp_create_nonce('wp_rest'),
'currentUser' => get_current_user_id(),
'debugMode' => defined('WP_DEBUG') && WP_DEBUG,
'allowedBlocks' => true,
'hasFixedToolbar' => true,
'hasUploadPermissions' => current_user_can('upload_files'),
'editorSettings' => array(
'__experimentalSetIsInserterOpened' => true,
'isRTL' => is_rtl(),
'disableCustomColors' => false,
'disableCustomFontSizes' => false,
),
)
);
}
}
}
/**
* Register REST API routes
*/
public function register_rest_routes() {
register_rest_route('gut-post-submitter/v1', '/submit', array(
'methods' => 'POST',
'callback' => array($this, 'handle_post_submission'),
'permission_callback' => array($this, 'check_permission')
));
}
/**
* Check if user has permission to submit posts
*/
public function check_permission() {
return current_user_can('manage_options');
}
/**
* Render the post submission form
*/
public function render_post_form() {
// Check if user is admin
if (!current_user_can('manage_options')) {
return '<p>' . esc_html__('You must be an administrator to access this form.', 'gutenberg-post-submitter') . '</p>';
}
ob_start();
?>
<div class="gut-post-form-container">
<form id="gut-post-form">
<div class="form-group">
<label for="gut-post-title"><?php esc_html_e('Post Title', 'gutenberg-post-submitter'); ?></label>
<input type="text" id="gut-post-title" name="post_title" required>
</div>
<div class="form-group">
<label for="gut-post-content"><?php esc_html_e('Post Content', 'gutenberg-post-submitter'); ?></label>
<div id="gut-editor-container"></div>
</div>
<div class="form-group">
<button type="submit" class="gut-submit-button"><?php esc_html_e('Submit Post', 'gutenberg-post-submitter'); ?></button>
</div>
<div id="gut-form-messages"></div>
</form>
</div>
<?php
return ob_get_clean();
}
/**
* Handle the post submission via REST API
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_REST_Response
*/
public function handle_post_submission($request) {
// Get request parameters
$params = $request->get_json_params();
// Validate input
if (empty($params['title']) || empty($params['content'])) {
return new WP_REST_Response(array(
'success' => false,
'message' => 'Title and content are required.'
), 400);
}
// Sanitize input
$title = sanitize_text_field($params['title']);
$content = wp_kses_post($params['content']);
// Create post
$post_data = array(
'post_title' => $title,
'post_content' => $content,
'post_status' => 'draft',
'post_author' => get_current_user_id(),
'post_type' => 'post'
);
$post_id = wp_insert_post($post_data);
if ($post_id) {
return new WP_REST_Response(array(
'success' => true,
'message' => 'Post created successfully!',
'post_id' => $post_id,
'edit_url' => get_edit_post_link($post_id, 'raw')
), 201);
} else {
return new WP_REST_Response(array(
'success' => false,
'message' => 'Error creating post.'
), 500);
}
}
}
// Initialize the plugin
$gutenberg_post_submitter = new Gutenberg_Post_Submitter();
submitter-script.js —
/**
* Gutenberg Post Submitter Script
* Integrates the Gutenberg editor into the frontend submission form
*/
;(function ($) {
"use strict"
// Wait for DOM to be fully loaded
$(document).ready(function () {
// Check if we're on the right page with the editor container
const editorContainer = document.getElementById("gut-editor-container")
if (!editorContainer) {
console.error("Editor container not found - cannot initialize Gutenberg")
return
}
// Check if all required WordPress packages are available
if (typeof wp === "undefined" || !wp.element || !wp.blockEditor || !wp.blocks || !wp.data) {
console.error("Required WordPress packages not loaded. Make sure all required scripts are enqueued.")
console.log("Available wp object:", wp)
return
}
console.log("Starting Gutenberg editor initialization...")
// Initialize variables
let editorData
// Initialize Gutenberg editor
initGutenbergEditor()
// Handle form submission
$("#gut-post-form").on("submit", function (e) {
e.preventDefault()
submitPost()
})
/**
* Fix contenteditable issues
*/
function fixContentEditableIssues() {
// Force the block-list-appender to be editable
const appenders = document.querySelectorAll(".block-list-appender")
if (appenders.length) {
appenders.forEach(appender => {
// Remove contenteditable="false" from appenders
appender.removeAttribute("contenteditable")
appender.style.pointerEvents = "auto"
appender.style.cursor = "pointer"
})
console.log("Fixed contenteditable issues on block appenders")
}
// Also fix any other non-editable blocks
const nonEditableBlocks = document.querySelectorAll('[contenteditable="false"]')
if (nonEditableBlocks.length) {
nonEditableBlocks.forEach(block => {
block.removeAttribute("contenteditable")
block.style.pointerEvents = "auto"
})
console.log("Fixed " + nonEditableBlocks.length + " contenteditable elements")
}
}
/**
* Force editor interaction by programmatically inserting blocks
*/
function forceEditorInteraction() {
// Simulate a click on the block appender to make it interactive
const appender = document.querySelector(".block-list-appender")
if (appender) {
// Force it to be a proper block
appender.style.border = "1px dashed #ccc"
appender.style.padding = "8px"
appender.style.marginBottom = "20px"
appender.style.minHeight = "40px"
// Try to create a paragraph block programmatically
try {
// Insert a default block through the WordPress data system
const { dispatch } = wp.data
dispatch("core/block-editor").insertBlocks(
wp.blocks.createBlock("core/paragraph", {
content: "Click here to start editing..."
})
)
console.log("Inserted default paragraph block")
} catch (error) {
console.error("Could not insert default block:", error)
}
}
}
/**
* Initialize the Gutenberg editor
*/
function initGutenbergEditor() {
// Get WordPress editor components
const { createElement, Fragment } = wp.element
const { BlockEditorProvider, BlockList, WritingFlow, ObserveTyping } = wp.blockEditor
const { Popover, SlotFillProvider } = wpponents
// Change this line in the initGutenbergEditor function:
const initialBlocks = wp.blocks.parse("<!-- wp:paragraph --><p>Start typing here...</p><!-- /wp:paragraph -->")
// Create editor component using React function component for simplicity
function Editor() {
// Use state hook for blocks
const [blocks, setBlocks] = wp.element.useState(initialBlocks)
// Update our reference to blocks for form submission
editorData = blocks
// Use effect hook to fix contenteditable issues after render
wp.element.useEffect(() => {
// Fix contenteditable issues after editor is rendered
setTimeout(fixContentEditableIssues, 100)
// Also set up a mutation observer to fix issues as DOM changes
const observer = new MutationObserver(mutations => {
fixContentEditableIssues()
})
// Start observing the editor container
observer.observe(editorContainer, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["contenteditable"]
})
// Cleanup function to disconnect observer when unmounting
return () => observer.disconnect()
}, [])
return createElement(
Fragment,
null,
createElement(
SlotFillProvider,
null,
createElement(
BlockEditorProvider,
{
value: blocks,
onInput: setBlocks,
onChange: setBlocks
},
createElement("div", { className: "editor-styles-wrapper" }, createElement(WritingFlow, null, createElement(ObserveTyping, null, createElement(BlockList, null)))),
createElement(Popover.Slot, null)
)
)
)
}
// Render the editor
try {
wp.element.render(createElement(Editor, null), editorContainer)
console.log("Gutenberg editor rendered successfully")
// Fix contenteditable issues after a moment
setTimeout(fixContentEditableIssues, 500)
// Force editor interaction after editor is fully loaded
setTimeout(forceEditorInteraction, 800)
} catch (error) {
console.error("Failed to render Gutenberg editor:", error)
}
}
/**
* Submit the post to WordPress using REST API
*/
function submitPost() {
// Get post title
const title = $("#gut-post-title").val()
// Validate title
if (!title) {
showMessage("Please enter a post title", "error")
return
}
// Get Gutenberg content
const content = editorData ? wp.blocks.serialize(editorData) : ""
// Validate content
if (!content || content.trim() === "") {
showMessage("Please add some content to your post", "error")
return
}
// Show loading state
showMessage("Submitting post...", "info")
// Debug log
if (gutPostSubmitter.debugMode) {
console.log("Submitting post data:", { title, content })
console.log("REST URL:", gutPostSubmitter.restUrl)
}
// Send data to REST API
fetch(gutPostSubmitter.restUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-WP-Nonce": gutPostSubmitter.nonce
},
body: JSON.stringify({
title: title,
content: content
})
})
.then(function (response) {
if (!response.ok) {
if (gutPostSubmitter.debugMode) {
console.error("Response not OK:", response)
}
throw new Error("Network response was not ok: " + response.status)
}
return response.json()
})
.then(function (responseData) {
if (gutPostSubmitter.debugMode) {
console.log("Response data:", responseData)
}
if (responseData.success) {
// Show success message
showMessage(responseData.message, "success")
// Clear form
$("#gut-post-title").val("")
// Reset editor - reinitialize with empty content
initGutenbergEditor()
// Add edit link
if (responseData.edit_url) {
$("#gut-form-messages").append('<p><a href="' + responseData.edit_url + '" target="_blank">Edit this post in admin</a></p>')
}
} else {
showMessage(responseData.message || "Error submitting post", "error")
}
})
.catch(function (error) {
console.error("Error:", error)
showMessage("An error occurred while submitting the post: " + error.message, "error")
})
}
/**
* Display a message to the user
*/
function showMessage(message, type) {
const messageContainer = $("#gut-form-messages")
// Clear previous messages
messageContainer.html("")
// Add new message
messageContainer.append('<div class="gut-message gut-message-' + type + '">' + message + "</div>")
// Scroll to message
$("html, body").animate(
{
scrollTop: messageContainer.offset().top - 100
},
200
)
}
})
})(jQuery)
The DOM output is this:
<div class="form-group">
<label for="gut-post-content">Post Content</label>
<div id="gut-editor-container"><div class="editor-styles-wrapper"><div tabindex="0"></div><div class="block-editor-writing-flow" tabindex="0" data-has-multi-selection="false"><div><div class="is-root-container block-editor-block-list__layout" data-is-drop-zone="true"><div tabindex="-1" class="block-list-appender wp-block" data-block="true" style="pointer-events: auto; cursor: pointer; border: 1px dashed rgb(204, 204, 204); padding: 8px; margin-bottom: 20px; min-height: 40px;"></div></div></div></div><div tabindex="0"></div></div><div class="popover-slot css-0 e19lxcc00"></div></div>
</div>
Still Can't type anything in that FRONT end editor. In front end it looks like this —