Allows RichTextEditor to have custom buttons#629
Conversation
|
Test Environment for snehilvj/dash-mantine-components-629 |
|
This looks great! Thanks for the PR 🏆 I'd like to see the |
| const controlName = Object.keys(ctl)[0]; | ||
| const options = ctl[controlName]; | ||
|
|
||
| if (controlName !== 'CustomButton') { |
There was a problem hiding this comment.
| if (controlName !== 'CustomButton') { | |
| if (controlName !== 'CustomControl') { |
Co-authored-by: Ann Marie Ward <72614349+AnnMarieW@users.noreply.github.com>
…Editor.tsx Co-authored-by: Ann Marie Ward <72614349+AnnMarieW@users.noreply.github.com>
…Editor.tsx Co-authored-by: Ann Marie Ward <72614349+AnnMarieW@users.noreply.github.com>
…Editor.tsx Co-authored-by: Ann Marie Ward <72614349+AnnMarieW@users.noreply.github.com>
|
Here is a sample app for custom Insert Table, Add Column and Delete Column as requested in #618 See it live on PyCafe: import dash_mantine_components as dmc
from dash import Dash
from dash_iconify import DashIconify
app=Dash()
content = """<h2 style="text-align: center;">RichTextEditor Custom Controls Demo</h2>"""
toolbar = {
"sticky": True,
"controlsGroups": [
[
{
"CustomControl": {
"ariaLabel": "Insert Table",
"title": "Insert Table",
"children": [DashIconify(icon="mdi:table-plus", width=20, height=20)],
"function": "insertTable",
},
},
{
"CustomControl": {
"ariaLabel": "Add Column Before",
"title": "Add Column Before",
"children": [DashIconify(icon="mdi:table-column-plus-before", width=20, height=20)],
"function": "addColumnBefore",
},
},
{
"CustomControl": {
"ariaLabel": "Delete Column",
"title": "Delete Column",
"children": [DashIconify(icon="mdi:table-column-remove", width=20, height=20)],
"function": "deleteColumn",
},
},
],
[
"Bold",
"Italic",
"Underline",
],
],
}
app.layout = dmc.MantineProvider(
dmc.RichTextEditor(
html=content,
toolbar=toolbar
)
)
if __name__ == "__main__":
app.run(debug=True)
.js var dmcfuncs = window.dashMantineFunctions = window.dashMantineFunctions || {};
dmcfuncs.insertTable = (_,__,{editor}) => {
if (!editor) {
return;
}
editor?.chain().focus().insertTable({ rows: 5, cols: 3, withHeaderRow: true }).run()
}
dmcfuncs.addColumnBefore = (_,__,{editor}) => {
if (!editor) {
return;
}
editor?.chain().focus().addColumnBefore().run()
}
dmcfuncs.deleteColumn= (_,__,{editor}) => {
if (!editor) {
return;
}
editor?.chain().focus().deleteColumn().run()
}.css |
|
The way we have it now, it's possible to place the custom control anywhere in the toolbar but it must be in a component = dmc.RichTextEditor(
html= '<div>Click control to insert star emoji</div>',
toolbar = {
"controlsGroups": [
[
{
"CustomControl": {
"aria-label": "Custom Button",
"title": "Custom Button",
"children": DashIconify(icon="mdi:star", width=20, height=20),
"function": "insertStar",
},
},
],
],
},
) |
| > | ||
| {newRenderDashComponent(children, i, [ | ||
| ...componentPath, | ||
| 'custom', |
There was a problem hiding this comment.
what's the custom prop for?
There was a problem hiding this comment.
It's to make the path right for Dash, it needs to be updated too.
|
is there a use-case for the first two props in the function? UpdateOK, I figured it out.... The first two props are fragments/RichTextEditor.tsx Here's a use-case, where you can make a re-usable function to insert content: .js file: dmcfuncs.insertContent = (e, options, {editor}) => {
if (!editor) {
return;
}
editor?.commands.insertContent(options)
}import dash_mantine_components as dmc
from dash import Dash
from dash_iconify import DashIconify
app=Dash()
content = """<h2 style="text-align: center;">RichTextEditor Custom Controls Demo</h2>"""
toolbar = {
"sticky": True,
"controlsGroups": [
[
{
"CustomControl": {
"ariaLabel": "Insert Star",
"title": "Insert Star",
"children": [DashIconify(icon="mdi:star", width=20, height=20)],
"onClick": {"function": "insertContent", "options": "⭐"},
},
},
{
"CustomControl": {
"ariaLabel": "Insert Heart",
"title": "Insert Heart",
"children": [DashIconify(icon="mdi:heart", width=20, height=20)],
"onClick": {"function": "insertContent", "options": "❤️"},
},
},
],
],
}
app.layout = dmc.MantineProvider(
dmc.RichTextEditor(
html=content,
toolbar=toolbar
)
)
if __name__ == "__main__":
app.run(debug=True) |
Co-authored-by: Ann Marie Ward <72614349+AnnMarieW@users.noreply.github.com>
|
Updated examples in the dmc-docs PR snehilvj/dmc-docs#239 |
| const { i, componentPath, editor, onClick, children, ...others } = props; | ||
| return ( | ||
| <MantineRichTextEditor.Control | ||
| onClick={resolveProp(onClick, { editor })} |
There was a problem hiding this comment.
Is there precedent here for this argument pattern, ie using resolveProp directly? Having two typically-ignored arguments up front and then a third that you always have to destructure is awkward. You could wrap resolveProp here in order to change this to (editor, event, options), and even put the editor existence guard into the wrapper if that's always going to be part of any real handler function.
If I'm understanding it correctly that would be something like:
onClick={(event) => {
if (editor) {
resolveProp(onClick)(editor, event);
}
}}That way you'd have much simpler functions most of the time:
dmcfuncs.insertContent = (editor, _, options) => {
editor?.commands.insertContent(options)
}That still leaves event in the middle as an often-ignored argument. I guess you could also put it in context:
onClick={(event) => {
if (editor) {
resolveProp(onClick, { event })(editor);
}
}}then nearly everyone would omit it entirely:
dmcfuncs.insertContent = (editor, options) => {
editor?.commands.insertContent(options)
}but if you did want it (to detect modifier keys or something?) you could use (editor, options, {event})
There was a problem hiding this comment.
I like having the attributes that are supplied by the function first, then options (passed from the dash app) last, so it's consistent with with all the other functions as props in dmc.
@BSd3v any thoughts?
There was a problem hiding this comment.
Yeah, I think that works. I was passing is as an object in the event that we wanted to expand it at some point to maybe pass other things from the RichTextEditor instance.
There was a problem hiding this comment.
Bryan also suggested passing both editor and event in an object. The latest commit makes it so can now do this:
dmcfuncs.insertContent = ({editor}, options) => {
editor?.commands.insertContent(options)
}

closes #618
example:
app.py
js
Also fixed an issue where
controlsGroupswouldnt allow to be empty or undefined.