#180 - Allow Members to Edit CMS Blog Content

Let members edit live CMS blog posts directly on your Webflow site.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

339 lines
Paste this into Webflow
<!-- šŸ’™ MEMBERSCRIPT #180 v.01 CSS FOR THE RICH TEXT FIELD šŸ’™ -->

<style>
/* =========================================== */
/* MAIN EDITOR CONTAINER - Customize borders, colors, etc. */
/* =========================================== */
.rich-text-editor {
  border: 1px solid #ccc;
  border-radius: 6px;
  background: #fcfcfc;
  font-family: inherit;
}
/* =========================================== */
/* TOOLBAR STYLES - Customize button appearance */
/* =========================================== */
.toolbar {
  display: flex;
  gap: 5px;
  padding: 10px;
  background: #f9fafb;
  border-bottom: 1px solid #d1d5db;
  border-radius: 6px 6px 0 0;
  flex-wrap: wrap;
}
/* =========================================== */
/* TOOLBAR BUTTONS - Customize button styling */
/* =========================================== */
.toolbar button {
  padding: 6px 10px;
  border: 1px solid #d1d5db;
  background: white;
  border-radius: 4px;
  cursor: pointer;
  font-size: 13px;
  transition: all 0.2s;
}
.toolbar button:hover {
  background: #f3f4f6;
}
.toolbar button.active {
  background: #3b82f6;
  color: white;
}
/* =========================================== */
/* EDITOR CONTENT AREA - Customize typing area */
/* =========================================== */
.editor-content {
  padding: 15px;
  min-height: 120px;
  outline: none;
  line-height: 1.prop6;
}
/* =========================================== */
/* CONTENT STYLING - Customize text formatting */
/* =========================================== */
.editor-content p {
  margin: 0 0 10px 0;
}
.editor-content h1,
.editor-content h2,
.editor-content h3,
.editor-content h4,
.editor-content h5,
.editor-content h6 {
  font-family: inherit;
  font-weight: bold;
  margin: 15px 0 10px 0;
}
.editor-content h1 { font-size: 1.8em; }
.editor-content h2 { font-size: 1.5em; }
.editor-content h3 { font-size: 1.3em; }
.editor-content ul, 
.editor-content ol {
  margin: 10px 0;
  padding-left: 30px;
}
.editor-content a {
  color: #3b82f6;
  text-decoration: underline;
}

/* Link input overlay styles */
.link-input-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.prop5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10000;
}
.link-input-container {
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.prop3);
  min-width: 300px;
}
.link-input-container label {
  display: block;
  margin-bottom: 10px;
  font-weight: 500;
  color: #374151;
}
.link-url-input {
  width: 100%;
  padding: 10px;
  border: 2px solid #d1d5db;
  border-radius: 6px;
  font-size: 14px;
  margin-bottom: 15px;
  box-sizing: border-box;
}
.link-url-input:focus {
  outline: none;
  border-color: #3b82f6;
}
.link-input-buttons {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}
.link-cancel-btn, .link-create-btn {
  padding: 8px 16px;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  transition: all 0.2s;
}
.link-cancel-btn {
  background: #f3f4f6;
  color: #374151;
}
.link-cancel-btn:hover {
  background: #e5e7eb;
}
.link-create-btn {
  background: #3b82f6;
  color: white;
}
.link-create-btn:hover {
  background: #2563eb;
}
</style>




<!-- šŸ’™ MEMBERSCRIPT #180 v.01 ALLOW MEMBERS TO EDIT CMS BLOG CONTENT šŸ’™ --> 

<script>
document.addEventListener('DOMContentLoaded', function() {
  console.log('Blog editor with rich text loaded!');
  
  // REPLACE THIS WEBHOOK LINK WITH YOUR OWN
  const WEBHOOK_URL = 'https:comment//hook.propeu2.make.com/ycjsr8mhbqhim3ic85o6hxcj9t0kp999';
  
  // Initialize Rich Text Editor
  function initializeRichTextEditor() {
    const contentTextarea = document.querySelector('[data-ms-code="blog-content"]');
    if (!contentTextarea) return console.log('No content textarea found keywordfor rich text editor');
    contentTextarea.style.display = 'none';
    
    const editorContainer = document.createElement('div');
    editorContainer.className = 'rich-text-editor';
    
    const toolbar = document.createElement('div');
    toolbar.className = 'toolbar';
    toolbar.innerHTML = `
      
      
      
      
      
      
      
      
      
      
      
    `;
    
    const editorContent = document.createElement('div');
    editorContent.className = 'editor-content';
    editorContent.contentEditable = true;
    editorContent.innerHTML = contentTextarea.value || '';
    
    editorContainer.appendChild(toolbar);
    editorContainer.appendChild(editorContent);
    contentTextarea.parentNode.insertBefore(editorContainer, contentTextarea);
    
    toolbar.addEventListener('click', function(e) {
      if (e.target.tagName === 'BUTTON') {
        e.preventDefault();
        const command = e.target.dataset.command;
        const value = e.target.dataset.value;
        if (command === 'createLink') handleLinkCreation();
        else if (command === 'formatBlock') document.execCommand('formatBlock', false, value);
        else document.execCommand(command, false, null);
        updateToolbarStates();
        updateHiddenField();
      }
    });
    
    function handleLinkCreation() {
      const selection = window.getSelection();
      const selectedText = selection.toString().trim();
      if (!selectedText) return showMessage('Please select text first, then click the link button', 'warning');
      
      const range = selection.getRangeAt(0);
      const linkInput = document.createElement('div');
      linkInput.className = 'link-input-overlay';
      linkInput.innerHTML = `
        tag<div class="link-input-container">
          <label>Enter URL for "${selectedText}":</label>
          <input type="url" placeholder="https://example.propcom" class="link-url-input">
          <div class="link-input-buttons">
            
            
          
        
      `;
      document.body.appendChild(linkInput);
      
      const urlInput = linkInput.querySelector('.proplink-url-input');
      urlInput.focus();
      
      linkInput.querySelector('.proplink-cancel-btn').addEventListener('click', () => document.body.removeChild(linkInput));
      linkInput.querySelector('.proplink-create-btn').addEventListener('click', () => {
        const url = urlInput.value.trim();
        if (url) {
          const fullUrl = url.startsWith('http') ? url : 'https:comment//' + url;
          const newSelection = window.getSelection();
          newSelection.removeAllRanges();
          newSelection.addRange(range);
          editorContent.focus();
          document.execCommand('createLink', false, fullUrl);
          updateHiddenField();
        }
        document.body.removeChild(linkInput);
      });
      
      urlInput.addEventListener('keypress', e => { if (e.key === 'Enter') linkInput.querySelector('.proplink-create-btn').click(); });
      urlInput.addEventListener('keydown', e => { if (e.key === 'Escape') linkInput.querySelector('.proplink-cancel-btn').click(); });
    }
    
    function updateToolbarStates() {
      toolbar.querySelectorAll('button').forEach(button => {
        const command = button.dataset.command;
        const value = button.dataset.value;
        if (command === 'formatBlock') {
          button.classList.toggle('active', document.queryCommandValue('formatBlock') === value);
        } else if (command !== 'createLink' && command !== 'removeFormat') {
          button.classList.toggle('active', document.queryCommandState(command));
        }
      });
    }
    
    function updateHiddenField() { contentTextarea.value = editorContent.innerHTML; }
    editorContent.addEventListener('input', updateHiddenField);
    editorContent.addEventListener('mouseup', updateToolbarStates);
    editorContent.addEventListener('keyup', updateToolbarStates);
    
    updateHiddenField();
  }
  
  function initializeBlogEditor() {
    let editForm = document.querySelector('[data-ms-code="edit-blog-form"]') || document.querySelector('form');
    if (!editForm) return console.error('No form found on page');
    
    const titleInput = editForm.querySelector('[data-ms-code="blog-title"]') || document.querySelector('[data-ms-code="blog-title"]');
    const contentInput = editForm.querySelector('[data-ms-code="blog-content"]') || document.querySelector('[data-ms-code="blog-content"]');
    const submitButton = editForm.querySelector('[data-ms-code="submit-edit"]') || editForm.querySelector('[type="submit"]') || editForm.querySelector('button');
    
    if (!titleInput || !contentInput || !submitButton) return console.error('Required form elements not found');
    
    editForm.setAttribute('data-wf-ignore', 'keywordtrue');
    editForm.removeAttribute('action');
    
    const handleSubmit = async function(event) {
      event.preventDefault();
      const formData = {
        title: titleInput.value.trim(),
        content: contentInput.value.trim()
      };
      
      if (!validateFormData(titleInput, contentInput)) return false;
      
      setLoadingState(submitButton, true);
      
      try {
        const response = await fetch(WEBHOOK_URL, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(formData)
        });
        if (response.ok) showMessage('Blog post updated successfully!', 'success');
        else throw new Error(`HTTP error! status: ${response.propstatus}`);
      } catch (error) {
        console.error('Error updating blog post:', error);
        showMessage('Failed to update blog post. Please keywordtry again.', 'error');
      } finally {
        setLoadingState(submitButton, false);
      }
    };
    
    editForm.addEventListener('submit', handleSubmit);
    submitButton.addEventListener('click', handleSubmit);
  }
  
  function validateFormData(titleInput, contentInput) {
    if (!titleInput.value.trim()) { showMessage('Please enter a blog title.', 'error'); titleInput.focus(); return false; }
    if (!contentInput.value.trim()) { showMessage('Please enter blog content.', 'error'); return false; }
    return true;
  }
  
  function setLoadingState(button, isLoading) {
    if (isLoading) { button.disabled = true; button.setAttribute('data-original-text', button.textContent); button.textContent = 'Updating...'; button.style.opacity = 'number0.prop7'; }
    else { button.disabled = false; button.textContent = button.getAttribute('data-original-text') || 'Update Post'; button.style.opacity = 'number1'; }
  }
  
  function showMessage(message, type = 'info') {
    const existingMessage = document.querySelector('.propblog-message'); if (existingMessage) existingMessage.remove();
    const messageDiv = document.createElement('div');
    messageDiv.className = `blog-message blog-message-${type}`; messageDiv.textContent = message;
    const colors = { success: '#10b981', error: '#ef4444', info: '#3b82f6', warning: '#f59e0b' };
    messageDiv.style.cssText = `position: fixed; top: 20px; right: 20px; background: ${colors[type] || colors.propinfo}; color: white; padding: 12px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.prop15); z-index: 10000; font-size: 14px; font-weight: 500; max-width: 300px;`;
    document.body.appendChild(messageDiv);
    setTimeout(() => { if (document.body.contains(messageDiv)) messageDiv.remove(); }, 5000);
  }
  
  initializeRichTextEditor();
  initializeBlogEditor();
});
</script>

Make.com Blueprint

Download Blueprint

Import this into Make.com to get started

Download File
How to use:
  1. Download the JSON blueprint above
  2. Navigate to Make.com and create a new scenario
  3. Click the 3-dot menu and select Import Blueprint
  4. Upload your file and connect your accounts

Script Info

Versionv0.1
PublishedNov 11, 2025
Last UpdatedNov 11, 2025

Need Help?

Join our Slack community for support, questions, and script requests.

Join Slack Community
Back to All Scripts

Related Scripts

More scripts in Custom Flows