Skip to content

Instantly share code, notes, and snippets.

@Reinmar
Created February 17, 2025 16:32
Show Gist options
  • Save Reinmar/e01ccfb644972c7ecafc18ab2cc341ff to your computer and use it in GitHub Desktop.
Save Reinmar/e01ccfb644972c7ecafc18ab2cc341ff to your computer and use it in GitHub Desktop.
Apex component to embed CKEditor 5 in Salesforce
<!-- File: ckeditor5.page -->
<apex:page showHeader="true" sidebar="true">
<head>
<!-- Load your editor’s JavaScript and CSS from the CDN -->
<!-- NOTE: You need to register for Free trial via https://portal.ckeditor.com/signup and
pass the license key below in the editor configuration. -->
<!-- NOTE: We also recommend https://ckeditor.com/ckeditor-5/builder to configure CKEditor and obtain working code snippets. -->
<link rel="stylesheet" href="https://cdn.ckeditor.com/ckeditor5/44.2.0/ckeditor5.css" />
<style>
body {
font-size: 100%;
}
.ck-editor__editable_inline:not(.ck-comment__input *) {
min-height: 300px;
overflow-y: auto;
}
.ck-content h3.category {
font-family: 'Oswald';
font-size: 20px;
font-weight: bold;
color: #555;
letter-spacing: 10px;
margin: 0;
padding: 0;
}
.ck-content h2.document-title {
font-family: 'Oswald';
font-size: 50px;
font-weight: bold;
margin: 0;
padding: 0;
border: 0;
}
.ck-content h3.document-subtitle {
font-family: 'Oswald';
font-size: 20px;
color: #555;
margin: 0 0 1em;
font-weight: bold;
padding: 0;
}
.ck-content p.info-box {
--background-size: 30px;
--background-color: #e91e63;
padding: 1.2em 2em;
border: 1px solid var(--background-color);
background: linear-gradient(
135deg,
var(--background-color) 0%,
var(--background-color) var(--background-size),
transparent var(--background-size)
),
linear-gradient(
135deg,
transparent calc(100% - var(--background-size)),
var(--background-color) calc(100% - var(--background-size)),
var(--background-color)
);
border-radius: 10px;
margin: 1.5em 2em;
box-shadow: 5px 5px 0 #ffe6ef;
}
.ck-content blockquote.side-quote {
font-family: 'Oswald';
font-style: normal;
float: right;
width: 35%;
position: relative;
border: 0;
overflow: visible;
z-index: 1;
margin-left: 1em;
}
.ck-content blockquote.side-quote::before {
content: '“';
position: absolute;
top: -37px;
left: -10px;
display: block;
font-size: 200px;
color: #e7e7e7;
z-index: -1;
line-height: 1;
}
.ck-content blockquote.side-quote p {
font-size: 2em;
line-height: 1;
}
.ck-content blockquote.side-quote p:last-child:not(:first-child) {
font-size: 1.3em;
text-align: right;
color: #555;
}
.ck-content span.marker {
background: yellow;
}
.ck-content span.spoiler {
background: #000;
color: #000;
}
.ck-content span.spoiler:hover {
background: #000;
color: #fff;
}
.ck-content pre.fancy-code {
border: 0;
margin-left: 2em;
margin-right: 2em;
border-radius: 10px;
}
.ck-content pre.fancy-code::before {
content: '';
display: block;
height: 13px;
background: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NCAxMyI+CiAgPGNpcmNsZSBjeD0iNi41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiNGMzZCNUMiLz4KICA8Y2lyY2xlIGN4PSIyNi41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiNGOUJFNEQiLz4KICA8Y2lyY2xlIGN4PSI0Ny41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiM1NkM0NTMiLz4KPC9zdmc+Cg==);
margin-bottom: 8px;
background-repeat: no-repeat;
}
.ck-content pre.fancy-code-dark {
background: #272822;
color:#fff;
box-shadow: 5px 5px 0 #0000001f;
}
.ck-content h1,h2,h3,h4,h5 {
display: block;
font-size: initial;
}
.ck-content h1 {
font-size: 1.8em;
}
.ck-content h2 {
font-size: 1.5em;
}
.ck-content h3 {
font-size: 1.3em;
}
.ck-content h4 {
font-size: 1.2em;
}
.ck-content h5 {
font-size: 1.1em;
}
.ck-content h6 {
font-size: 1em;
}
.button {
background-color: rgb(116, 60, 205);
color: rgb(255, 255, 255);
font-weight: bold;
padding: 10px;
margin-top: 10px;
background-image: none;
}
.button:hover {
cursor: pointer;
background-color: rgb(57, 27, 105);
}
</style>
</head>
<body>
<!-- Container where the editor will render -->
<div id="editor-container" style="min-height: 300px; border: 1px solid #ccc;"></div>
<button id="set-data-button" class="button">Load data</button>
<button id="get-data-button" class="button">Log data to the console</button>
<script src="https://cdn.ckeditor.com/ckeditor5/44.2.0/ckeditor5.umd.js"></script>
<script>
const {
ClassicEditor,
Alignment,
AutoImage,
AutoLink,
Autosave,
BlockQuote,
Bold,
Bookmark,
CloudServices,
Code,
Essentials,
FontBackgroundColor,
FontColor,
FontFamily,
FontSize,
GeneralHtmlSupport,
Heading,
Highlight,
HorizontalLine,
ImageBlock,
ImageCaption,
ImageEditing,
ImageInline,
ImageInsertViaUrl,
ImageResize,
ImageStyle,
ImageTextAlternative,
ImageToolbar,
ImageUpload,
ImageUtils,
Indent,
IndentBlock,
Italic,
Link,
LinkImage,
List,
ListProperties,
Paragraph,
RemoveFormat,
Strikethrough,
Style,
Subscript,
Superscript,
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
TodoList,
Underline
} = window.CKEDITOR;
const container = document.querySelector( '#editor-container' );
ClassicEditor.create( container, {
licenseKey: 'YOUR-TRIAL-LICENSE-KEY',
toolbar: {
items: [
'heading',
'style',
'|',
'fontSize',
'fontFamily',
'fontColor',
'fontBackgroundColor',
'|',
'bold',
'italic',
'underline',
'|',
'link',
'insertTable',
'highlight',
'blockQuote',
'|',
'alignment',
'|',
'bulletedList',
'numberedList',
'todoList',
'outdent',
'indent'
],
shouldNotGroupWhenFull: false
},
plugins: [
Alignment,
AutoImage,
AutoLink,
Autosave,
BlockQuote,
Bold,
Bookmark,
CloudServices,
Code,
Essentials,
FontBackgroundColor,
FontColor,
FontFamily,
FontSize,
GeneralHtmlSupport,
Heading,
Highlight,
HorizontalLine,
ImageBlock,
ImageCaption,
ImageEditing,
ImageInline,
ImageInsertViaUrl,
ImageResize,
ImageStyle,
ImageTextAlternative,
ImageToolbar,
ImageUpload,
ImageUtils,
Indent,
IndentBlock,
Italic,
Link,
LinkImage,
List,
ListProperties,
Paragraph,
RemoveFormat,
Strikethrough,
Style,
Subscript,
Superscript,
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
TodoList,
Underline
],
fontFamily: {
supportAllValues: true
},
fontSize: {
options: [10, 12, 14, 'default', 18, 20, 22],
supportAllValues: true
},
heading: {
options: [
{
model: 'paragraph',
title: 'Paragraph',
class: 'ck-heading_paragraph'
},
{
model: 'heading1',
view: 'h1',
title: 'Heading 1',
class: 'ck-heading_heading1'
},
{
model: 'heading2',
view: 'h2',
title: 'Heading 2',
class: 'ck-heading_heading2'
},
{
model: 'heading3',
view: 'h3',
title: 'Heading 3',
class: 'ck-heading_heading3'
},
{
model: 'heading4',
view: 'h4',
title: 'Heading 4',
class: 'ck-heading_heading4'
},
{
model: 'heading5',
view: 'h5',
title: 'Heading 5',
class: 'ck-heading_heading5'
},
{
model: 'heading6',
view: 'h6',
title: 'Heading 6',
class: 'ck-heading_heading6'
}
]
},
htmlSupport: {
allow: [
{
name: /^.*$/,
styles: true,
attributes: true,
classes: true
}
]
},
image: {
toolbar: [
'toggleImageCaption',
'imageTextAlternative',
'|',
'imageStyle:inline',
'imageStyle:wrapText',
'imageStyle:breakText',
'|',
'resizeImage'
]
},
link: {
addTargetToExternalLinks: true,
defaultProtocol: 'https://',
decorators: {
toggleDownloadable: {
mode: 'manual',
label: 'Downloadable',
attributes: {
download: 'file'
}
}
}
},
list: {
properties: {
styles: true,
startIndex: true,
reversed: true
}
},
menuBar: {
isVisible: true
},
placeholder: 'Type or paste your content here!',
style: {
definitions: [
{
name: 'Article category',
element: 'h3',
classes: ['category']
},
{
name: 'Title',
element: 'h2',
classes: ['document-title']
},
{
name: 'Subtitle',
element: 'h3',
classes: ['document-subtitle']
},
{
name: 'Info box',
element: 'p',
classes: ['info-box']
},
{
name: 'Side quote',
element: 'blockquote',
classes: ['side-quote']
},
{
name: 'Marker',
element: 'span',
classes: ['marker']
},
{
name: 'Spoiler',
element: 'span',
classes: ['spoiler']
},
{
name: 'Code (dark)',
element: 'pre',
classes: ['fancy-code', 'fancy-code-dark']
},
{
name: 'Code (bright)',
element: 'pre',
classes: ['fancy-code', 'fancy-code-bright']
}
]
},
table: {
contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties']
},
initialData: '<p>This is my data. Salesforce integration for CKEditor 5</p>'
}).then(editorInstance => {
console.log('Editor initialized', editorInstance);
window.editor = editorInstance;
const htmlToInsert = `<h2>The three greatest things you learn from traveling</h2><p>Like all the great things on earth traveling teaches us by example. Here are some of the most precious lessons I’ve learned over the years of traveling.</p><figure class="image image-style-side"><img src="https://ckeditor.com/docs/ckeditor5/latest/assets/img/volcano.jpg" alt="A lone wanderer looking at Mount Bromo volcano in Indonesia." srcset="https://ckeditor.com/docs/ckeditor5/latest/assets/img/volcano.jpg, https://ckeditor.com/docs/ckeditor5/latest/assets/img/volcano_2x.jpg 2x" sizes="100vw"><figcaption>Leaving your comfort zone might lead you to such beautiful sceneries like this one.</figcaption></figure><h3>Appreciation of diversity</h3><p>Getting used to an entirely different culture can be challenging. While it’s also nice to learn about cultures online or from books, nothing comes close to experiencing cultural diversity in person. You learn to appreciate each and every single one of the differences while you become more culturally fluid.</p><blockquote><p>The real voyage of discovery consists not in seeking new landscapes, but having new eyes.</p><p><strong>Marcel Proust</strong></p></blockquote><h3>Improvisation</h3><p>Life doesn't allow us to execute every single plan perfectly. This especially seems to be the case when you travel. You plan it down to every minute with a big checklist. But when it comes to executing it, something always comes up and you’re left with your improvising skills. You learn to adapt as you go. Here’s how my travel checklist looks now:</p><ul><li>buy the ticket</li><li>start your adventure</li></ul><figure class="image image-style-side"><img src="https://ckeditor.com/docs/ckeditor5/latest/assets/img/umbrellas.jpg" alt="Three monks ascending the stairs of an ancient temple." srcset="https://ckeditor.com/docs/ckeditor5/latest/assets/img/umbrellas.jpg, https://ckeditor.com/docs/ckeditor5/latest/assets/img/umbrellas_2x.jpg 2x" sizes="100vw"><figcaption>Three monks ascending the stairs of an ancient temple.</figcaption></figure><h3>Confidence</h3><p>Going to a new place can be quite terrifying. While change and uncertainty make us scared, traveling teaches us how ridiculous it is to be afraid of something before it happens. The moment you face your fear and see there is nothing to be afraid of, is the moment you discover bliss.</p>`;
const setDataButton = document.querySelector( '#set-data-button' );
const getDataButton = document.querySelector( '#get-data-button' );
setDataButton.addEventListener( 'click', () => editor.setData( htmlToInsert ) );
getDataButton.addEventListener( 'click', () => console.log( editor.getData() ) );
}).catch(error => {
console.error('Error initializing editor:', error);
});
</script>
</body>
</apex:page>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment