<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: CKEditor</title>
    <description>The latest articles on Forem by CKEditor (@ckeditor).</description>
    <link>https://forem.com/ckeditor</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2059%2F560da4e4-70e8-4a52-8cb6-b9f0b4398147.png</url>
      <title>Forem: CKEditor</title>
      <link>https://forem.com/ckeditor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ckeditor"/>
    <language>en</language>
    <item>
      <title>DrupalCon Chicago 2026: How I vibe-coded my first custom Drupal module extending CKEditor</title>
      <dc:creator>Ondřej Chrastina</dc:creator>
      <pubDate>Wed, 01 Apr 2026 14:58:57 +0000</pubDate>
      <link>https://forem.com/ckeditor/drupalcon-chicago-2026-how-i-vibe-coded-my-first-custom-drupal-module-extending-ckeditor-59mn</link>
      <guid>https://forem.com/ckeditor/drupalcon-chicago-2026-how-i-vibe-coded-my-first-custom-drupal-module-extending-ckeditor-59mn</guid>
      <description>&lt;p&gt;I was at &lt;a href="https://events.drupal.org/chicago2026" rel="noopener noreferrer"&gt;DrupalCon Chicago 2026&lt;/a&gt;, juggling booth duty, an AI Summit talk, and a Lightning Talk about CKEditor's contrib modules. &lt;strong&gt;But it's those hallway conversations that stick with you – and this time, the same question kept coming up.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;"How do I customize CKEditor just a little bit?"&lt;/li&gt;
&lt;li&gt;"Can I add my own button?"&lt;/li&gt;
&lt;li&gt;"I want something specific to our workflow, but where do I even start?"&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;People knew what they wanted. They just didn't know how to get there.&lt;/p&gt;

&lt;p&gt;So I grabbed &lt;a href="https://www.drupal.org/u/salmonek" rel="noopener noreferrer"&gt;Wojtek Kukowski&lt;/a&gt; – the main maintainer of the CKEditor Drupal modules (&lt;a href="https://www.drupal.org/project/ckeditor5_plugin_pack" rel="noopener noreferrer"&gt;CKEditor 5 Plugin Pack&lt;/a&gt; and &lt;a href="https://www.drupal.org/project/ckeditor5_premium_features" rel="noopener noreferrer"&gt;CKEditor 5 Premium Features&lt;/a&gt;), and asked him:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What's actually required to make a CKEditor plugin extension for Drupal?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He briefed me on the essentials: create a Drupal module that wraps a CKEditor plugin. And I thought: "Let me try to translate this into a prompt for &lt;a href="https://docs.anthropic.com/en/docs/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; and include the official docs and see what happens."&lt;/p&gt;

&lt;h2&gt;
  
  
  The experiment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Drupal&lt;/strong&gt;: 11.3.3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool&lt;/strong&gt;: Claude Code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goal&lt;/strong&gt;: Create a Drupal module with a CKEditor 5 plugin from scratch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The prompt (almost too simple)
&lt;/h3&gt;

&lt;p&gt;I kept it minimal:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I would like to register a new module for Drupal based on the &lt;a href="https://www.drupal.org/docs/develop/creating-modules" rel="noopener noreferrer"&gt;https://www.drupal.org/docs/develop/creating-modules&lt;/a&gt; information. I would like the module to be a CKEditor plugin &lt;a href="https://ckeditor.com/docs/ckeditor5/latest/framework/tutorials/creating-simple-plugin-timestamp.html" rel="noopener noreferrer"&gt;https://ckeditor.com/docs/ckeditor5/latest/framework/tutorials/creating-simple-plugin-timestamp.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are not sure, ask me.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it. Two documentation links and an invitation to ask questions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happened
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Claude explored the codebase&lt;/strong&gt; – I was prompting in the local project with CKEditor AI running. It found existing CKEditor modules (&lt;a href="https://www.drupal.org/project/ckeditor5_plugin_pack" rel="noopener noreferrer"&gt;CKEditor 5 Plugin Pack&lt;/a&gt; and &lt;a href="https://www.drupal.org/project/ckeditor5_premium_features" rel="noopener noreferrer"&gt;CKEditor 5 Premium Features&lt;/a&gt; in my case) and learned from their patterns.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Claude asked clarifying questions&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What should the plugin do? (Insert timestamp.)&lt;/li&gt;
&lt;li&gt;What should the module be called? (&lt;code&gt;ckeditor5_timestamp&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Toolbar button or context menu? (Toolbar button.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Claude created all the files&lt;/strong&gt; – module metadata, CKEditor configuration, JavaScript plugin, PHP class. Everything.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Time&lt;/strong&gt;: A couple of minutes.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let me show you what it generated.&lt;/p&gt;

&lt;h2&gt;
  
  
  The files Claude created
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;All in &lt;code&gt;~/web/modules/custom/ckeditor5_timestamp&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Module metadata
&lt;/h3&gt;

&lt;p&gt;The standard &lt;code&gt;.info.yml&lt;/code&gt; that tells Drupal about your module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CKEditor 5 Timestamp&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;module&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Adds&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CKEditor&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;plugin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;that&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;inserts&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;current&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cursor&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;position."&lt;/span&gt;
&lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CKEditor&lt;/span&gt;
&lt;span class="na"&gt;core_version_requirement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^10.1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;^11"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CKEditor plugin definition
&lt;/h3&gt;

&lt;p&gt;This is where the magic happens – the &lt;code&gt;.ckeditor5.yml&lt;/code&gt; file that registers your plugin with Drupal's CKEditor integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ckeditor5_timestamp_timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ckeditor5&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;timestamp.Timestamp&lt;/span&gt;
  &lt;span class="na"&gt;drupal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Timestamp&lt;/span&gt;
    &lt;span class="na"&gt;library&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ckeditor5_timestamp/timestamp&lt;/span&gt;
    &lt;span class="na"&gt;toolbar_items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Timestamp&lt;/span&gt;
    &lt;span class="na"&gt;elements&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  JavaScript plugin
&lt;/h3&gt;

&lt;p&gt;The actual CKEditor 5 plugin that creates the toolbar button and handles the timestamp insertion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Plugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ckeditor5/src/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ButtonView&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ckeditor5/src/ui&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Timestamp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timestamp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ButtonView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Timestamp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;withText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;tooltip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;execute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;change&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;editing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Timestamp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Timestamp&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PHP plugin class
&lt;/h3&gt;

&lt;p&gt;A minimal PHP class that extends Drupal's CKEditor plugin system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Drupal\ckeditor5_timestamp\Plugin\CKEditor5Plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Drupal\ckeditor5\Plugin\CKEditor5PluginDefault&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * CKEditor 5 Timestamp plugin.
 */&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Timestamp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;CKEditor5PluginDefault&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The gotcha (there's always one)
&lt;/h2&gt;

&lt;p&gt;I enabled the module, cleared the cache, added the button to a text format... and hit this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CKEditorError: plugincollection-plugin-not-found {"plugin":null}
Read more: https://ckeditor.com/docs/ckeditor5/latest/support/error-codes.html#error-plugincollection-plugin-not-found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipugutm2q1qbiw3l1kdp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipugutm2q1qbiw3l1kdp.png" alt="console with error" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;p&gt;Drupal's CKEditor 5 uses a &lt;strong&gt;DLL (Dynamic Link Library) pattern&lt;/strong&gt; – webpack-specific builds that register plugins to the global &lt;code&gt;CKEditor5&lt;/code&gt; variable. Raw ES modules don't work – plugins must be bundled and exported to the &lt;code&gt;CKEditor5.[pluginname]&lt;/code&gt; namespace.&lt;/p&gt;

&lt;p&gt;Drupal core is discussing an &lt;a href="https://www.drupal.org/node/3398525" rel="noopener noreferrer"&gt;Import Maps API&lt;/a&gt; that might let browsers resolve ES module imports natively – no bundling to UMD required. If import maps land in Drupal core, you'll be able to write clean &lt;code&gt;import { Plugin } from 'ckeditor5/src/core'&lt;/code&gt; statements and have the browser figure out the rest. &lt;a href="https://caniuse.com/?search=import+map" rel="noopener noreferrer"&gt;Browser support for import maps&lt;/a&gt; is getting there too: Chrome 133+ and Safari already support multiple import maps, with Firefox ESR 153 expected around July 2026.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix
&lt;/h3&gt;

&lt;p&gt;Claude created a properly bundled version in &lt;code&gt;js/build/timestamp.js&lt;/code&gt; and updated the library configuration to point to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;js&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;js/build/timestamp.js&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;minified&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;true&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;core/ckeditor5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Total additional time&lt;/strong&gt;: About 2 minutes.&lt;/p&gt;

&lt;p&gt;Voila! The timestamp button appeared in the toolbar, and clicking it inserted the current date and time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmq8wlugim1ycgekyf0kn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmq8wlugim1ycgekyf0kn.png" alt="Timestamp plugin showcase" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9qm1zrp69dfehrk3s1rs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9qm1zrp69dfehrk3s1rs.png" alt="Timestamp diagram flow" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;p&gt;Two files that can help you extend CKEditor:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SKILL.md&lt;/strong&gt; – A comprehensive guide teaching Claude Code how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explore existing CKEditor implementations.&lt;/li&gt;
&lt;li&gt;Ask the right clarifying questions.&lt;/li&gt;
&lt;li&gt;Create all necessary files.&lt;/li&gt;
&lt;li&gt;Handle the DLL bundling requirement.&lt;/li&gt;
&lt;li&gt;Troubleshoot common issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PROMPT.md&lt;/strong&gt; – A template you can customize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace the plugin requirements with yours.&lt;/li&gt;
&lt;li&gt;Specify your module name.&lt;/li&gt;
&lt;li&gt;Define the UI type you need.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to use it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Install Claude Code&lt;/strong&gt;: See the &lt;a href="https://docs.anthropic.com/en/docs/claude-code" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Put the files in your Drupal project&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Adjust PROMPT.md&lt;/strong&gt; with your requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plugin function: What should it do?&lt;/li&gt;
&lt;li&gt;Module name: your_module_name.&lt;/li&gt;
&lt;li&gt;UI type: Toolbar button, context menu, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ask Claude to run it&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please create a CKEditor 5 plugin for Drupal following the SKILL.md approach using the requirements in PROMPT.md&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enable and configure&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   drush en your_module_name
   drush cr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure the text format in Drupal admin to add your new button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;My plugin was straightforward – a simple timestamp inserter. I'm curious how it works for you with more complex requirements.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did it work on the first try?&lt;/li&gt;
&lt;li&gt;What gotchas did you encounter?&lt;/li&gt;
&lt;li&gt;How complex was your plugin?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can check out the files like &lt;code&gt;PROMPT.md&lt;/code&gt; and &lt;code&gt;SKILL.md&lt;/code&gt; as well as the module files in the &lt;a href="https://github.com/Simply007/drupalcon-chicago-26-ckeditor-ai-playground" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond the plugin
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How the SKILL.md and PROMPT.md came to be
&lt;/h3&gt;

&lt;p&gt;The same "reverse engineering" approach I used for the webpack configuration also created the SKILL.md and PROMPT.md files. After Claude successfully built the plugin, I backtracked through its actions – what did it explore? What questions did it ask? What files did it create? – and distilled that into reusable instructions.&lt;/p&gt;

&lt;p&gt;I do this whenever I need repeatable context. Instead of relying solely on a project-wide CLAUDE.md file, I create focused instruction files for specific tasks. The SKILL.md teaches Claude &lt;em&gt;how&lt;/em&gt; to build CKEditor plugins; the PROMPT.md provides the &lt;em&gt;what&lt;/em&gt; – the specific requirements for each new plugin.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI for the build
&lt;/h3&gt;

&lt;p&gt;Similarly, I asked Claude to generate a GitHub Actions workflow that builds the JavaScript on every push to main. If the bundled output changes, the action commits it automatically. This keeps the repository's &lt;code&gt;js/build/timestamp.js&lt;/code&gt; in sync without manual intervention – useful for contributors who might not have Node.js set up locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making it community-friendly
&lt;/h3&gt;

&lt;p&gt;To make this repository truly accessible as a starting point, more work would be needed: proper documentation, contribution guidelines, issue templates, and licensing clarity. I maintain a template for this at &lt;a href="https://github.com/Simply007/os-guidelines" rel="noopener noreferrer"&gt;os-guidelines&lt;/a&gt; – a checklist covering everything from repository naming to CI automation across different tech stacks. If you're publishing your own CKEditor plugin module, consider using it as a starting point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to learn more?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create your own plugin&lt;/strong&gt;: Check out the &lt;a href="https://ckeditor.com/docs/ckeditor5/latest/framework/tutorials/creating-simple-plugin-timestamp.html" rel="noopener noreferrer"&gt;CKEditor 5 plugin development guide&lt;/a&gt; for the full tutorial on building custom plugins from scratch. Once you're comfortable with the basics, explore more capabilities – &lt;a href="https://ckeditor.com/docs/ckeditor5/latest/getting-started/setup/toolbar.html" rel="noopener noreferrer"&gt;toolbar customization&lt;/a&gt;, &lt;a href="https://ckeditor.com/docs/ckeditor5/latest/framework/architecture/editing-engine.html" rel="noopener noreferrer"&gt;editor behavior hooks&lt;/a&gt;, and &lt;a href="https://ckeditor.com/docs/ckeditor5/latest/framework/architecture/core-editor-architecture.html#commands" rel="noopener noreferrer"&gt;custom commands&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Try CKEditor AI&lt;/strong&gt;: The &lt;a href="https://ckeditor.com/ckeditor-5/capabilities/ai-features/" rel="noopener noreferrer"&gt;AI capabilities&lt;/a&gt; have been in CKEditor since last year and are gaining adoption across the ecosystem. Drupal support landed just a week before DrupalCon – perfect timing! You can &lt;a href="https://ckeditor.com/ckeditor-5/capabilities/ai-features/" rel="noopener noreferrer"&gt;try the demo&lt;/a&gt; right in your browser, or grab a &lt;a href="https://portal.ckeditor.com/signup/" rel="noopener noreferrer"&gt;free 14-day trial&lt;/a&gt; to test it in your own Drupal installation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Grab the code&lt;/strong&gt;: All the files from this article – SKILL.md, PROMPT.md, the complete module, webpack config, and GitHub Actions workflow – are available in the &lt;a href="https://github.com/Simply007/drupalcon-chicago-26-ckeditor-ai-playground" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enjoy! And if you have any question - the comments section is yours, or find me up on Drupal Slack! 🥑&lt;/p&gt;

</description>
      <category>drupal</category>
      <category>ai</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>AI Conference 2025 - CKEditor in da house</title>
      <dc:creator>Ondřej Chrastina</dc:creator>
      <pubDate>Tue, 07 Oct 2025 12:55:04 +0000</pubDate>
      <link>https://forem.com/ckeditor/ai-conference-2025-ckeditor-in-da-house-m11</link>
      <guid>https://forem.com/ckeditor/ai-conference-2025-ckeditor-in-da-house-m11</guid>
      <description>&lt;p&gt;In the middle of September 2025, the CKEditor team attended &lt;a href="https://aiconference.com" rel="noopener noreferrer"&gt;The AI Conference&lt;/a&gt; in San Francisco. The team engaged with the vibrant AI community, connected with other leaders for potential integrations, and revealed news about the new CKEditor AI suite of capabilities 👻.&lt;/p&gt;

&lt;p&gt;

&lt;iframe class="tweet-embed" id="tweet-1968337657546813612-747" src="https://platform.twitter.com/embed/Tweet.html?id=1968337657546813612"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1968337657546813612-747');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1968337657546813612&amp;amp;theme=dark"
  }





&lt;/p&gt;

&lt;p&gt;I wanted to summarize the conference from the eyes of a developer advocate who has worked in the field for decades and has put particular focus on AI in recent months. Here you go...&lt;/p&gt;

&lt;h2&gt;
  
  
  Conference rundown
&lt;/h2&gt;

&lt;p&gt;It was a busy two days filled with basically non-stop talking to people about CKEditor and &lt;a href="https://ckeditor.com/ckeditor-ai/?utm_campaign=ckeditor_aiconference&amp;amp;utm_source=dev_to_article_aiconference_wrapup" rel="noopener noreferrer"&gt;CKEditor AI (😅 ohh I spoiled it already)&lt;/a&gt;. The overall vibe was more on the business side: lots of CEOs, founders of AI startups, and business professionals. Developers? Probably less than 10% of attendees. Not the standard crowd for the developer advocate part of me, but very exciting for the AI enthusiast part.&lt;/p&gt;

&lt;p&gt;The venue itself was unique. It was an old shed at Pier 39 (Google says &lt;a href="https://maps.app.goo.gl/eYdGTkg5LVuiZLSW7" rel="noopener noreferrer"&gt;Pier 48 - Lot #39&lt;/a&gt;). My guess is it was originally a boat storage or maintenance shed. We arrived to set up our booth the evening before the event: swag arrived on time, the booth looked good, and the stage and the venue were still getting ready. Still partly looking like a shed.&lt;/p&gt;

&lt;p&gt;On the first day of the conference, we encountered a lot of excitement, but also a pinch of hesitance with AI advancing so quickly. Inside, the Pier was nice and shiny: almost unrecognizable from the evening before. The main corridor in the center included two stages on the sides, with the back lined with booths, and finishing with the main stage.&lt;/p&gt;

&lt;p&gt;The morning flew by so quickly that we barely had time to grab breakfast or coffee. Lunch wasn’t much different. We had to take turns just to grab a bite. And right after, I went to deliver my talk (more about that in the next section).&lt;/p&gt;

&lt;p&gt;Day two was quieter, but no less engaging. The slower pace meant more one-on-one conversations and deeper dives into what CKEditor AI can really do.&lt;/p&gt;

&lt;p&gt;

&lt;iframe class="tweet-embed" id="tweet-1968721785777451082-439" src="https://platform.twitter.com/embed/Tweet.html?id=1968721785777451082"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1968721785777451082-439');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1968721785777451082&amp;amp;theme=dark"
  }





&lt;/p&gt;

&lt;h2&gt;
  
  
  The talk — why we went there
&lt;/h2&gt;

&lt;p&gt;I gave a lightning talk during lunch and expected maybe 30 people casually eating. Instead, I ended up speaking to ~300 (my best guess from the photos). That was a surprise, but also a great opportunity.&lt;/p&gt;

&lt;p&gt;The talk focused on the problem of “Bring Your Own AI” chaos (78% of knowledge workers bring their own AI tools) and how CKEditor AI solves it by offering a frictionless, unified AI writing stack inside any application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Outline of the talk
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The Bring Your Own AI (BYOAI) problem: copy/paste friction, compliance risks, and content inconsistency&lt;/li&gt;
&lt;li&gt;What is CKEditor? 

&lt;ul&gt;
&lt;li&gt;Editor (customizable and framework-agnostic)&lt;/li&gt;
&lt;li&gt;Cloud services (including telemetry, logs, compliance, and on-prem or SaaS deployment)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ckeditor.com/ckeditor-ai/?utm_campaign=ckeditor_aiconference&amp;amp;utm_source=dev_to_article_aiconference_wrapup" rel="noopener noreferrer"&gt;&lt;strong&gt;🎉 CKEditor AI Early access announcement&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The three AI surfaces:

&lt;ul&gt;
&lt;li&gt;AI Chat: An AI writing assistant directly next to your editor&lt;/li&gt;
&lt;li&gt;Quick AI Actions: Sentiment analysis, proofreading, translation, repetitive tasks - anything you use repeatably&lt;/li&gt;
&lt;li&gt;AI Content Review: Handles complex use cases, offering help for suggestion-heavy workflows and advanced AI tasks&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Real-world use case examples: Audits in regulated industries, marketing translations with disclaimers, and support ticket reply generation&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Below is the exclusive preview of the talk (after a bit of post-processing):&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/uic6IdZvnME"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;&lt;a href="https://l.ead.me/ai-conference-ckeditor-ai-slides" rel="noopener noreferrer"&gt;🖼️ Slide deck 🔗&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to learn more, check out the &lt;a href="https://ckeditor.com/ckeditor-ai/?utm_campaign=ckeditor_aiconference&amp;amp;utm_source=dev_to_article_aiconference_wrapup" rel="noopener noreferrer"&gt;CKEditor AI Landing page&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>news</category>
      <category>software</category>
    </item>
    <item>
      <title>CKEditor 5 Pitfall: Don't Forget the Paragraph Plugin</title>
      <dc:creator>Ondřej Chrastina</dc:creator>
      <pubDate>Mon, 23 Jun 2025 10:51:07 +0000</pubDate>
      <link>https://forem.com/ckeditor/ckeditor-5-pitfall-dont-forget-the-paragraph-plugin-i91</link>
      <guid>https://forem.com/ckeditor/ckeditor-5-pitfall-dont-forget-the-paragraph-plugin-i91</guid>
      <description>&lt;p&gt;When you're adding &lt;strong&gt;CKEditor 5&lt;/strong&gt; to your platform, chances are you're starting simple—maybe testing in a sandbox project or integrating through the excellent &lt;a href="https://ckeditor.com/ckeditor-5/builder/" rel="noopener noreferrer"&gt;CKEditor Builder&lt;/a&gt;, or diving into the &lt;a href="https://ckeditor.com/docs/ckeditor5/latest/framework/tutorials/crash-course/editor.html" rel="noopener noreferrer"&gt;CKEditor Framework Crash Course&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In my case, I tried both approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=V02w3CK8KG4&amp;amp;list=PLKzl1AGxt2J0j2H3xUuFLGO-s_-f0K9eD" rel="noopener noreferrer"&gt;🧱 Using the CKEditor Builder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/PVOdv2Mw02I?feature=shared" rel="noopener noreferrer"&gt;⚙️ Following the CKEditor framework crash course&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Both are fantastic resources. However, there's one small but &lt;strong&gt;crucial&lt;/strong&gt; detail that’s easy to overlook:&lt;br&gt;
&lt;em&gt;💡 You need &lt;strong&gt;two plugins&lt;/strong&gt; to make even a minimal CKEditor 5 setup work:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Essentials&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;🚨 &lt;code&gt;Paragraph&lt;/code&gt; (yes, it's &lt;em&gt;not&lt;/em&gt; included in Essentials!)
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ClassicEditor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;licenseKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GPL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;Essentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// * ✅ Essential&lt;/span&gt;
        &lt;span class="nx"&gt;Paragraph&lt;/span&gt; &lt;span class="c1"&gt;// 🚨 Paragraph 🚨 &lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Pitfall: Only Using Essentials
&lt;/h2&gt;

&lt;p&gt;It might seem like the following setup is enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Import the editor.&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ClassicEditor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Essentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ckeditor5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Import the styles.&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ckeditor5/ckeditor5.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Get the HTML element with the ID of 'app'.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Instantiate the editor using the `create` method.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ClassicEditor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;licenseKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GPL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;Essentials&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The editor initializes, &lt;strong&gt;but nothing works&lt;/strong&gt;—no content, no cursor, and no error messages either! 😱&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Yes, the configuration on the image is slightly richer, but still behaves the same.&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70u7zyfklvfa7w014gjq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70u7zyfklvfa7w014gjq.png" alt="Creation of the CKEditor withouts the Paragraph plugin being used." width="605" height="915"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take a look at this case:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Are you missing something?”&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Yes. Yes, you are!&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  YES, it is in the code docs
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;(alias) class Essentials&lt;/code&gt;&lt;br&gt;
&lt;code&gt;import Essentials&lt;/code&gt;&lt;br&gt;
A plugin including all essential editing features. It represents a set of features that enables similar functionalities to a  element.&lt;br&gt;
It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:ui/editorui/accessibilityhelp/accessibilityhelp~AccessibilityHelp&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:clipboard/clipboard~Clipboard&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:enter/enter~Enter&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:select-all/selectall~SelectAll&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:enter/shiftenter~ShiftEnter&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:typing/typing~Typing&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:undo/undo~Undo&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This plugin set does not define any block-level containers (such as &lt;strong&gt;:paragraph/paragraph~Paragraph&lt;/strong&gt;). If your editor is supposed to handle block content, make sure to include it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Fix: Add the Paragraph Plugin 🎉
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Import the editor.&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ClassicEditor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Essentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Paragraph&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ckeditor5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Import the styles.&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ckeditor5/ckeditor5.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Get the HTML element with the ID of 'app'.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Instantiate the editor using the `create` method.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ClassicEditor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;licenseKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GPL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;Essentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;Paragraph&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And voilà:&lt;br&gt;
“Much better!”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6pynght6k45tqi5lymte.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6pynght6k45tqi5lymte.png" alt="Showcase of the working CKEditor with thew Paragraph plugin being defined." width="605" height="915"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 Key Takeaway
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Essentials&lt;/code&gt; plugin ≠ has everything you need for editing.&lt;/li&gt;
&lt;li&gt;For a working CKEditor instance, especially one with block content, &lt;strong&gt;add the Paragraph plugin&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Always double-check the docs—even for plugins that sound... well, essential.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Keep this in mind as you build your rich text editing experiences! 💡&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devrel</category>
      <category>ckeditor</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Storybook Starter Guide: Learn Design System Principles</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Fri, 28 Mar 2025 17:44:22 +0000</pubDate>
      <link>https://forem.com/ckeditor/storybook-starter-guide-learn-design-system-principles-2b61</link>
      <guid>https://forem.com/ckeditor/storybook-starter-guide-learn-design-system-principles-2b61</guid>
      <description>&lt;p&gt;Run this in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create storybook@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, it's a generic starter for a component library: a button, a header, and a few example stories. But underneath that simplicity lies something much more powerful — the foundation of a design system.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore how Storybook’s starter is an introduction to core design system principles — reusability, composability, and consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Storybook?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://storybook.js.org/" rel="noopener noreferrer"&gt;Storybook&lt;/a&gt; is an open-source tool for building and testing UI components in isolation. Think of it as a dedicated workshop where you can create, preview, and document components in every possible state without spinning up the full application.&lt;/p&gt;

&lt;p&gt;But Storybook isn’t just a place to organize your UI components. In practice, it becomes a powerful platform that organizes frontend workflows by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Letting you build and debug components without business logic getting in the way.
&lt;/li&gt;
&lt;li&gt;Supporting a wide range of frameworks like React, Vue, Angular, Svelte, and more.
&lt;/li&gt;
&lt;li&gt;Offering a natural cross-team collaboration workflow — designers, developers, and stakeholders can all review and work on components to deliver great quality user interfaces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you're crafting a single button or a complete design system, Storybook helps you move faster, stay consistent, and collaborate more effectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of Using Storybook
&lt;/h3&gt;

&lt;p&gt;Now, let's address some more arguments in favor of using Storybook:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Visual Testing of Component States:&lt;/strong&gt; Each story represents a specific state of a component. This makes it easier to notice edge cases and ensure a consistent look across the application.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-Generated Docs:&lt;/strong&gt; &lt;a href="https://storybook.js.org/docs/writing-docs/autodocs" rel="noopener noreferrer"&gt;Autodocs&lt;/a&gt; turns your stories into complete, shareable documentation. Anyone on the team can explore usage examples, parameters, and behaviors. This is also a great point when onboarding new team members on the project.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smooth Collaboration:&lt;/strong&gt; Designers, product managers, and QA engineers can view and interact with components in the browser, leave feedback, and review changes even without technical knowledge of setting up a local environment.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works Across Frameworks:&lt;/strong&gt; Storybook supports React, Vue, Angular, Svelte, Web Components, and more, making it a good option for any team to avoid framework support limitations.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusted by Top Companies:&lt;/strong&gt; &lt;a href="https://storybook.js.org/showcase/the-washington-post-wpds" rel="noopener noreferrer"&gt;The Washington Post&lt;/a&gt;, &lt;a href="https://storybook.js.org/showcase/shopify-polaris-react" rel="noopener noreferrer"&gt;Shopify&lt;/a&gt;, &lt;a href="https://storybook.js.org/showcase/nasa-jpl-explorer-1" rel="noopener noreferrer"&gt;NASA&lt;/a&gt;, and others rely on Storybook to power their UI workflows. It’s also the foundation behind popular component libraries like &lt;a href="https://storybook.js.org/showcase/chakra-chakra-ui" rel="noopener noreferrer"&gt;Chakra UI&lt;/a&gt; or &lt;a href="https://storybook.js.org/showcase/microsoft-fluent-ui-web-components" rel="noopener noreferrer"&gt;Fluent UI&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Core Design System Principles in the Starter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Reusability
&lt;/h3&gt;

&lt;p&gt;Reusability is a fundamental principle of any design system. It’s about creating abstract components that can be used in different use cases while maintaining a consistent look and feel.&lt;/p&gt;

&lt;p&gt;Let's review the included &lt;code&gt;Button&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;primary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ButtonProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;primary&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storybook-button--primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storybook-button--secondary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
      &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storybook-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`storybook-button--&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;backgroundColor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It might look basic, but out of the box, it has a good set of props:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Boolean prop &lt;code&gt;primary&lt;/code&gt; to toggle variants
&lt;/li&gt;
&lt;li&gt;Props for size, color, and label&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stories for this component reinforce reusability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Large&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;large&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The story format encourages creating components in a &lt;a href="https://dev.to/jps27cse/understanding-reusable-components-and-the-dry-principle-4ijm"&gt;Don’t Repeat Yourself&lt;/a&gt; (DRY), consistent manner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Composability
&lt;/h3&gt;

&lt;p&gt;Design systems aren’t just about single-use components — they’re about components that combine like LEGO blocks.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Header&lt;/code&gt; component showcases this principle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onLogin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onLogout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onCreateAccount&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;HeaderProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"storybook-header"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"32"&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"32"&lt;/span&gt; &lt;span class="na"&gt;viewBox&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0 0 32 32"&lt;/span&gt; &lt;span class="na"&gt;xmlns&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;http://www.w3.org/2000/svg&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      ...
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Acme&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"welcome"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              Welcome, &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;!
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"small"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onLogout&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Log out"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"small"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onLogin&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Log in"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;primary&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"small"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onCreateAccount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Sign up"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we don't hardcode buttons that should be present in the header but reuse the existing&lt;code&gt;Button&lt;/code&gt; component. This is how good design systems are built — small parts are assembled into larger elements.&lt;/p&gt;

&lt;p&gt;Storybook makes it easier to follow this mindset by isolating each component. You focus on one piece at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consistency
&lt;/h3&gt;

&lt;p&gt;Consistency is key in a design system — visually, behaviorally, and structurally. That can be achieved with design tokens.&lt;/p&gt;

&lt;p&gt;The starter doesn’t include an implementation out of the box, but its architecture makes it easy to add one.&lt;/p&gt;

&lt;p&gt;For example, at &lt;a href="https://ckeditor.com/" rel="noopener noreferrer"&gt;CKEditor&lt;/a&gt;, we use a hybrid approach — &lt;a href="https://sass-lang.com/" rel="noopener noreferrer"&gt;Syntactically Awesome Style Sheets&lt;/a&gt; (Sass) preprocessor and CSS variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sass"&gt;&lt;code&gt;&lt;span class="nv"&gt;$radius-medium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;app-radius-medium&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$radius-big&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;app-radius-big&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$radius-bigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;app-radius-bigger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we can define theme-specific values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.app-theme-custom&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--app-radius-medium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--app-radius-big&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--app-radius-bigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extending with Addons
&lt;/h2&gt;

&lt;p&gt;One of Storybook’s biggest strengths is its ecosystem of addons. These allow tailoring a Storybook setup for more specific needs, such as testing the accessibility of your components.&lt;/p&gt;

&lt;p&gt;With the accessibility addon, accessibility checks can be integrated directly into your component stories.&lt;/p&gt;

&lt;p&gt;To enable it, start by installing the npm package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @storybook/addon-a11y &lt;span class="nt"&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then register it inside your &lt;code&gt;.storybook/main.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;addons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;...,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@storybook/addon-a11y&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once set up, every component story gets an &lt;strong&gt;Accessibility&lt;/strong&gt; panel that performs automated audits. It highlights issues like missing labels, contrast problems, and more without leaving your development environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyj9tioheex9rypx1xmge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyj9tioheex9rypx1xmge.png" alt="Image description" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This addon serves as a perfect example of how you can extend Storybook beyond the basics.&lt;/p&gt;

&lt;p&gt;The full list of available addons can be found on the &lt;a href="https://storybook.js.org/addons" rel="noopener noreferrer"&gt;Storybook Integrations page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;In this article, we explored the Storybook starter and its main concepts and features.&lt;/p&gt;

&lt;p&gt;It introduces you to the core principles of a good design system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reusability
&lt;/li&gt;
&lt;li&gt;Composability
&lt;/li&gt;
&lt;li&gt;Consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not code that is meant to be removed as soon as generated, but a foundation for your design system. Study, expand, and use it to create a design system that will help you create stunning user interfaces!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Detect Bot Traffic using Next.js Middleware: A Quick Guide</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Fri, 28 Feb 2025 15:30:35 +0000</pubDate>
      <link>https://forem.com/ckeditor/how-to-detect-bot-traffic-using-nextjs-middleware-a-quick-guide-14o8</link>
      <guid>https://forem.com/ckeditor/how-to-detect-bot-traffic-using-nextjs-middleware-a-quick-guide-14o8</guid>
      <description>&lt;p&gt;Bots make up a significant portion of internet traffic. Some, like &lt;strong&gt;Googlebot&lt;/strong&gt;, are beneficial, while others — such as scrapers — can harm your site. In this article, we'll explore how to detect and block bots using &lt;strong&gt;Next.js Middleware&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Bot Detection and Why Does it Matter?
&lt;/h2&gt;

&lt;p&gt;Not all web traffic is human. In fact, almost half of web traffic is automated. Bots can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scrape content
&lt;/li&gt;
&lt;li&gt;Spam your website
&lt;/li&gt;
&lt;li&gt;Slow down your app&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Should You Implement Bot Detection?
&lt;/h3&gt;

&lt;p&gt;From a security point of view, bot detection functionality is an essential part of the security toolset.&lt;/p&gt;

&lt;p&gt;With bot detection, you can improve security by blocking malicious traffic, reduce server load by filtering unnecessary requests, and protect analytics accuracy by preventing bot traffic.&lt;/p&gt;

&lt;p&gt;Since Next.js Middleware runs before a request is processed, it’s the perfect solution for implementing bot detection.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Middleware in Next.js?
&lt;/h2&gt;

&lt;p&gt;Middleware in Next.js allows you to run code and execute logic before completing the request.&lt;/p&gt;

&lt;p&gt;Additionally, Middleware can be run globally. However, you can modify this behavior for specific routes. It's important to mention that Middleware works really well with platforms like Vercel and Netlify.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Next.js Middleware Works
&lt;/h3&gt;

&lt;p&gt;Let's simplify the explanation by identifying key facts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Middleware must be placed in the project root as a &lt;code&gt;middleware.ts&lt;/code&gt; or &lt;code&gt;middleware.js&lt;/code&gt; file depending if you are using TypeScript or not.
&lt;/li&gt;
&lt;li&gt;It automatically applies to &lt;strong&gt;all routes&lt;/strong&gt; unless configured otherwise.
&lt;/li&gt;
&lt;li&gt;It uses the &lt;code&gt;NextResponse&lt;/code&gt; to modify requests and perform specific actions like redirecting or rewrite&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Simple Middleware Implementation
&lt;/h2&gt;

&lt;p&gt;Before we add bot detection, let's create a basic Middleware implementation to log requests. First, we need to create &lt;code&gt;middleware.ts&lt;/code&gt; in the root directory.&lt;/p&gt;

&lt;p&gt;For a basic implementation, our Middleware will do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log every incoming request in the terminal.
&lt;/li&gt;
&lt;li&gt;Pass the request through without modification.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EXCLUDED_PATHS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/_next/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/static/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EXCLUDED_EXTENSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.svg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.ico&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.gif&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.webp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.woff2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EXCLUDED_PATHS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;EXCLUDED_EXTENSIONS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Request received: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's run our Next.js application to verify that our Middleware is functioning as expected.&lt;/p&gt;

&lt;p&gt;Start your Next.js app by running the &lt;code&gt;npm run dev&lt;/code&gt; in the terminal. Once you visit the &lt;code&gt;localhost:3000&lt;/code&gt; in the browser, check the terminal — you should see the following log there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request received: &amp;lt;http://localhost:3000/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extend Middleware with Bot Detection
&lt;/h2&gt;

&lt;p&gt;Now, let's improve our Middleware to detect bot-like User-Agents. We can do that by updating &lt;code&gt;middleware.ts&lt;/code&gt; with Bot Detection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EXCLUDED_PATHS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/_next/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/static/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EXCLUDED_EXTENSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.svg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.ico&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.gif&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.webp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.woff2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BOT_PATTERNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;/bot/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/crawler/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/spider/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/curl/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/wget/i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EXCLUDED_PATHS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;EXCLUDED_EXTENSIONS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BOT_PATTERNS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Bot detected! (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) - Redirecting to /bot-detected.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/bot-detected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Request received: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | User-Agent: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's review what changed in this version of our Middleware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We extract the &lt;code&gt;User-Agent&lt;/code&gt; header from the request.
&lt;/li&gt;
&lt;li&gt;Check if it matches one of the bot keywords (&lt;code&gt;bot&lt;/code&gt;, &lt;code&gt;crawler&lt;/code&gt;, &lt;code&gt;spider&lt;/code&gt;, etc.).
&lt;/li&gt;
&lt;li&gt;Redirect detected bots to &lt;code&gt;/bot-detected&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Log each request’s &lt;code&gt;User-Agent&lt;/code&gt; for debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test the Middleware
&lt;/h2&gt;

&lt;p&gt;Now, let's test our middleware using &lt;code&gt;curl&lt;/code&gt; and simulate a bot request. Run the following in your terminal without stopping the Next.js application locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -i -A "Googlebot" &amp;lt;http://localhost:3000&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you implemented the Middleware correctly, the following response will be visible in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 307 Temporary Redirect
location: /bot-detected
Date: Mon, 24 Feb 2025 17:00:34 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked

/bot-detected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the bot detection works!&lt;/p&gt;

&lt;h2&gt;
  
  
  Review of Common Bot Detection Techniques
&lt;/h2&gt;

&lt;p&gt;Now that our basic bot detection Middleware works, let’s explore some common techniques.&lt;/p&gt;

&lt;h3&gt;
  
  
  User-Agent Header Checking
&lt;/h3&gt;

&lt;p&gt;This is the solution that we implemented as a simple demonstration of bot detection.&lt;/p&gt;

&lt;p&gt;It was very straightforward and was great for demo purposes. However, many bots use agents similar to real users, which makes this approach sub-optimal for real-life applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  IP Address Filtering
&lt;/h3&gt;

&lt;p&gt;This approach can be more effective in filtering and can be applied to real-life, production-ready applications. However, you can't rely on an array of addresses you manually gathered, so you must use anAPI that analyses IP.&lt;/p&gt;

&lt;p&gt;That approach offers better protection but may be ineffective for more advanced bots.&lt;/p&gt;

&lt;h3&gt;
  
  
  Third-Party Bot Detection APIs
&lt;/h3&gt;

&lt;p&gt;Building on the previous approach, if we want enterprise-grade protection, a complete dedicated bot detection service like BotD or DataDome is the most solid means that guarantees high detection accuracy.&lt;/p&gt;

&lt;p&gt;While these solutions are not free, they are good enterprise-level solutions for production-ready applications.&lt;/p&gt;

&lt;p&gt;If you want to get started with one of the solutions that were mentioned above, Vercel provides starter projects that allow you to see both of them in action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/vercel/examples/tree/main/edge-middleware/bot-protection-botd" rel="noopener noreferrer"&gt;&lt;strong&gt;BotD by FingerprintJS&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vercel/examples/tree/main/edge-middleware/bot-protection-datadome" rel="noopener noreferrer"&gt;&lt;strong&gt;DataDome&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Bots can have a huge impact on your website, from content scraping and spam to security risks and performance issues. Fortunately, &lt;a href="https://nextjs.org/docs/app/building-your-application/routing/middleware" rel="noopener noreferrer"&gt;Next.js Middleware&lt;/a&gt; provides an efficient way to filter out unwanted traffic before it reaches your app.&lt;/p&gt;

&lt;p&gt;While basic bot detection is a great starting point, bots can bypass simple User-Agent checks. If you need stronger protection, consider third-party services like BotD or DataDome to detect even the most advanced bots.&lt;/p&gt;

&lt;p&gt;Now, it's time for you to find the optimal solution for your needs!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>javascript</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Create a Rich Text Editor with Astro, Preact &amp; CKEditor 5</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Thu, 06 Feb 2025 12:46:47 +0000</pubDate>
      <link>https://forem.com/ckeditor/create-a-rich-text-editor-with-astro-preact-ckeditor-5-n1f</link>
      <guid>https://forem.com/ckeditor/create-a-rich-text-editor-with-astro-preact-ckeditor-5-n1f</guid>
      <description>&lt;p&gt;&lt;strong&gt;Astro&lt;/strong&gt; is a modern framework designed for ultra-fast performance by shipping only the JavaScript that's needed. Pair that with &lt;strong&gt;Preact&lt;/strong&gt;, a lightweight alternative to React, and you’ve got the perfect recipe for building fast and efficient applications. To take things a step further, you can integrate &lt;strong&gt;CKEditor 5&lt;/strong&gt;, a powerful &lt;a href="https://ckeditor.com/" rel="noopener noreferrer"&gt;rich text editor&lt;/a&gt;, to add robust content editing capabilities to your app.&lt;/p&gt;

&lt;p&gt;In this article, we’ll walk you through the process of setting up an &lt;strong&gt;Astro + Preact&lt;/strong&gt; project from scratch and show you how to seamlessly integrate CKEditor 5. Let’s dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why Choose Astro + Preact?&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Astro:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimal JavaScript by default&lt;/strong&gt;: Astro ships only what’s necessary to the client.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perfect for content-focused websites&lt;/strong&gt;: Ideal for blogs, documentation sites, and other projects where performance matters but also suitable for more complex and dynamic applications.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework flexibility&lt;/strong&gt;: Supports popular frameworks like React, Preact, Vue, Svelte, and Solid.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Preact:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Extremely lightweight&lt;/strong&gt;: Just ~3 kB gzipped, making it an excellent choice for performance-conscious projects.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React ecosystem compatibility&lt;/strong&gt;: Most React libraries work seamlessly with Preact.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast and efficient&lt;/strong&gt;: A great way to build modern, fast applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By combining Astro’s performance-first approach with Preact’s lightweight design, you get the best of both worlds: a highly-performant app and a pleasant developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why CKEditor?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ckeditor.com/" rel="noopener noreferrer"&gt;CKEditor&lt;/a&gt; is a powerful, flexible, rich text editor that enhances content creation and collaboration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast &amp;amp; Efficient&lt;/strong&gt;: Great writing experience with autoformatting, merge fields, and text transformation.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete Toolset for Collaboration&lt;/strong&gt;: Real-time co-editing, track changes, and comments.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible &amp;amp; Customizable&lt;/strong&gt;: Over 100 plugins are configurable toolbar are available with CKEditor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Integrating CKEditor into Astro + Preact starter will allow you to build complex applications with excellent performance and feature-rich editing experience for various use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Setting Up Your Astro + Preact Project&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s how to go from zero to a fully functional &lt;strong&gt;Astro + Preact&lt;/strong&gt; project with CKEditor 5.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1: Create a New Astro Project&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;First, let’s create a fresh Astro project. Open your terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create astro@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the CLI’s prompts to set up your project. Astro’s installation process is straightforward and beginner-friendly.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 2: Add Preact to Your Astro Project&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Once your Astro project is set up, it’s time to integrate Preact. Astro provides a simple way to add Preact to your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx astro add preact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command automatically updates your configuration files and installs the necessary dependencies. With just one command, Preact is ready to go!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 3: Generate Your CKEditor Code&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;CKEditor 5 is a flexible and customizable rich text editor. To add it to your project, use the &lt;a href="https://ckeditor.com/ckeditor-5/builder/" rel="noopener noreferrer"&gt;CKEditor 5 Builder&lt;/a&gt;. Here’s how:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Select the preset&lt;/strong&gt;: Start with the &lt;strong&gt;Classic Editor (starter)&lt;/strong&gt; for a simple yet powerful editor.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable features&lt;/strong&gt;: CKEditor offers over 100 plugins. Pick the features you need (e.g., bold, italic, link) and decide between free or premium options.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure the toolbar&lt;/strong&gt;: Customize the toolbar layout to match your requirements.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate code snippets&lt;/strong&gt;: Choose &lt;code&gt;React&lt;/code&gt; as the technology and &lt;code&gt;Cloud (CDN)&lt;/code&gt; as the integration method. If you’ve selected only free features, your code snippet will include a 24-hour evaluation key; alternatively, press the &lt;code&gt;Sign up for trial&lt;/code&gt; button to create a license key and automatically propagate it in your code snippets. If you have issues, refer to &lt;a href="https://ckeditor.com/docs/ckeditor5/latest/getting-started/licensing/license-key-and-activation.html#license-key-set-up" rel="noopener noreferrer"&gt;License key set up documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 4: Integrate CKEditor 5 into Your Project&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now let’s integrate CKEditor 5 into your Astro + Preact project.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Install CKEditor’s React Package&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Install CKEditor’s React package using npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @ckeditor/ckeditor5-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;2. Add CKEditor Styles&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;styles&lt;/code&gt; folder in your project and add a file named &lt;code&gt;App.css&lt;/code&gt;. Paste the styles provided by the CKEditor Builder into this file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="sx"&gt;url('&amp;lt;https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&amp;amp;display=swap&amp;gt;')&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="n"&gt;print&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.main-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Lato'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fit-content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.ck-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Lato'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;word-break&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;break-word&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.editor-container_classic-editor&lt;/span&gt; &lt;span class="nc"&gt;.editor-container__editor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;795px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;795px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;3. Create an Editor Component&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code&gt;editor.jsx&lt;/code&gt; in the &lt;code&gt;components&lt;/code&gt; folder. Here’s the component code, slightly modified for Preact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useMemo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preact/hooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CKEditor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ckeditor/ckeditor5-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LICENSE_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_LICENSE_KEY_HERE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Editor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editorContainerRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editorRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLayoutReady&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLayoutReady&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cloud&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCKEditorCloud&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;44.1.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setIsLayoutReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIsLayoutReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ClassicEditor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;editorConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isLayoutReady&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ClassicEditor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AutoLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Autosave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Essentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Italic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Paragraph&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CKEditor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;ClassicEditor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;editorConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;toolbar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;italic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="na"&gt;shouldNotGroupWhenFull&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AutoLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Autosave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Essentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Italic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="na"&gt;initialData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;h2&amp;gt;Evaluation license key 🔑&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;tAn evaluation key is being used in this editor. &amp;lt;a href="&amp;lt;https://portal.ckeditor.com/checkout?plan=free&amp;gt;"&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;t&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;tCreate an account to use your own license keys.&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;t&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&amp;lt;h2&amp;gt;Congratulations on setting up CKEditor 5! 🎉&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;tYou&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;ve&lt;/span&gt; &lt;span class="nx"&gt;successfully&lt;/span&gt; &lt;span class="nx"&gt;created&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;CKEditor&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;This&lt;/span&gt; &lt;span class="nx"&gt;powerful&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;twill&lt;/span&gt; &lt;span class="nx"&gt;enhance&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;enabling&lt;/span&gt; &lt;span class="nx"&gt;rich&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="nx"&gt;editing&lt;/span&gt; &lt;span class="nx"&gt;capabilities&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;tare&lt;/span&gt; &lt;span class="nx"&gt;customizable&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;easy&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&amp;lt;h3&amp;gt;What&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;'s next&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ol&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;strong&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Integrate&lt;/span&gt; &lt;span class="nx"&gt;into&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/strong&amp;gt;: time to bring the editing into&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;t&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;tyour application. Take the code you created and add to your application.&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;t&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;strong&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Explore&lt;/span&gt; &lt;span class="na"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Experiment with different plugins and\\\\n\\\\t\\\\ttoolbar options to discover what works best for your needs.\\\\n\\\\t&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;strong&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Customize&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="na"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;:&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Tailor the editor\\\\'s\\\\n\\\\t\\\\tconfiguration to match your application\\\\'s style and requirements. Or\\\\n\\\\t\\\\teven write your plugin!\\\\n\\\\t&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ol&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;tKeep experimenting, and don&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;'t hesitate to push the boundaries of what you&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;tcan achieve with CKEditor 5. Your feedback is invaluable to us as we strive&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;tto improve and evolve. Happy editing!&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Helpful&lt;/span&gt; &lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h3&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&amp;lt;ul&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;t&amp;lt;li&amp;gt;📝 &amp;lt;a href="&amp;lt;https:/&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;portal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ckeditor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;free&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;Trial sign up&amp;lt;/a&amp;gt;,&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s2"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s2"&gt;t&amp;lt;li&amp;gt;📕 &amp;lt;a href=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//ckeditor.com/docs/ckeditor5/latest/installation/index.html&amp;gt;"&amp;gt;Documentation&amp;lt;/a&amp;gt;,&amp;lt;/li&amp;gt;\\\\n\\\\t&amp;lt;li&amp;gt;⭐️ &amp;lt;a href="&amp;lt;https://github.com/ckeditor/ckeditor5&amp;gt;"&amp;gt;GitHub&amp;lt;/a&amp;gt; (star us if you can!),&amp;lt;/li&amp;gt;\\\\n\\\\t&amp;lt;li&amp;gt;🏠 &amp;lt;a href="&amp;lt;https://ckeditor.com&amp;gt;"&amp;gt;CKEditor Homepage&amp;lt;/a&amp;gt;,&amp;lt;/li&amp;gt;\\\\n\\\\t&amp;lt;li&amp;gt;🧑‍💻 &amp;lt;a href="&amp;lt;https://ckeditor.com/ckeditor-5/demo/&amp;gt;"&amp;gt;CKEditor 5 Demos&amp;lt;/a&amp;gt;,&amp;lt;/li&amp;gt;\\\\n&amp;lt;/ul&amp;gt;\\\\n&amp;lt;h3&amp;gt;Need help?&amp;lt;/h3&amp;gt;\\\\n&amp;lt;p&amp;gt;\\\\n\\\\tSee this text, but the editor is not starting up? Check the browser\\\\'s\\\\n\\\\tconsole for clues and guidance. It may be related to an incorrect license\\\\n\\\\tkey if you use premium features or another feature-related requirement. If\\\\n\\\\tyou cannot make it work, file a GitHub issue, and we will help as soon as\\\\n\\\\tpossible!\\\\n&amp;lt;/p&amp;gt;\\\\n',&lt;/span&gt;
                &lt;span class="na"&gt;licenseKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LICENSE_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Type or paste your content here!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cloud&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLayoutReady&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"main-container"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"editor-container editor-container_classic-editor"&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;editorContainerRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"editor-container__editor"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;editorRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ClassicEditor&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;editorConfig&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CKEditor&lt;/span&gt; &lt;span class="na"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ClassicEditor&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;editorConfig&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Step 5: Adjust Astro for Preact Compatibility&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To make React libraries (like &lt;strong&gt;CKEditor 5 integration package&lt;/strong&gt;) work in a Preact environment, update your &lt;code&gt;astro.config.mjs&lt;/code&gt; file as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;preact&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@astrojs/preact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;integrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;preact&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;vite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;react&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preact/compat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preact/compat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Step 6: Add the Editor to Your Page&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Finally, import the &lt;code&gt;Editor&lt;/code&gt; component in your Astro page and render it using the &lt;code&gt;client:only="preact"&lt;/code&gt; directive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import Layout from '../layouts/Layout.astro';
import Editor from '../components/editor';
---

&amp;lt;Layout&amp;gt;
  &amp;lt;Editor client:only="preact" /&amp;gt;
&amp;lt;/Layout&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once completed, you will be able to see the editor on the page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1u80sepvtdgn36zgztso.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1u80sepvtdgn36zgztso.png" alt="Rich text editor in an Astro + Preact environment designed for fast and efficient content editing" width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Wrapping Up&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Congratulations! You’ve successfully set up an &lt;strong&gt;Astro + Preact&lt;/strong&gt; project and integrated &lt;strong&gt;CKEditor 5&lt;/strong&gt; into it. Here’s a quick recap of the key benefits of the created setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimal JavaScript Thanks to Preact&lt;/strong&gt;: Preact keeps your application lightweight by reducing the shipped JavaScript, resulting in faster load times and improved performance, especially on content-focused websites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Powerful React Compatibility&lt;/strong&gt;: Since Preact is compatible with most React libraries and components, you can easily integrate React libraries while benefiting from Preact’s smaller footprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Great Developer Experience&lt;/strong&gt;: Astro’s flexibility and Preact’s simplicity allow for a scalable setup with reusable components, making development more efficient and easier to maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Powerful Editing Tool&lt;/strong&gt;: CKEditor 5 enhances content creation with rich text formatting, real-time collaboration, media embedding, and an extensive plugin ecosystem, giving users a professional-grade editing experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup offers incredible performance while maintaining a great developer experience.&lt;/p&gt;

&lt;p&gt;If you’d like to extend your CKEditor setup, check out the following resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ckeditor.com/ckeditor-5/demo/" rel="noopener noreferrer"&gt;CKEditor demo pages&lt;/a&gt; to get inspiration
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ckeditor.com/ckeditor-5/features/" rel="noopener noreferrer"&gt;CKEditor 5 features&lt;/a&gt; to get a high-level understanding of what is available with CKEditor
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ckeditor.com/docs/" rel="noopener noreferrer"&gt;CKEditor Documentation&lt;/a&gt; to go deeper and see how you can customize not only the editor setup but also the plugin's behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let us know in the comments what plugins or configurations you’ve used! 🚀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>astro</category>
    </item>
    <item>
      <title>Building a Measuring Tool with the Resize Observer API</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Fri, 20 Dec 2024 18:03:45 +0000</pubDate>
      <link>https://forem.com/ckeditor/building-a-measuring-tool-with-the-resize-observer-api-4bd8</link>
      <guid>https://forem.com/ckeditor/building-a-measuring-tool-with-the-resize-observer-api-4bd8</guid>
      <description>&lt;p&gt;Web APIs - a very interesting and rarely well explored territory. And yet, there's a large number of unique and very useful APIs that can help you to create tools for your projects.&lt;/p&gt;

&lt;p&gt;For example, tracking size changes is key to creating dynamic, responsive experiences. This is where the &lt;strong&gt;Resize Observer API&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;In this article, we’ll build a measuring tool that displays the width and height of a resizable box in real-time. This is a project that demonstrates the power of the Resize Observer API and Browser APIs in general in a practical and interactive way.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Resize Observer API?
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Resize Observer API&lt;/strong&gt; is a browser feature that allows you to detect changes to the size of an element. Resize Observer works on individual elements. It works out-of-the-box, and can be a great addition to your toolset for building responsive design and dynamic UIs.&lt;/p&gt;

&lt;p&gt;Here’s what makes it great:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It’s lightweight and easy to use&lt;/li&gt;
&lt;li&gt;You can track changes in size for specific elements, not just the entire viewport&lt;/li&gt;
&lt;li&gt;It’s perfect for building responsive components or resizable widgets&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What We’re Building
&lt;/h2&gt;

&lt;p&gt;We’ll create a resizable box with dimensions displayed inside of it. As the user resizes the box, the displayed dimensions will update in real time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Setting Up the Project
&lt;/h2&gt;

&lt;p&gt;First, let’s set up the basic structure for our project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resize-tool/
├── index.html
├── styles.css
├── script.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: The Markup
&lt;/h2&gt;

&lt;p&gt;Here’s a simple layout for our app. The resizable box includes a text span to display its dimensions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Measuring Tool with Resize Observer API&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"styles.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"resizableBox"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"resizable"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"dimensions"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Width: 0px, Height: 0px&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"script.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Styling the App
&lt;/h2&gt;

&lt;p&gt;We’ll add some styles to make the resizable box more visually appealing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f0f0f0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.resizable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;dashed&lt;/span&gt; &lt;span class="m"&gt;#007bff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;both&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="nc"&gt;.resizable&lt;/span&gt; &lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Adding Real-Time Resize Tracking
&lt;/h2&gt;

&lt;p&gt;Now let’s bring the project to life using the Resize Observer API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resizableBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resizableBox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dimensions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resizeObserver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ResizeObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;inlineSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;blockSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;borderBoxSize&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Width: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;px, Height: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;resizeObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resizableBox&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Testing the Tool
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;code&gt;index.html&lt;/code&gt; file in your browser.&lt;/li&gt;
&lt;li&gt;Drag the corners of the box to resize it.&lt;/li&gt;
&lt;li&gt;Watch as the dimensions update instantly!&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The Resize Observer API is initialized to monitor size changes for the &lt;code&gt;resizableBox&lt;/code&gt; element. It triggers a callback whenever the size of the observed element changes.&lt;/li&gt;
&lt;li&gt;The Resize Observer Entry provides updated dimensions through the &lt;code&gt;borderBoxSize&lt;/code&gt; property.&lt;/li&gt;
&lt;li&gt;The updated width and height values are extracted and displayed dynamically by modifying the text content of the &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; element inside the resizable box.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we built a fun and interactive &lt;strong&gt;measuring tool&lt;/strong&gt; using the &lt;strong&gt;Resize Observer API&lt;/strong&gt;. This project demonstrates how browser APIs can simplify complex tasks.&lt;br&gt;
If you try this out or extend it further, feel free to share your creations in the comments!&lt;br&gt;
Also, check out the &lt;a href="https://ckeditor.com/blog/?utm_campaign=devrel_devto_ckeditor&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_term=browser-api-article" rel="noopener noreferrer"&gt;CKEditor blog&lt;/a&gt; for articles around rich-text editors, and start your journey with CKEditor 5 by signing up for a free trial today!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>OpenAI vs AWS Bedrock vs Azure Open AI - Choosing the right model for your AI Assistant</title>
      <dc:creator>Mrinalini Sugosh (Mrina)</dc:creator>
      <pubDate>Mon, 13 May 2024 04:07:33 +0000</pubDate>
      <link>https://forem.com/ckeditor/openai-vs-aws-bedrock-vs-azure-open-ai-choosing-the-right-model-for-your-ai-assistant-1h3a</link>
      <guid>https://forem.com/ckeditor/openai-vs-aws-bedrock-vs-azure-open-ai-choosing-the-right-model-for-your-ai-assistant-1h3a</guid>
      <description>&lt;p&gt;In the fast-paced world of AI development, choosing the right platform can significantly influence the efficiency, scalability, and success of your projects. Adding to this complexity is the integration of powerful editing tools like &lt;a href="https://ckeditor.com/ai-assistant/" rel="noopener noreferrer"&gt;CKEditor 5's AI Assistant&lt;/a&gt;, which supports all three major AI platforms: &lt;a href="https://ckeditor.com/docs/ckeditor5/latest/features/ai-assistant/ai-assistant-integration.html#openai-integration" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;, &lt;a href="https://ckeditor.com/docs/ckeditor5/latest/features/ai-assistant/ai-assistant-integration.html#amazon-bedrock-integration" rel="noopener noreferrer"&gt;AWS Bedrock&lt;/a&gt;, and &lt;a href="https://ckeditor.com/docs/ckeditor5/latest/features/ai-assistant/ai-assistant-integration.html#azure-openai-integration" rel="noopener noreferrer"&gt;Azure Open AI&lt;/a&gt;. This feature enhances editing efficiency and creativity by allowing users to generate content, process data with custom queries, or utilize predefined commands — all within the editor itself. Note that this is a premium feature, requiring an additional license on top of the CKEditor 5 commercial license.&lt;/p&gt;

&lt;p&gt;📊 Comparison Chart&lt;br&gt;
To put these frameworks into perspective, consider the following attributes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;OpenAI&lt;/th&gt;
&lt;th&gt;AWS Bedrock&lt;/th&gt;
&lt;th&gt;Azure Open AI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Language Support&lt;/td&gt;
&lt;td&gt;Python, JavaScript&lt;/td&gt;
&lt;td&gt;Python, Java, Go&lt;/td&gt;
&lt;td&gt;Python, .NET, Java&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration Ease&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalability&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pre-trained Models&lt;/td&gt;
&lt;td&gt;Extensive&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Extensive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom Model Training&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;Pay-as-you-go&lt;/td&gt;
&lt;td&gt;Subscription-based&lt;/td&gt;
&lt;td&gt;Pay-as-you-go&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  🚀 OpenAI
&lt;/h2&gt;

&lt;p&gt;OpenAI provides a robust suite of AI models like GPT and DALL-E, known for revolutionizing natural language processing and image generation. It's celebrated for easy integration and generating human-like text. Here’s how you can use OpenAI's GPT model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;

&lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your-api-key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Completion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-davinci-002&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Translate the following English text to French: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello, how are you?&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🌐 AWS Bedrock
&lt;/h2&gt;

&lt;p&gt;AWS Bedrock, part of Amazon Web Services, is designed to provide developers with scalable and integrated tools to build, train, and deploy machine learning models. AWS Bedrock emphasizes security and flexibility, supporting a wide array of machine learning frameworks like TensorFlow and PyTorch. Below is an example of deploying a machine learning model with AWS Bedrock:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize the Bedrock client
&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bedrock&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Deploy a model
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deploy_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ModelName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your-model-name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MinCapacity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MaxCapacity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deployment status:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🔵 Azure Open AI
&lt;/h2&gt;

&lt;p&gt;Microsoft’s Azure Open AI service integrates OpenAI's powerful models with Azure's robust cloud infrastructure, offering enhanced security, compliance, and enterprise-grade capabilities. It is particularly useful for organizations that require deep integration with other Azure services. Here’s how you might set up a request to Azure Open AI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.ai.textanalytics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TextAnalyticsClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.identity&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DefaultAzureCredential&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextAnalyticsClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://&amp;lt;your-endpoint&amp;gt;.cognitiveservices.azure.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                             &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Let&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s analyze the sentiment of this text.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analyze_sentiment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Document Sentiment:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sentiment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  In Summary
&lt;/h2&gt;

&lt;p&gt;Each platform offers unique strengths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI&lt;/strong&gt; is ideal for developers looking for state-of-the-art language models and simplicity in integration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Bedrock&lt;/strong&gt; suits those who need a robust, secure environment with the flexibility to work with various ML frameworks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure Open AI&lt;/strong&gt; is best for enterprises that require deep integration with other Azure services and enhanced regulatory compliance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Integrating these AI platforms with &lt;a href="https://ckeditor.com/ai-assistant/" rel="noopener noreferrer"&gt;CKEditor 5's AI Assistant&lt;/a&gt; can significantly boost your content creation capabilities, offering seamless support for generating and editing content directly within your applications. This premium feature can transform how you interact with text, combining AI's power with user-friendly editing tools. Don't forget to sign up for the &lt;a href="https://orders.ckeditor.com/trial/premium-features" rel="noopener noreferrer"&gt;CKEditor Premium Features 30-day free trial&lt;/a&gt; to test these capabilities. Whether you're building simple chatbots or complex, scalable AI solutions, choosing the right framework can pave the way for innovation and efficiency.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>openai</category>
      <category>azure</category>
      <category>aws</category>
    </item>
    <item>
      <title>How to upload different media types in ckEditor5 using custom upload Adaptor</title>
      <dc:creator>Amir Najafi</dc:creator>
      <pubDate>Thu, 18 Jan 2024 22:30:39 +0000</pubDate>
      <link>https://forem.com/ckeditor/how-to-upload-different-media-types-in-ckeditor5-using-custom-upload-adaptor-1mhi</link>
      <guid>https://forem.com/ckeditor/how-to-upload-different-media-types-in-ckeditor5-using-custom-upload-adaptor-1mhi</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5frn7shonqec5ujf0us.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5frn7shonqec5ujf0us.jpg" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
hello my fellow developers 🙌&lt;/p&gt;

&lt;p&gt;One of the main issues when dealing with &lt;strong&gt;WYSIWYG&lt;/strong&gt; editors is uploading media (audio, video, image, etc.)&lt;br&gt;
there are always limitations on how to upload certain media and of course developers have come up with professional solutions that might help you in managing your files (tools like &lt;a href="https://ckeditor.com/ckbox/" rel="noopener noreferrer"&gt;CKBox&lt;/a&gt;), but what if you just want to use  your personal custom editor for a change?&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create a Nextjs project
&lt;/h2&gt;

&lt;p&gt;to begin, open the terminal and run the following command:&lt;br&gt;
&lt;code&gt;npx create-next-app&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;you can also activate Typescript, ESlint and other options if you intend to use them in your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Create a custom build
&lt;/h2&gt;

&lt;p&gt;you can choose your own ckeditor5 toolbar and plugins by going to &lt;a href="https://ckeditor.com/ckeditor-5/online-builder/" rel="noopener noreferrer"&gt;CKEditor Online Builder&lt;/a&gt; and download the package.&lt;br&gt;
unzip it and then copy the folder into your project's &lt;em&gt;src&lt;/em&gt; folder and change these lines in &lt;code&gt;ckeditor5/build/ckeditor.js&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;acceptedType:r.map((t=&amp;gt;`image/${t}`)).join(","))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;acceptedType:'*'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;i=Array.from(n).filter((t=&amp;gt;s.test(t.type)))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;to&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;i=Array.from(n)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and finally run &lt;code&gt;npm install file:src/ckeditor5&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;you should now see &lt;code&gt;ckeditor5-custom-build&lt;/code&gt; in your &lt;code&gt;package.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;you should also install ckeditor package for react&lt;br&gt;
&lt;code&gt;npm install @ckeditor/ckeditor5-react"&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  step 3: Add editor to page
&lt;/h2&gt;

&lt;p&gt;make a new folder inside &lt;em&gt;app&lt;/em&gt; directory and rename it to blog.&lt;br&gt;
then create a &lt;code&gt;page.tsx&lt;/code&gt; and &lt;code&gt;CustomEditor&lt;/code&gt; component:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CustomEditor.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CKEditor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ckeditor/ckeditor5-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Editor&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ckeditor5-custom-build&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// upload Adaptor&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;uploadAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// here check the mimetype and send request&lt;/span&gt;
          &lt;span class="c1"&gt;// to relevant backend api endpoint&lt;/span&gt;
          &lt;span class="nx"&gt;axios&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://sample.com/files/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;endPoint&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;fileAddress&lt;/span&gt;
              &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;uploadPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FileRepository&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;createUploadAdapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;uploadAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// when upload completes, replace tag&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageUploadEditing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ImageUploadEditing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;imageUploadEditing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uploadComplete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;imageElement&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;change&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;video src='&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' controls="controls"&amp;gt;&amp;lt;/video&amp;gt;`&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;audio src='&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' controls="controls"&amp;gt;&amp;lt;/audio&amp;gt;`&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;img src='&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' /&amp;gt;`&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;editing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editorConfiguration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;write your content...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;extraPlugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uploadPlugin&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;toolbar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uploadImage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;codeBlock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;showBlocks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sourceEditing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;shouldNotGroupWhenFull&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;simpleUpload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;uploadUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://sample.com/files/editor`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fileTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.doc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.docx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.xls&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.xlsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mp4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;htmlSupport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/.*/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CustomEditor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CKEditor&lt;/span&gt;
      &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Editor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;editorConfiguration&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;CustomEditor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// blog/page.tsx&lt;/span&gt;

&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/dynamic.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CustomEditor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./CustomEditor.jsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;ssr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setContent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CustomEditor&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;handleContent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="na"&gt;newContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newContent&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Blog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;you can use your custom endpoints to send data to your backend and receive the file address. basically the code tries to replace the tag in the editor using &lt;a href="https://ckeditor.com/docs/ckeditor5/latest/api/module_engine_conversion_conversion-Conversion.html" rel="noopener noreferrer"&gt;conversion&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;also don't forget you need to add your backend uploader for this code to work (I'm using multer and express in my own project).&lt;/p&gt;

&lt;p&gt;Here's the result: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1xqi6zob2ar4jga1rf6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1xqi6zob2ar4jga1rf6.jpg" alt="Uploaded Audio &amp;amp; Video"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ckeditor</category>
      <category>nextjs</category>
      <category>uploadadaptor</category>
    </item>
    <item>
      <title>Upload images in Ckeditor 5 with Laravel</title>
      <dc:creator>David Carr</dc:creator>
      <pubDate>Sat, 21 Jan 2023 03:39:00 +0000</pubDate>
      <link>https://forem.com/ckeditor/upload-images-in-ckeditor-5-with-laravel-3320</link>
      <guid>https://forem.com/ckeditor/upload-images-in-ckeditor-5-with-laravel-3320</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F23azqgjwp8nw3o6sqlht.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F23azqgjwp8nw3o6sqlht.png" alt="Upload images in Ckeditor 5 with Laravel" width="800" height="449"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;CKeditor 5 out of the box does not come with upload capabilities. Uploading is supported with its plugins, some are official paid plugins that require subscriptions. There are a few free options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Base64 upload adapter
&lt;/h2&gt;

&lt;p&gt;This plugin allows uploads that will convert an uploaded image into base64 data. Very easy to use but will require you to save the complete base64 code with the post, this can be get long.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ckeditor.com/docs/ckeditor5/latest/features/images/image-upload/base64-upload-adapter.html" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple image adapter
&lt;/h2&gt;

&lt;p&gt;An image upload tool. It allows uploading images to an application running on your server using the &lt;strong&gt;XMLHttpRequest&lt;/strong&gt; API with a minimal editor configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ckeditor.com/docs/ckeditor5/latest/features/images/image-upload/simple-upload-adapter.html" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use the &lt;a href="https://ckeditor.com/ckeditor-5/online-builder/" rel="noopener noreferrer"&gt;online builder&lt;/a&gt; to add the simple image adapter then download the generated bundle unzip and place the folder inside a publicly accessible place in Laravel. such as &lt;strong&gt;/public/js/ckeditor5&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then link ckeditor.js in the pages you want to use &lt;strong&gt;Ckeditor&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/js/ckeditor5/build/ckeditor.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using Ckeditor in a textarea.&lt;/p&gt;

&lt;p&gt;I've made a blade component called Ckeditor so I can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;x-form.ckeditor&lt;/span&gt; &lt;span class="na"&gt;wire:model=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To render the editor. I'm using Livewire and AlpineJS.&lt;/p&gt;

&lt;p&gt;The component looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nf"&gt;props&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'label'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'required'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt;
        &lt;span class="c1"&gt;//remove underscores from name&lt;/span&gt;
        &lt;span class="nv"&gt;$label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'_'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;//detect subsequent letters starting with a capital&lt;/span&gt;
        &lt;span class="nv"&gt;$label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/(?=[A-Z])/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;//display capital words with a space&lt;/span&gt;
        &lt;span class="nv"&gt;$label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;implode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;//uppercase first letter and lower the rest of a word&lt;/span&gt;
        &lt;span class="nv"&gt;$label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ucwords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;strtolower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;endphp&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;endif&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;wire&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ignore&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mt-5"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="s1"&gt;'none'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{ &lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt; }}"&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"block text-sm font-medium leading-5 text-gray-700 dark:text-gray-200"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$label&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$required&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-red-600"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;*&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;@&lt;/span&gt;&lt;span class="k"&gt;endif&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;endif&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;textarea&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"
            ClassicEditor
                .create(&lt;/span&gt;&lt;span class="nv"&gt;$refs&lt;/span&gt;&lt;span class="s2"&gt;.item, {
                simpleUpload: {
                    uploadUrl: '{{ url('admin/image-upload') }}'
                }
                })
                .then(editor =&amp;gt; {
                    editor.model.document.on('change:data', () =&amp;gt; {
                    @this.set('{{ &lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt; }}', editor.getData());
                    })
               })
                .catch(error =&amp;gt; {
                    console.error(error);
                });
        "&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"item"&lt;/span&gt;
        &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$slot&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;textarea&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;enderror&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the important part is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="n"&gt;simpleUpload&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;uploadUrl&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'{{ url('&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;upload&lt;/span&gt;&lt;span class="s1"&gt;') }}'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Ckeditor where to upload files to.&lt;/p&gt;

&lt;p&gt;My route is defined inside an auth group so you have to be authenticated in order to use the upload route.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'web'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'auth'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//other routes&lt;/span&gt;
    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin/image-upload'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;UploadController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'index'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create an UploadController:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Storage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Intervention\Image\Facades\Image&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UploadController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'upload'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'upload'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getClientOriginalName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'png'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'.png'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'images'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts/'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s1"&gt;'url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/images/posts/&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, check if there is a file request.&lt;/p&gt;

&lt;p&gt;Next, collect the file, and define its file name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'upload'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getClientOriginalName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;          
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll use a slug to rename the file name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I use &lt;a href="https://image.intervention.io/v2" rel="noopener noreferrer"&gt;Intervention&lt;/a&gt; package to use its Image class to make the image and stream it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, remove any unwanted text from the file name&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'png'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'.png'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then save the image&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'images'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts/'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$img&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, return the image path so Ckeditor can load the image into the textarea&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/images/posts/&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>laravel</category>
      <category>ckeditor</category>
      <category>upload</category>
      <category>image</category>
    </item>
    <item>
      <title>How to detect human faces (and other shapes) in JavaScript</title>
      <dc:creator>Tomasz Jakut</dc:creator>
      <pubDate>Tue, 24 Nov 2020 15:50:07 +0000</pubDate>
      <link>https://forem.com/ckeditor/how-to-detect-human-faces-and-other-shapes-in-javascript-1b5i</link>
      <guid>https://forem.com/ckeditor/how-to-detect-human-faces-and-other-shapes-in-javascript-1b5i</guid>
      <description>&lt;p&gt;Google believes in a &lt;a href="https://www.chromium.org/teams/web-capabilities-fugu" rel="noopener noreferrer"&gt;Web that can compete with native applications unintimidated&lt;/a&gt;. One of the areas in which native applications for years have been superior to web applications was detecting shapes in images. Tasks such as face recognition were not possible until recently… But not anymore!&lt;/p&gt;

&lt;h2&gt;
  
  
  Shape Detection API
&lt;/h2&gt;

&lt;p&gt;A new standard proposal has recently been announced in the Web Platform Incubator Community Group (WICG): &lt;a href="https://wicg.github.io/shape-detection-api/" rel="noopener noreferrer"&gt;Shape Detection API&lt;/a&gt;. It allows detecting two types of shapes in an image:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://wicg.github.io/shape-detection-api/#face-detection-api" rel="noopener noreferrer"&gt;faces&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://wicg.github.io/shape-detection-api/#barcode-detection-api" rel="noopener noreferrer"&gt;barcodes and QR codes&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Currently, both of these detectors are implemented inside Chrome. Barcode detection is enabled by default and face detection is behind a flag (&lt;code&gt;chrome://flags#enable-experimental-web-platform-features&lt;/code&gt;). There is also one more specification defining &lt;a href="https://wicg.github.io/shape-detection-api/text.html" rel="noopener noreferrer"&gt;Text Detection API&lt;/a&gt; that allows detecting text.&lt;/p&gt;

&lt;p&gt;All of these detectors share the same API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FaceDetector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;optionalOptions&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;detector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;imageBitmap&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are three interfaces available globally (both inside the page and inside the Web Worker thread):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FaceDetector&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BarcodeDetector&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TextDetector&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;optionalOptions&lt;/code&gt; parameter is an object containing additional configuration for the detector. Every shape detector has its own set of options, but you can also omit this parameter altogether — in most cases, the defaults are usually enough.&lt;/p&gt;

&lt;p&gt;After constructing a detector, you can use its asynchronous &lt;code&gt;detect()&lt;/code&gt; method to actually detect shapes in the image. The method returns an object with the coordinates of the shape in the image and additional information about it (for example, recognized text in the &lt;code&gt;TextDetector&lt;/code&gt; API or coordinates of particular face parts, like eyes or nose, in the &lt;code&gt;FaceDetector&lt;/code&gt; API).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;imageBitmap&lt;/code&gt; parameter is the image to analyze, passed as an &lt;a href="https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#imagebitmap" rel="noopener noreferrer"&gt;&lt;code&gt;ImageBitmap&lt;/code&gt; instance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Side note: Why is this &lt;code&gt;ImageBitmap&lt;/code&gt; instead of just an &lt;code&gt;img&lt;/code&gt; element or simply a &lt;code&gt;Blob&lt;/code&gt;? This is because the shape detectors are available also inside workers, where there is no access to the DOM. Using &lt;code&gt;ImageBitmap&lt;/code&gt; objects resolves this issue. Additionally, they allow using more image sources, like &lt;code&gt;canvas&lt;/code&gt; elements  (including &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas" rel="noopener noreferrer"&gt;offscreen ones&lt;/a&gt;) or even &lt;code&gt;video&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And that's basically it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample application
&lt;/h2&gt;

&lt;p&gt;Ok, let's see how the new knowledge can be applied in practice. Let's prepare a sample web application that will allow you to detect shapes using the proposed API!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1jqhjk1dniws8xzlf9zj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1jqhjk1dniws8xzlf9zj.gif" alt="Shape detection API screencast" width="800" height="635"&gt;&lt;/a&gt;Detecting faces and text on an uploaded photo with the Shape Detection API.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTML
&lt;/h3&gt;

&lt;p&gt;Start with the &lt;code&gt;index.html&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Shape Detection API demo&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Shape Detection API&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Face detection&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Choose an image file:
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;"image/*"&lt;/span&gt; &lt;span class="na"&gt;data-type=&lt;/span&gt;&lt;span class="s"&gt;"face"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Barcode detection&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Choose an image file:
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;"image/*"&lt;/span&gt; &lt;span class="na"&gt;data-type=&lt;/span&gt;&lt;span class="s"&gt;"barcode"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Text detection&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Choose an image file:
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;"image/*"&lt;/span&gt; &lt;span class="na"&gt;data-type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file contains three &lt;code&gt;input[type=file]&lt;/code&gt; elements that will be the sources of images to analyze. All of them have a &lt;code&gt;[data-type]&lt;/code&gt; attribute that informs the script which shape you want to retrieve. There is also a &lt;code&gt;script[type=module]&lt;/code&gt; element that will contain the code needed to handle the &lt;code&gt;input&lt;/code&gt; elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;detectShape&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./detector.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// 2&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;detectShape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 4&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;detected&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 5&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, you import the &lt;code&gt;detectShape()&lt;/code&gt; function from &lt;code&gt;detector.mjs&lt;/code&gt; (1). This function will do the entire job.&lt;/p&gt;

&lt;p&gt;Then you bind the &lt;code&gt;change&lt;/code&gt; event listener to &lt;code&gt;document.body&lt;/code&gt; (2). It will react to all changes in &lt;code&gt;input&lt;/code&gt; elements thanks to the &lt;a href="https://javascript.info/event-delegation" rel="noopener noreferrer"&gt;event delegation&lt;/a&gt; mechanism.&lt;/p&gt;

&lt;p&gt;Additionally, the listener is asynchronous, as the detector is also asynchronous and I like to use the &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; syntax whenever I can.&lt;/p&gt;

&lt;p&gt;There is also a destructuring statement to get only the &lt;code&gt;target&lt;/code&gt; property of the &lt;code&gt;event&lt;/code&gt; object passed to the listener — so only the element which fired the event.&lt;/p&gt;

&lt;p&gt;Fortunately, the next line is not as crowded and it basically gets the file chosen by the user and saves it to the &lt;code&gt;image&lt;/code&gt; variable (3).&lt;/p&gt;

&lt;p&gt;When you get the image, you can just pass it to the &lt;code&gt;detectShape()&lt;/code&gt; function alongside the type of the detector, fetched from the &lt;code&gt;[data-type]&lt;/code&gt; attribute (4).&lt;/p&gt;

&lt;p&gt;After awaiting results, you can log them into the console (5).&lt;/p&gt;

&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;

&lt;p&gt;Let's move to the &lt;code&gt;detector.mjs&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// 5&lt;/span&gt;
    &lt;span class="na"&gt;face&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;fastMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;maxDetectedFaces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;barcode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detectShape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createImageBitmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 2&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nf"&gt;getDetectorName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;](&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//3&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;detector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 6&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;detected&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 7&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getDetectorName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}${&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;Detector`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 4&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;detectShape&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is only one export in this file, the default one: &lt;code&gt;detectShape()&lt;/code&gt; (1). This function converts the passed file (as a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File" rel="noopener noreferrer"&gt;&lt;code&gt;File&lt;/code&gt; instance&lt;/a&gt;) to the needed &lt;code&gt;ImageBitmap&lt;/code&gt; using the &lt;code&gt;createImageBitmap()&lt;/code&gt; global function (2). Then an appropriate detector is created (3).&lt;/p&gt;

&lt;p&gt;The constructor name is derived from the &lt;code&gt;type&lt;/code&gt; parameter. Its first letter is changed to the upper-case and the &lt;code&gt;Detector&lt;/code&gt; suffix is added (4).&lt;/p&gt;

&lt;p&gt;There is also an object containing options for every type of detector (5). Both the barcode and text detector will use the default options, however, for the face detector, there are two options: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fastMode&lt;/code&gt; – Switches on less accurate detection (that will recognize more faces but also increase the number of false positives).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maxDetectedFaces&lt;/code&gt; – Set to &lt;code&gt;1&lt;/code&gt; to detect only one face.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After creating the shape detector, you can call its &lt;code&gt;detect()&lt;/code&gt; method and await results (6). When the results arrive, return them (7).&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the application
&lt;/h3&gt;

&lt;p&gt;Coding is complete, however, the application will not work correctly if you start it directly from the directory. This is caused mainly by the fact that the code uses ES modules that are bound by &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener noreferrer"&gt;CORS rules&lt;/a&gt;. There are two solutions to these issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switch back to old, non-module JavaScript — you will not be cool anymore.&lt;/li&gt;
&lt;li&gt;Use a local web server to serve the site — you will still be cool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fortunately, using a local web server is as simple as running the following command inside the directory with the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx http-server ./
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will download and run the &lt;a href="https://www.npmjs.com/package/http-server" rel="noopener noreferrer"&gt;&lt;code&gt;http-server&lt;/code&gt; npm package&lt;/a&gt;. You can then navigate to &lt;code&gt;http://localhost:8080&lt;/code&gt; (or to another address that will be displayed in your terminal) and test your own barcode, text and face detector application. Remember to use Chrome with the experimental Web platform features enabled!&lt;/p&gt;

&lt;p&gt;And that's it! With the new Shape Detection APIs, it is fairly easy to detect certain shapes in the image — at least in Chrome. We will need to wait and see if other browsers will follow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Source code and demo
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/Comandeer/shape-detection-api-demo" rel="noopener noreferrer"&gt;The complete code of the application is available on GitHub&lt;/a&gt;. There is also a slightly enhanced and styled &lt;a href="https://ckeditor.github.io/labs/shape-detection-api/" rel="noopener noreferrer"&gt;live text, barcode and face detection demo&lt;/a&gt; available for you to play with. &lt;a href="https://github.com/ckeditor/labs/tree/master/docs/shape-detection-api" rel="noopener noreferrer"&gt;Its source code is also available on GitHub&lt;/a&gt;. Unfortunately, at the time of writing this article, shape detecting is not supported on Linux.&lt;/p&gt;

&lt;p&gt;As for the next steps, one of the most important applications of face detection is facial recognition. This technology matches human faces detected on images or video frames against a database of faces. As other biometric technologies, it can be used to authenticate users, interact with computers, smartphones or other robotic systems, automatically index images or for video surveillance purposes.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>How to become a better programmer</title>
      <dc:creator>Dominik Szczepaniak</dc:creator>
      <pubDate>Mon, 23 Nov 2020 11:02:27 +0000</pubDate>
      <link>https://forem.com/ckeditor/how-to-become-a-better-programmer-4bho</link>
      <guid>https://forem.com/ckeditor/how-to-become-a-better-programmer-4bho</guid>
      <description>&lt;p&gt;Being a programmer is not a simple thing. The variety of technologies, concepts, rules, and terminologies can be overwhelming for newcomers. What’s more, you need to be up-to-date all the time. Otherwise, you take a step back.&lt;/p&gt;

&lt;p&gt;But if you made an effort and started your developer career, there is still much more to do to improve your programming skills. This article presents some ideas on how to become a better programmer. You will also find out how your company can help you with this endeavor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous growth means challenging tasks
&lt;/h2&gt;

&lt;p&gt;To challenge yourself as a developer, it is worth to participate in as many and as diverse software projects as possible. If programming is your day-to-day job, consider what kind of tasks in your current project would be most challenging for you. Then talk about them with your superiors. If you see room for improvement in your current project, mention it.&lt;/p&gt;

&lt;p&gt;However, you shouldn't limit your learning experience to what your job throws at you. To develop yourself as a programmer you need to actively seek opportunities outside your daily work.&lt;/p&gt;

&lt;p&gt;No matter if you are an active developer working in the field, a beginner or an IT student, one of the most effective ways to improve as a programmer is to become a member of an open source project. You can start your search on &lt;a href="https://github.com/open-source" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Find a project that uses the technology stack you would like to learn about and make your first contribution. Sometimes it’s even better to start something from scratch — GitHub is also a great place to begin your own project. You can adjust the technology stack to your needs, which allows you to learn whatever you want. Your project can be a clone of an application you use. It can also be a new idea for an application you need.&lt;/p&gt;

&lt;p&gt;You shouldn’t be afraid of programming tasks that seem to be hard or even impossible. Facing challenges and leaving your comfort zone is one of the best things you can do to speed up your growth. The most obvious benefit is experience which will make you more attractive in the job market. According to the &lt;a href="https://manifesto.softwarecraftsmanship.org/" rel="noopener noreferrer"&gt;Software Craftsmanship&lt;/a&gt; approach, both theoretical knowledge and experience are required to deliver good software.&lt;/p&gt;

&lt;p&gt;An interesting approach to learning to code is &lt;a href="http://codekata.com/" rel="noopener noreferrer"&gt;CodeKata&lt;/a&gt;, which emphasizes combining practical skills with theory. This approach assumes that after gaining new theoretical knowledge, you should use it in practice. The more you practice, the more you learn along the way. To learn all you can, practice often. In this approach practicing is the goal, not a solution.&lt;/p&gt;

&lt;p&gt;In challenging yourself, you will also gain confidence. Perhaps you have imposter syndrome, a psychological pattern in which you constantly doubt your own achievements. It is estimated that up to 70% of the population will experience at least one episode of imposter syndrome in their lives. The first observable symptom is that you don’t believe you really deserve your success, which can discourage you and lead you to slow or even stop your growth. You can find a more in-depth analysis in &lt;a href="https://so06.tci-thaijo.org/index.php/IJBS/article/view/521/pdf" rel="noopener noreferrer"&gt;this thesis&lt;/a&gt; prepared by the Behavioral Science Research Institute.&lt;/p&gt;

&lt;p&gt;However, remember to choose your tasks carefully. Be reasonable and avoid taking on too much complex work at once. Failures also can teach you a lot, but don’t bite off more than you can chew. Nevertheless, challenging yourself on your way to becoming a better programmer has many more advantages than disadvantages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn how to learn
&lt;/h2&gt;

&lt;p&gt;After graduating from school or university some people lose their willingness to learn. Even for people who like learning, it may be hard to stay in a constant learning mode. Fortunately, there are &lt;a href="https://www.youtube.com/watch?v=9oWOsocN7qg" rel="noopener noreferrer"&gt;a few tips that can help motivate you&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, it is helpful to learn more about the topics you are interested in. Even if programming isn’t your hobby, you can create an application or website that is related to your hobbies. Second, avoid context switching and learn only one thing at a time. Third, make sure you understand the topic, not only memorize it. Memorization is effective only in the short term. Finally — learn from more than one source. The way knowledge is learned is the most important aspect of learning aside from the knowledge itself.  If you prefer videos — watch videos. If you prefer books — read books.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqo9ndlglh5arglyu4p50.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqo9ndlglh5arglyu4p50.jpg" alt="Blackboard with various programming languages written; C++, Scla, JavaScript, GO, PHP, Java, CSS, Ruby C#, HTML, Python"&gt;&lt;/a&gt;As a programmer, you have lots to learn. Do it wisely!&lt;/p&gt;

&lt;p&gt;For effective learning, you will need the right environment. To start, clean your desk. Remove all unnecessary books, notebooks, and distractions. Turn off the TV, mute notifications from your phone, and stay off social media. If you feel that you need a break, then go for a walk. Getting your blood flowing from a brief walk may even spark some ideas to help you tackle the challenge at hand.&lt;/p&gt;

&lt;p&gt;You should also consider getting official certification. For example, for non-native English speakers certificates like &lt;a href="https://www.cambridgeenglish.org/exams-and-tests/cefr/" rel="noopener noreferrer"&gt;FCE, CAE, and CPE&lt;/a&gt; are useful. If you are passionate about testing, you can consider getting your &lt;a href="https://www.istqb.org/certification-path-root/why-istqb-certification.html" rel="noopener noreferrer"&gt;ISTQB&lt;/a&gt; certification. To validate your cloud expertise, an &lt;a href="https://aws.amazon.com/certification/" rel="noopener noreferrer"&gt;Amazon certificate&lt;/a&gt; is a valuable asset. These certificates are not cheap, but the invested money can be additional motivation to learn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn how to be self-reliant
&lt;/h2&gt;

&lt;p&gt;When you run into a problem, it is tempting to ask your colleagues or create new questions on Stack Overflow. Unfortunately, relying on somebody else's work or knowledge is just a quick fix. In the long run, a lack of self-reliance will negatively affect your path to become a better programmer.&lt;/p&gt;

&lt;p&gt;To learn more and reach your potential, you need to make mistakes on your own. Of course, there is nothing wrong with googling and reading case studies. Many known programming problems have been resolved this way. Don’t try to reinvent the wheel and don’t be afraid to use well-known solutions. However, the very first person you should seek help from is you. There is nothing wrong with asking other people, but research the problem and come up with some ideas first. Do not always expect somebody else to solve your issue.&lt;/p&gt;

&lt;p&gt;If you need help, know how to ask the right questions. For starters, ensure that the description of the problem is sufficient. Provide as many details as possible. If you’ve found a bug, provide reproduction steps, error stack, or a screenshot if possible. It’s good to also compare the pros and cons of the potential solutions to your problem. You can ask Stack Overflow, GitHub, or even Slack channels related to your field.&lt;/p&gt;

&lt;p&gt;A bad question may be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How do I quit Vim!? Please help!”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now compare how the question should be asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I need to close the Vim editor. I’ve tried to close it by using Esc and Alt+F4 and it didn’t work. I can turn off my workstation but I have some changes in a document and I’d like to save them. Can somebody explain to me what I should do to close Vim and save changes?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can find the full list of good practices related to creating questions at the &lt;a href="https://stackoverflow.com/help/how-to-ask" rel="noopener noreferrer"&gt;Stack Overflow Help Center&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expose your code
&lt;/h2&gt;

&lt;p&gt;If you don’t have an account at a place like GitHub or GitLab and want to become a better programmer, you should create one as soon as possible. The code you create says a lot about you. It shows your way of thinking, your skill, and your progress. It documents your growth as a developer. However, you need to be open to criticism.&lt;/p&gt;

&lt;p&gt;Publishing your code also allows other programmers to review it. Even though being self-reliant is crucial to personal growth, the perspective of more experienced coders is extremely useful during the learning process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3arnlkamdz03k5f93qj0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3arnlkamdz03k5f93qj0.jpg" alt="Visual presenting and abstract code editing and reviewing screen"&gt;&lt;/a&gt;Show your code and be ready to have it reviewed.&lt;/p&gt;

&lt;p&gt;If you don't want to develop your own project, you can try to contribute to others’ open source projects. To make your first contribution, you will need to study a lot of code. Very often it will be undocumented code with a different code style than yours. It is worth it, though. You will not only learn a lot but also become more recognizable in programming communities.&lt;/p&gt;

&lt;p&gt;To dive into a project smoothly, you can find an issue with the "good first issue" ("easy" or "beginner-friendly") label. This label is dedicated to tasks that are good points to start in a project, or are relatively easy to do. For example, this label is available in the &lt;a href="https://github.com/ckeditor/ckeditor4/labels/good%20first%20issue" rel="noopener noreferrer"&gt;CKEditor 4 repository&lt;/a&gt;, as well as in other very popular projects like &lt;a href="https://github.com/nodejs/node/labels/good%20first%20issue" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; or &lt;a href="https://github.com/microsoft/vscode/labels/good%20first%20issue" rel="noopener noreferrer"&gt;VSCode&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Socialize and network
&lt;/h2&gt;

&lt;p&gt;One of the simplest ways to dive into the programming world is to take part in conferences&lt;br&gt;
and meetings. Being a speaker will help you grow your experience. If you don’t feel confident enough to do a full presentation, you can give a lightning talk, a brief form of presentation that lasts only a few minutes. A short presentation in &lt;a href="https://prezi.com/" rel="noopener noreferrer"&gt;Prezi&lt;/a&gt;, &lt;a href="https://www.google.com/slides/about/" rel="noopener noreferrer"&gt;Google Slides&lt;/a&gt;, or even just a slide containing a piece of code with explanation will do. Even if you don’t feel comfortable with public appearances, just attend them and listen to what others have to say. Case study presentations are extremely useful for learning what went right and wrong in coding experiments.&lt;/p&gt;

&lt;p&gt;It is said that people should learn from their mistakes. But it is much more comfortable to learn from the mistakes of other people. Conferences also give coders a forum to discuss, network and make new contacts in the developers community. Do not forget to add your new contacts to your social media accounts to stay in touch. If you don’t use social media, it is a good idea to at least create a &lt;a href="https://www.linkedin.com/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; account for this purpose.&lt;/p&gt;

&lt;p&gt;Engaging with other programmers is a brilliant way to verify your knowledge and opinions. It is worth trying even if you are an introvert. Encountering new knowledge and the alternate opinions of others can make a profound difference. Leaving your comfort zone can lead you to some realizations that you haven’t had before, which will only help you grow as a developer. Discussions with other programmers can also be the first step to become a better public speaker.&lt;/p&gt;

&lt;p&gt;Additionally, you can also join dedicated groups on Facebook, Reddit, or Slack channels to get even more new contacts in the software community, and to exchange knowledge and ideas.&lt;/p&gt;

&lt;p&gt;Try to also reach out to locally-oriented social media groups in your area, for example on Facebook. You can find them through &lt;a href="https://www.meetup.com/" rel="noopener noreferrer"&gt;Meetup&lt;/a&gt; as well, which will let you join local coding events and notify you of new ones. Another place to search for these groups is at universities. At almost every university there are also coding communities.&lt;/p&gt;

&lt;p&gt;Socializing also helps you develop your soft skills that are needed to become a better programmer. Being communicative, helpful, respectful, and open-minded has tremendous value, as does having the ability to see the software from the customer’s point of view. Soft skills are the key to advancement in a programming career, especially if you want to become a team lead, mentor or trainer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read, watch, and listen
&lt;/h2&gt;

&lt;p&gt;To use knowledge in practice, you need to acquire it first. There is a wide range of knowledge sources to help you become a better programmer. The most obvious are books. However, it is important to choose a book wisely, as many are outdated (or are going to be outdated shortly). Mostly, they deal with specialized technologies, languages or frameworks. Before you start reading, check reviews and even the table of contents. It will help you filter out books not worth your time. With that said, many books are evergreen titles for software developers. Here are some books that probably will never go out of style and will let you improve as a programmer:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fsrd7gowb2noga2na49jx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fsrd7gowb2noga2na49jx.jpg" alt="stacked books"&gt;&lt;/a&gt;There is a wide range of knowledge sources that you can choose from.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Pragmatic Programmer by David Thomas and Andrew Hunt;&lt;/li&gt;
&lt;li&gt;Refactoring: Improving the Design of Existing Code by Martin Fowler;&lt;/li&gt;
&lt;li&gt;Introduction to Algorithms by Thomas H. Cormen;&lt;/li&gt;
&lt;li&gt;Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin;&lt;/li&gt;
&lt;li&gt;Agile Software Development, Principles, Patterns, and Practices by Robert C. Martin;&lt;/li&gt;
&lt;li&gt;Working Effectively with Legacy Code by Michael C. Feathers;&lt;/li&gt;
&lt;li&gt;The Clean Coder: A Code of Conduct for Professional Programmers by Robert C. Martin;&lt;/li&gt;
&lt;li&gt;Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides;&lt;/li&gt;
&lt;li&gt;Domain-Driven Design: Tackling Complexity in the Heart of Software by Eric Evans;&lt;/li&gt;
&lt;li&gt;Cracking the Coding Interview: 150 Programming Questions and Solutions by Gayle Laakmann McDowell;&lt;/li&gt;
&lt;li&gt;Patterns of Enterprise Application Architecture by Martin Fowler;&lt;/li&gt;
&lt;li&gt;The Mythical Man-Month: Essays on Software Engineering by Frederick P. Brooks;&lt;/li&gt;
&lt;li&gt;Algorithms + Data Structures = Programs by Niklaus Wirth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The alternatives for books are newsletters and blogs. Here is a list of blogs that are worth following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://netflixtechblog.com/" rel="noopener noreferrer"&gt;Netflix Tech Blog&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hackernoon.com/" rel="noopener noreferrer"&gt;Hacker Noon&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.cleancoder.com/" rel="noopener noreferrer"&gt;The Clean Code Blog&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://overreacted.io/" rel="noopener noreferrer"&gt;Overreacted&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.blog/" rel="noopener noreferrer"&gt;The GitHub blog&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/"&gt;The Practical Dev&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://davidwalsh.name/" rel="noopener noreferrer"&gt;David Walsh&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://css-tricks.com/" rel="noopener noreferrer"&gt;CSS Tricks&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.codepen.io/" rel="noopener noreferrer"&gt;CodePen Blog&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://khalilstemmler.com/" rel="noopener noreferrer"&gt;Khalil Stemmler&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.smashingmagazine.com/" rel="noopener noreferrer"&gt;Smashing Magazine&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ckeditor.com/blog/" rel="noopener noreferrer"&gt;CKEditor Ecosystem Blog&lt;/a&gt; 😉&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unfortunately, many articles on less-known blogs contain mistakes and false claims. What’s more, a lot of that content is dedicated to beginners or is strongly opinionated. But the big advantage is the blogs are mostly free. An awesome tool you can use to read blogs is &lt;a href="https://app.getpocket.com/" rel="noopener noreferrer"&gt;Pocket&lt;/a&gt;, which provides a mobile app and browser plugin with a nice and convenient UI that lets you save articles for later reading.&lt;/p&gt;

&lt;p&gt;Another good place to start improving as a programmer is issues and pull requests in open source projects. Discussions on GitHub, for example, are a plentiful source of knowledge. Even if you aren’t working on an issue, you can still read or comment. See how it looks in &lt;a href="https://github.com/ckeditor/ckeditor5-engine/pull/1205" rel="noopener noreferrer"&gt;CKEditor 5&lt;/a&gt; or in &lt;a href="https://github.com/nodejs/node/issues/6456" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have a bigger or more flexible budget, you can consider buying a more advanced training course. The cost of these courses can be very high, but the return of investment can be fast and significant. Before signing up, read some reviews. Be sure to check that your course of choice will get you a valid, reputable certification (and for how long).&lt;/p&gt;

&lt;p&gt;Last but not least, audiovisual media can help you expand your knowledge. Videos on YouTube, podcasts, or courses on e-learning platforms like &lt;a href="https://www.udemy.com/" rel="noopener noreferrer"&gt;Udemy&lt;/a&gt; are all good ways to learn about programming. The biggest advantage of these forms of education, especially podcasts, is that they are easy to use. You can read, watch, or listen to them during your daily workout routine or on public transport. Listening to podcasts in a tram or a gym won't make you a noticeably more talented programmer overnight, but it is a tiny step forward in your development as a programmer. Additionally, archival materials from conferences are incredibly insightful. Many presentations are recorded and then shared on YouTube, such as &lt;a href="https://www.youtube.com/playlist?list=PLNYkxOF6rcIDA1uGhqy45bqlul0VcvKMr" rel="noopener noreferrer"&gt;Chrome Dev Summit 2019&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Take care of your health
&lt;/h2&gt;

&lt;p&gt;Do not neglect your diet and health. A &lt;a href="https://www.helpguide.org/articles/healthy-living/the-mental-health-benefits-of-exercise.htm" rel="noopener noreferrer"&gt;bit of sport&lt;/a&gt; and &lt;a href="https://www.mhanational.org/healthy-diet-eating-mental-health-mind" rel="noopener noreferrer"&gt;healthy eating&lt;/a&gt; can positively affect your brain. Try to reduce coffee, alcohol, and cigarettes. Consider eating more fruit, vegetables and &lt;a href="https://smallbiztrends.com/2017/02/brain-food-snacks.html" rel="noopener noreferrer"&gt;brainfood&lt;/a&gt;. Reduce &lt;a href="https://www.aipt.edu.au/articles/2018/10/what-happens-your-brain-when-you-eat-junk-food" rel="noopener noreferrer"&gt;junk food in your diet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Do not forget to sleep enough — late working hours won’t help you become a better programmer. Lack of sleep has also a &lt;a href="https://sportsmedicineweekly.com/2020/07/14/science-sleep-why-you-need-7-8-hours-night/" rel="noopener noreferrer"&gt;negative influence on other parts of your life&lt;/a&gt;. Even worse if you don’t get enough sleep and drink a lot of coffee to get going in the morning. It has a &lt;a href="https://www.independent.co.uk/life-style/food-and-drink/news/why-drinking-coffee-first-thing-in-the-morning-is-a-bad-idea-10291621.html" rel="noopener noreferrer"&gt;destructive impact on your body and your mind&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Consider using a &lt;a href="https://www.revolutioncycleandski.com/articles/top-10-reasons-to-bike-instead-of-driving-pg100.htm" rel="noopener noreferrer"&gt;bike instead of a car&lt;/a&gt; or public transport. Don’t hesitate to walk to work if the distance is relatively short. Avoid sitting in front of a computer for long stretches. Take &lt;a href="https://www.themuse.com/advice/take-five-51-things-to-do-when-you-need-a-break-at-work" rel="noopener noreferrer"&gt;short breaks&lt;/a&gt; and short walks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work for a company that invests in your development
&lt;/h2&gt;

&lt;p&gt;If you work for a caring company, your path to becoming a better programmer can be much easier. Many companies know that investing in employees will benefit all involved. What’s more, some of these investments are cheap and easy to introduce. Still, some companies may not be so generous. If your company has no obvious development programs, try speaking with your superiors. Explain to them what practices are worth introducing and why they are beneficial for the company. Focus on the benefits for your company and the company’s bottom line. What benefits of employee investment are there, they might ask? Well, consider the examples below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Assigning tasks to more than one person
&lt;/h3&gt;

&lt;p&gt;Organized cooperation can shorten the development process. It also stimulates the knowledge sharing process among employees. Potential problems can be detected and resolved faster. Common goals also mean better relationships between employees.&lt;/p&gt;

&lt;p&gt;Pair programming, where colleagues sit together and/or share screens, is another solution.  Their ideas can be easily exchanged, and potential problems can be recognized and solved more quickly and satisfactorily.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code reviews
&lt;/h3&gt;

&lt;p&gt;Code reviews have benefits both for employees and for the company. It is always better to detect a problem during the review process than after the release. In general, they reduce costs and save developers time. Code reviews also save you time later — many bugs could have been avoided if the code was reviewed well in the first place. In addition to that, it provides the same benefits as assigning tasks to more than one person and stimulates the knowledge sharing process.&lt;/p&gt;

&lt;p&gt;Code review can be also the beginning of a longer discussion. Opinions and biases can be challenged, which leads to further learning and growth. During the review, problems and areas of improvement can be discovered. See how it looks in the &lt;a href="https://github.com/ckeditor/ckeditor5-engine/pull/1503" rel="noopener noreferrer"&gt;CKEditor 5 repository&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2xg3b90ja26art6yjh8b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2xg3b90ja26art6yjh8b.png" alt="Screenshot of a GitHub dicussion regarding code review"&gt;&lt;/a&gt;A discussion during the code review can be enlightening.&lt;/p&gt;

&lt;h3&gt;
  
  
  Development fund
&lt;/h3&gt;

&lt;p&gt;Another form of supporting employees is a development fund. Books and courses are very often quite expensive, and participating in conferences usually isn’t free either. Setting up a development fund is the most direct form of investing in an employee’s development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Have an impact on the final product and used technologies
&lt;/h3&gt;

&lt;p&gt;Having a real impact on the developed product not only sparks motivation, but it also helps you advance in your chosen career path. But with opportunities comes also responsibility. You are responsible for the technology you use. You will need to maintain, develop, and perhaps fix it in the future.&lt;/p&gt;

&lt;p&gt;However, the whole process from research through implementation to deployment will teach you a lot. First, you will need to do research — find the pros and cons of proposed solutions, and finally, choose the best one. Next, you will need to prepare a prototype, then a ready-to-use implementation. Finally, you will have to handle the product release.&lt;/p&gt;

&lt;p&gt;Each step can teach you a lot. Being a better programmer means not only being good at implementing features and fixing bugs. It means being good at research, maintaining products, and convincing others to use your ideas.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to find these companies?
&lt;/h3&gt;

&lt;p&gt;To find the right company, the most direct way is to read reviews. &lt;a href="https://blog.ongig.com/employer-branding/the-top-5-sites-for-employer-reviews-ratings/" rel="noopener noreferrer"&gt;Employer review sites&lt;/a&gt; are a good place to start. These pages provide very detailed information about salary, interview, and opportunities to grow.&lt;/p&gt;

&lt;p&gt;You can also ask other programmers about their companies, or for companies they recommend. If you participate in meetups and conferences, you can often see which companies programmers work for. Focus on t-shirts and badges. If you meet some brilliant people that talk about interesting projects, why not apply to the companies where they work? Go and ask them about their employer and projects they participate in. What’s more, if you see a programmer actively using their company’s gadgets, it is a good sign that the company invests in employees.&lt;/p&gt;

&lt;p&gt;All of these benefits are ones you can find working on &lt;a href="https://ckeditor.com/" rel="noopener noreferrer"&gt;CKEditor Ecosystem&lt;/a&gt; products for CKSource. Did you know that we are hiring? Visit the &lt;a href="https://cksource.com/careers/" rel="noopener noreferrer"&gt;career page&lt;/a&gt; and see what else we can do to boost your growth!&lt;/p&gt;

&lt;h2&gt;
  
  
  But most of all – have fun with programming
&lt;/h2&gt;

&lt;p&gt;Without having fun with programming, it will be more difficult to develop your coding skills. As I mentioned before, programming isn’t a simple thing. Learning a lot of stuff that’s hard to understand without liking it is even more challenging than programming itself.&lt;/p&gt;

&lt;p&gt;You need to be aware that programming is not only implementing new features and maintaining clean and perfect code. Very often you will spend hours or even days on arduous debugging or refactoring. You will also work with poorly written and buggy code.&lt;/p&gt;

&lt;p&gt;However, thanks to programming you can create incredible things. You can simplify, automate, or even get rid of many daily activities. Furthermore, you can also develop your other hobbies. For example, if you are interested in meteorology, you can build and program a weather station. Then you can collect statistics and make forecasts. Or whatever else you want! That’s the magic of programming.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3rwe5nzl5ujse32f3mzu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3rwe5nzl5ujse32f3mzu.jpg" alt="Remote controller and a toy car"&gt;&lt;/a&gt;Have fun with programming!&lt;/p&gt;

&lt;p&gt;People don’t only live to work. You can develop and use your skills by playing some coding games:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://flexboxfroggy.com/" rel="noopener noreferrer"&gt;Flexbox Froggy&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cssgridgarden.com/" rel="noopener noreferrer"&gt;Grid Garden&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codingame.com/start" rel="noopener noreferrer"&gt;CodinGame&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vim-adventures.com/" rel="noopener noreferrer"&gt;VIM Adventures&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codecombat.com/" rel="noopener noreferrer"&gt;CodeCombat&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another way to get a lot of satisfaction from programming is to create software that people need. Positive opinions from grateful customers and community can inspire a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  And that's still not all...
&lt;/h2&gt;

&lt;p&gt;Becoming a better programmer is an everyday job. It takes a lot of effort and dedication. There is no one universal path to improve as a software developer and this article, while giving you some hints, is definitely not complete. Why don’t you share your tips and tricks to become a better programmer in 2020 in the comments? You can also reach out to us on our &lt;a href="https://twitter.com/ckeditor" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;. 😉 &lt;/p&gt;

</description>
      <category>programming</category>
      <category>beginners</category>
      <category>learning</category>
      <category>codenewbie</category>
    </item>
  </channel>
</rss>
