How to specify features for a RichTextBlock in wagtail - wagtail

I have a page that is using both a RichTextField and a StreamField with RichTextBlocks.
class TwoColumnBlock(StructBlock):
content = StructBlock(
[
("left_column", StreamBlock([("paragraph", RichTextBlock()), ("image", ImageChooserBlock())], max_num=1)),
("right_column", StreamBlock([("paragraph", RichTextBlock()), ("image", ImageChooserBlock())], max_num=1)),
]
)
class ScrollingExhibitPage(Page):
banner_text = RichTextField(blank=True, features=["bold", "italic"])
body = StreamField(
[("one_column", OneColumnBlock()), ("two_column", TwoColumnBlock())],
blank=True,
)
I'd like to have it so that anywhere a user is entering rich text, they are seeing the same options in the editor. However, I haven't been able to find any mention in the wagtail docs of how to set features for a RichTextBlock like you can for a RichTextField.
How would I go about doing that?

RichTextBlock accepts a features argument just like RichTextField does - this is documented at https://docs.wagtail.org/en/stable/reference/streamfield/blocks.html#wagtail.blocks.RichTextBlock.
class TwoColumnBlock(StructBlock):
content = StructBlock(
[
("left_column", StreamBlock([("paragraph", RichTextBlock(features=["bold", "italic"])), ("image", ImageChooserBlock())], max_num=1)),
("right_column", StreamBlock([("paragraph", RichTextBlock(features=["bold", "italic"])), ("image", ImageChooserBlock())], max_num=1)),
]
)

You can use WAGTAILADMIN_RICH_TEXT_EDITORS to configure different feature sets you can use different places. So you could have a full-featured version for your default and then a minimal editor that only allows bold and italics in other places. So an example from my code:
WAGTAILADMIN_RICH_TEXT_EDITORS = {
'default': {
'WIDGET': 'wagtail.admin.rich_text.DraftailRichTextArea',
# since sub, super, and a couple more are not included by default, we need to add them in this config
'OPTIONS': {'features': ['bold', 'italic', 'superscript', 'subscript', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul',
'hr', 'blockquote', 'pre', 'link', 'embed', 'document-link', 'image']}
},
'minimal': {
'OPTIONS': {
'features': ['bold', 'italic', 'subscript', 'superscript', 'link']
}
}
}
And then when you want to use the minimal one, you can do something like:
description = RichTextField(
editor='minimal',
blank=True,
)
caption = blocks.RichTextBlock(
required=False,
label='Caption',
editor='minimal',
)

Related

Tailwind ignoring darkMode: "class", not paying attention to class="dark" on html tag

Using tailwind and react, I am using a config file as follows:
/** #type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{html,js,jsx,ts,tsx}"],
darkMode: "class",
important: "#app",
theme: {
colors: {
...
},
fontFamily: {
...
},
extend: {},
},
plugins: [],
corePlugins: {
preflight: false,
},
};
and setting the class as follows
darkMode
? document.documentElement.classList.add("dark")
: document.documentElement.classList.remove("dark");
Inspecting the top element, it is indeed adding and removing a class="dark" to the top element. However, two elements like
text-grey-300 dark:text-grey-700
aren't changing when the class changes, at all. When the darkMode: Class is removed, it accurately follows OS preference however. Whats going on?
I have tried changing the class selector manually, tried placing the dark on other elements, moving the declaration around in my config file, none of it seems to help.

CKEditorError: ckeditor-duplicated-modules: Some CKEditor 5 modules are duplicated

import CKEditor from '#ckeditor/ckeditor5-react';
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
import Base64UploadAdapter from '#ckeditor/ckeditor5-upload/src/adapters/base64uploadadapter';
Getting ckeditor 5 duplicate modules error. Anyone can help me.
Thanks in Advance.
It's because you are importing the plugin with the build !
In order to add plugins, you have to make a personnal build. Please read this page to know more about it : ckeditor offical documentation.
They even have an official online builder that does all the work for you ! : ckeditor online builder
Once you created it, you have to import the editor just as you have done before on line 2 but instead of writing from "#ckeditor/ckeditor5-build-classic" you have to write from "adress of the build folder of your personnal build".
I hope it helped you.
I had this problem when I tried to add CKEditor and a plugin separately.
the easiest way is go to CKEditor Online Builder and choose what plugins and toolbar items you need then after five steps the code that you need to work with is generated.
Then you can use the file named ckeditor.js in build folder and this is almost all that you need.
1- Add CKEditorModule
#NgModule({
imports: [CKEditorModule],
...
}
2- Add the CKEditor tag to your template
<ckeditor
[editor]="Editor"
[(ngModel)]="notification.body"
(ready)="onReady($event)"
[config]="config"
></ckeditor>
3- import the customized CKEditor js file(that you should copy from build folder in downloaded customized CKEditor)in your component
import * as customEditor from './ckeditor';
4- Create a property in your component
public Editor = customEditor;
5- Add your configs
ngOnInit() {
this.config = {
toolbar: {
items: [
'heading',
'|',
'fontSize',
'fontFamily',
'|',
'bold',
'italic',
'underline',
'strikethrough',
'highlight',
'|',
'alignment',
'|',
'numberedList',
'bulletedList',
'|',
'indent',
'outdent',
'|',
'todoList',
'link',
'blockQuote',
'imageUpload',
'insertTable',
'|',
'undo',
'redo'
]
},
language: 'en',
image: {
toolbar: [
'imageTextAlternative',
'imageStyle:full',
'imageStyle:side'
]
},
table: {
contentToolbar: [
'tableColumn',
'tableRow',
'mergeTableCells'
]
},
licenseKey: '',
wordCount: {
onUpdate: stats => {
this.charactersLength = stats.characters
}
}
}
}
That's it :)
NOTE: We don't use #ckeditor/ckeditor5-build-classic any more!
Wrong:
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
Correct:
import ClassicEditor from '#ckeditor/ckeditor5-editor-classic/src/classiceditor';
I had a similar problem. I solved it when I installed all modules of one version
I've face this issue when tried to use an editor on different pages few times.
Tried everything what is written above, nothing helped.
To solve the issue I used a React lazy loading for importing an editor.

Prevent user from pasting images into CKEditor 5

Our users are copy pasting images into the editor (CKEditor 5), which is something we don't want to support.
The suggested fix for CKEditor 4 does not seem to work in 5.
How can I change the editor to disable this feature?
This is my config:
const editorConfig = {
toolbar: ['heading', 'bold', 'italic', 'bulletedList', 'numberedList'],
removePlugins: ['Image', 'ImageToolbar', 'ImageStyle', 'ImageUpload', 'ImageCaption'],
heading: {
options: [
{
model: 'paragraph',
title: 'Paragraph',
class: 'ck-heading_paragraph',
},
{
model: 'heading2',
view: 'h2',
title: 'Heading',
class: 'ck-heading_heading2',
},
],
},
};
If you want to completely disable the image feature, this is – make it impossible for images to be inserted/loaded in any way to the editor, then you should remove the Image, ImageToolbar, ImageStyle, 'ImageUpload, and ImageCaption plugins from your editor. The easiest way to achieve that is to use config.removePlugins. You can also check how to install plugins cause uninstalling is just the opposite process.
If you want to disallow inserting images, but still keep support for those images which are already present in the content, you need to handle it a bit differently. You'd have to block pasting/dropping them which can be done with the features exposed by the clipboard integration. You may also want to remove the ImageUpload plugin in thi case too. Finally, you probably won't need the imageUpload button in the toolbar, so you have to reconfigure it.

How to use React (this.state) to achieve user input save automatically

I use react-quill made a rich text editor. I want to use this.state to store data, so that the content of input text box can be automatically saved, and the text input will not disappear after refreshing the page.
The code is for react-quill,How to modify the code to achieve my goal
import React from 'react'
export default class Editor extends React.Component {
constructor (props) {
super(props)
this.state = {
editorHtml: '',
}
this.handleChange = this.handleChange.bind(this)
if (typeof window !== 'undefined') {
this.ReactQuill = require('react-quill')
}
}
handleChange (html) {
this.setState({ editorHtml: html });
}
render() {
const ReactQuill = this.ReactQuill
if (typeof window !== 'undefined' && ReactQuill) {
return (
<div className='app'>
<ReactQuill
onChange={this.handleChange}
value={this.state.editorHtml}
modules={Editor.modules}
formats={Editor.formats}
/>
)
} else {
return null;
}
}
}
Editor.modules = {
toolbar: [
[{ 'header': '1'}, {'header': '2'}, { 'font': [] }],
[{size: []}],
['bold', 'italic', 'underline', 'strike', 'blockquote'],
[{'list': 'ordered'}, {'list': 'bullet'},
{'indent': '-1'}, {'indent': '+1'}],
['link', 'image', 'video'],
['clean']
],
clipboard: {
matchVisual: false,
}
}
Editor.formats = [
'header', 'font', 'size',
'bold', 'italic', 'underline', 'strike', 'blockquote',
'list', 'bullet', 'indent',
'link', 'image', 'video'
]
Where should your data be saved?
You don't mention a backend service so I'm not sure where you want to save this data. Since a react runs fresh on every page refresh you cannot store data only retrieve it from either an api or local storage.
local storage
If you would like to store data in local storage you will need to hydrate your app when it loads. This is the process of looking into the local storage and retrieving data for use. Note that this data will only be accessible from the machine & browser its written on and no good if you want to post this to other platforms.
API
If you wish to store your data in "the cloud" for later retrieval you should make an api to handle post and get requests. these will let you save data to a connected cloud database. In this case when called componentDidMount you can retrieve the previous sessions data with a get request.
Lambda functions
Similar to an api you can write "serverless" functions to handle data. firebase functions is a good place to start there, they are easy to understand and write in a few minutes.
Conclusion
All of these solutions will take quite a bit of time to put together, there's no "easy" way to just store and retrieve data outside of using some 3rd party service or setting up your own server & database.
Where you want to store data is the main question. If you are saving it to database and have an endpoint for getting the data. Let`s say "http://localhost:3001/api/form/data".
Make a request to the endpoint and set state after getting data in componentDidMount method of React, like this:
componentDidMount(){
fetch("http://localhost:3001/api/form/data")
.then(res=>res.json())
.then(data=>{
if(data){
this.setState.({editorHtml : data});
}
}).catch(ex){
//handle error here
}

Mobiscroll with Wheels Option breaks using display:inline

just found this plugin and like it pretty well so far. But, I'm having trouble with it. When I'm using the wheels option, I'm having trouble with the display:inline option working properly. If I remove the display:inline option and let it default to showing the input, I can click on it and it brings up everything in a modal fashion and it scrolls properly. Using the inline option, the scrollers won't work properly. Hoping someone can offer some assistance. Thanks!
Here's my code:
var options = [{
'Wheel 1': {
Option1: 'Option1'
, Option2: 'Option2'
, Option3: 'Option3'
, Option4: 'Option4'
, Option5: 'Option5'
, Option6: 'Option6'
, Option7: 'Option7'
}
, 'Wheel 2': {
Option1: 'Option1'
, Option2: 'Option2'
, Option3: 'Option3'
, Option4: 'Option4'
}
, 'Wheel 3': {
Option1: 'Option1'
, Option2: 'Option2'
, Option3: 'Option3'
}
}];
$('#ProjectSpinner').scroller({
theme: 'default',
preset: 'select',
rows:3,
//display: 'inline' <-- This is what kills it,
mode: 'scroller',
showLabel: true,
headerText: '<p style="font-weight:bold;font-size:1.3em;">Automagically Spin Up Your Project</p>',
wheels: options,
onShow: function (html, instance) {
$("input[id$='_dummy']").hide();
}
});
The problem is you are using the select preset with custom wheels. You should not use both, at the same time. You either use the select preset without the wheels option, or you use custom wheels without the preset option.

Resources