Workgroup - Django CMS CKEditor5 Technical Brief ============================================================================ ###### tags: `workgroup` ## Workgroup Goals - integrate ckeditor5 modern & reliable UX - allow to extend it with the new ckeditor5 es6 framework - allow to create flexible es6 extentions for ckeditor5 instead of the old in-text plugins - implement the new url management system (based on - rest api based - allow to link custom models, eg apphooks (blog articles, events, etc) - automatically handle redirects ## Links - PR (draft) - - - login: - password: - (for access ask Victor) ## Milestones | status | date | description | | ------ | ---------- | ----------- | | ✅ | 10.12.2020 | deploy a demo project to divio with plain ckeditor5 integration | | ✅ | 17.12.2020 | create a demo ckeditor5 framework es6 plugin and add it to the PR | | ✅ | 21.12.2020 | specify the djangocms-url-manager integration | | ⚙️ | Q3 2021 | the python packages from pypi should be integratable with djangocms-text-ckeditor5 (eg as djangocms-text-ckeditor5-image) | | ⚙️ | Q3 2021 | implement REST api for the url manager along with the new CMSPlugin'less architecture | | ⚙️ | Q3 2021 | implement a ckeditor5 es6 plugin for the url manager | ## Tentative Roadmap - 2022 Q2 - the first alpha release (PoC) - 2022 Q3 - the beta community release - collect feedback form the community, verify the feasibility of the selected direction - 2022 Q3-Q4 - the stable release ## Approximate specification Main points: - drop the in-text plugins support, no migration *or* - keep the in-text plugins but allow tight binding with es6 ck5 plugin - allow to extend CKEditor5 through [its es6 framework]( - for example a dev can: - copy-past webpack.config.js - add custom action items, plugins, toolbars buttons, etc - build it and put the compiled app in `static/djangocms_text_ckeditor5/app.js` - run `python collectstatic` which would override the default `static/djangocms_text_ckeditor5/app.js` from pypi - alternatively, django CMS apps might register extensions which are loaded dynamically (feasibility with webpack DLL to be checked) - implement a rest api based url manager plugin - implement a rest api based link plugin based on legacy links - release as a separate pypi package, eg [djangocms-text-ckeditor5]( ### api draft ideas - GET `/api/urls/types/` returns a list of `django.contrib.contenttypes.models.ContentType` instances that can be attached to urls - or maybe not content types, because we also need to have types as `phone`, `email`, etc - POST `/api/url/`, which would create a new `Url` model instance by accepting - `type` - either a content type or a Enum type as `Type.PHONE`, `Type.EMAIL` - `instance_id`, eg - `label` - the text label to be rendered - a custom ckeditor5 es6 plugin would fetch the required `Url` django model instance by id from the respective endpoint. To implement that we can store the link code in html as following `<a data-url-id="35" data-plugin-type="django-url-manager" href="${url.path}">label from ckeditor</a>` - We need to specify how the link is resolved on published pages where no CKEditor is loaded and just the HTML is rendered. Where does `${url.path}` come from? standarization: - /api/url/{id} GET - /api/url/{id} POST - /api/url/{id} DELETE - /api/url/{id} PATCH which django model fields do we want standarize: - is_attached_to_cms_plugin: bool = False And then we might have a cronjob that drops the ghost models, eg `Url.objects.filter(is_attached_to_cms_plugin=False).delete()` - but this will drop the Url instancesa that an editor might intend to save in the next few minutes ### es6 plugins integration proposition - ckeditor-build with `npm run build` a dist ckeditor.js blob and push it to pypi - the developer runs pip install djangocms-ckeditor-text - the developer creates a custom es6 ckeditor5 plugin in his project and compiles it into `demo_plugin/static/demo-plugin.js` - we're planning to allow that using webpack DDL + ckeditor 5 integration - - the developer adds to his `` something as ```python CKEDITOR5_PLUGINS = [ 'demo_plugin/static/demo-plugin.js', ] ``` *or* - we offer a ck5 plugin pool allowing the developer to register his plugin - ckeditor5 in its `render_template` renders: ```html <script src="{% 'djangocms_text_ckeditor/ckeditor5.js'"> <script> CKEditor.init({ plugins: {{ settings.CKEDITOR5_PLUGINS }} }) </script> ``` *or* - we use data attributes, e.g. of script tags, to send configuration for inline editors, admin editors and HTML fields to the frontend. #### Plugins integration use cases 1. Installing djangocms-text-ckeditor5 from pypi should work out of the box without webpack build 2. Installing third-party djangocms-text-ckeditor5 plugins from pypi (eg as djangocms-text-ckeditor5-image) should work without webpack build - by adding it to 3. creation of a custom es6 ckeditor5 plugin - which would have to use a webpack/esbuilt/rollup builder, since it would require ckeditor5 es6 framework - Alex: it seems to be possible to connect custom es6 plugins to ckeditor5 framework to webpack DLL system without requiring a specific builder (eg webpack) - Andrew: the case #3 would be as important as #2 #### links for dynamic loading of plugins init a compiled ckeditor5 by selector - A ticket about loading plugins with a library function - ```html <script src="../build/ckeditor.js"></script> <script> CKEDITOR.ClassicEditor .create( document.querySelector( '#classic-editor' ) ) .catch( err => { console.error( err.stack ); } ); CKEDITOR.InlineEditor .create( document.querySelector( '#inline-editor' ) ) .catch( err => { console.error( err.stack ); } ); </script> ``` ### edge cases #### Ghost models Our case of potential django "ghost" models: 1. user opens a new ckeditor5 creation modal 2. user adds a url es6 plugin which creates an instance with `Url.objects.create(pk=1, is_attached_to_cms_plugin=False)` 3. user closes the tab 4. the `Url` models instance stays attached to nothing and we need to define a cleaning routine to get rid of it Questions: Is this a problem? Currently the djangocms-url-manager requires to create an unattached url before using it. It is unclear why a site should not have a set of potential link targets in its url database w/o using them immediately. This capability requires the rewrite of djangocms-url-manager admin logic in es6. Is this the right place for it? #### Any existing in-text plugins that can't be replaced with ckeditor5 es6 framework? - Anything non-local that, e.g. uses the `context` to get information. My favourite example: Footnote plugin that only renders consecutive numbers and stores the content for later rendering in the footer. - Also, local information (like links) probably need to resolve their information to html (i.e. content type = Page, id = 7 corresponds to url /this/is/great). This requires interaction with the backend either by in-text plugins or by ajax calls. The latter solution introduces latency for links work / images show. ## Pre-MVP launch (Q3) - document why we're dropping the plugins (or leave them?) - WYSIWYG correct styling - ie when the resulting plugin is rendered with a specific background and fonts, the WYSIWYG editing window should render it respectively. - It has been implemented for CKEditor4 already to some extend, we would need to integrate it though - CK5 has little but opinionated styling hard coded. This leads to deviations from document stylings. Potential solution: Identification of unwanted CK5 styling and removing it from the site and admin forms using javascript. - The missing stylings lead to admin styling in admin forms. To avoid this, the document css needs to be identified and supplied to the ck5 field. Beware of potential gaps between browser default styling on the site and django admin styling in the form where the site styling fails to override. <details> <summary>example screenshot of a footer editing in CKEditor4</summary> ![]( </details> * Inline editing? ![]( ## Post-MVP Launch (Optional Features) - image plugin - localization - the documentation for the developers who would like to upgrade from ckeditor 4 to 5 - [Restricetive editing]( - we can try to use it eg when only few areas of a plugin are supposed to be editable. Up until now we would move those fields to a django model as CharFields, but restrictive editing has the potential to resolve it. - IE11 support