Here is my plugin that I use to customise the nodes in writer field, including text alignment and also some other styles I commonly need. This wraps the selected element in a class, which you can style in the panel and in your site using css.
plugin index.php
<?php
Kirby::plugin(
name: 'rm/writer-toolbar',
version: '1.0.0',
info: [
'description' => 'Custom marks and nodes for the writer',
'license' => 'RM',
'authors' => [
[
'name' => 'RM'
]
]
],
extends: [
'hooks' => [
'kirbytags:before' => function ($text, $data, $options) {
\Kirby\Sane\Html::$allowedTags['align-left'] = true;
\Kirby\Sane\Html::$allowedTags['align-center'] = true;
\Kirby\Sane\Html::$allowedTags['align-right'] = true;
\Kirby\Sane\Html::$allowedTags['text-heading'] = true;
\Kirby\Sane\Html::$allowedTags['type-size-notes'] = true;
return $text;
}
],
],
);
plugin index.js
window.panel.plugin('rm/writer-toolbar', {
writerNodes: {
textIndent: {
get button() {
return {
id: this.name,
icon: 'angle-right',
label: 'Indent',
name: this.name,
};
},
commands({ utils, schema, type }) {
return {
textIndent: () => {
return utils.setBlockType(type);
},
};
},
get name() {
return 'textIndent';
},
get schema() {
return {
content: 'inline*',
group: 'block',
draggable: false,
parseDOM: [
{
tag: 'p',
priority: 51,
getAttrs: node => (node.classList.contains('text-indent') ? {} : false),
},
],
toDOM: () => ['p', { class: 'text-indent' }, 0],
};
},
},
paragraphHeading: {
get button() {
return {
id: this.name,
icon: 'headline',
label: 'Paragraph Heading',
name: this.name,
separator: true,
};
},
commands({ utils, schema, type }) {
return {
paragraphHeading: () => {
return utils.setBlockType(type);
},
};
},
get name() {
return 'paragraphHeading';
},
get schema() {
return {
content: 'inline*',
group: 'block',
draggable: false,
parseDOM: [
{
tag: 'p',
priority: 51,
getAttrs: node => (node.classList.contains('paragraph-heading') ? {} : false),
},
],
toDOM: () => ['p', { class: 'paragraph-heading' }, 0],
};
},
},
textSizeHeading: {
get button() {
return {
id: this.name,
icon: 'font-size',
label: 'Heading',
name: this.name,
};
},
commands({ utils, schema, type }) {
return {
textSizeHeading: () => {
return utils.setBlockType(type);
},
};
},
get name() {
return 'textSizeHeading';
},
get schema() {
return {
content: 'inline*',
group: 'block',
draggable: false,
parseDOM: [
{
tag: 'p',
priority: 51,
getAttrs: node => (node.classList.contains('heading-text') ? {} : false),
},
],
toDOM: () => ['p', { class: 'heading-text' }, 0],
};
},
},
textSizeBody: {
get button() {
return {
id: this.name,
icon: 'font-size',
label: 'Body',
name: this.name,
};
},
commands({ utils, schema, type }) {
return {
textSizeBody: () => {
return utils.setBlockType(type);
},
};
},
get name() {
return 'textSizeBody';
},
get schema() {
return {
content: 'inline*',
group: 'block',
draggable: false,
parseDOM: [
{
tag: 'p',
priority: 51,
getAttrs: node => (node.classList.contains('body-text') ? {} : false),
},
],
toDOM: () => ['p', { class: 'body-text' }, 0],
};
},
},
textSizeNotes: {
get button() {
return {
id: this.name,
icon: 'font-size',
label: 'Notes',
name: this.name,
separator: true,
};
},
commands({ utils, schema, type }) {
return {
textSizeNotes: () => {
return utils.setBlockType(type);
},
};
},
get name() {
return 'textSizeNotes';
},
get schema() {
return {
content: 'inline*',
group: 'block',
draggable: false,
parseDOM: [
{
tag: 'p',
priority: 51,
getAttrs: node => (node.classList.contains('notes-text') ? {} : false),
},
],
toDOM: () => ['p', { class: 'notes-text' }, 0],
};
},
},
textAlignLeft: {
get button() {
return {
id: this.name,
icon: 'text-left',
label: 'Left',
name: this.name,
};
},
commands({ utils, schema, type }) {
return {
textAlignLeft: () => {
return utils.setBlockType(type);
},
};
},
get name() {
return 'textAlignLeft';
},
get schema() {
return {
content: 'inline*',
group: 'block',
draggable: false,
parseDOM: [
{
tag: 'p',
priority: 51,
getAttrs: node => (node.classList.contains('align-left') ? {} : false),
},
],
toDOM: () => ['p', { class: 'align-left' }, 0],
};
},
},
textAlignCenter: {
get button() {
return {
id: this.name,
icon: 'text-center',
label: 'Center',
name: this.name,
};
},
commands({ utils, schema, type }) {
return {
textAlignCenter: () => {
return utils.setBlockType(type);
},
};
},
get name() {
return 'textAlignCenter';
},
get schema() {
return {
content: 'inline*',
group: 'block',
draggable: false,
parseDOM: [
{
tag: 'p',
priority: 51,
getAttrs: node => (node.classList.contains('align-center') ? {} : false),
},
],
toDOM: () => ['p', { class: 'align-center' }, 0],
};
},
},
textAlignRight: {
get button() {
return {
id: this.name,
icon: 'text-right',
label: 'Right',
name: this.name,
separator: true,
};
},
commands({ utils, schema, type }) {
return {
textAlignRight: () => {
return utils.setBlockType(type);
},
};
},
get name() {
return 'textAlignRight';
},
get schema() {
return {
content: 'inline*',
group: 'block',
draggable: false,
parseDOM: [
{
tag: 'p',
priority: 51,
getAttrs: node => (node.classList.contains('align-right') ? {} : false),
},
],
toDOM: () => ['p', { class: 'align-right' }, 0],
};
},
},
},
});
site > blueprints > fields > writer.yml
This activates the custom nodes in the writer field.
label: Text
type: writer
# toolbar:
# inline: false
spellcheck: true
marks:
- italic
- link
- clear
nodes:
- bulletList
- quote
- textIndent
- paragraphHeading
- textSizeHeading
- textSizeBody
- textSizeNotes
- textAlignLeft
- textAlignCenter
- textAlignRight
assets > css > panel.css
This allows you to visualise the nodes applied in the panel. Using the same classes in your main css will allow you to style the text on the frontend.
/*--------------------------------------------------------------
# Panel
---------------------------------------------------------------*/
/* Additional Writer styles for the Writer plugin*/
/* Text Formatting */
.k-text blockquote {
padding-left: 3em;
font-size: inherit;
line-height: inherit;
padding-inline-start: none;
border-inline-start: none;
}
.text-indent {
padding-left: 3em;
margin-bottom: 0 !important;
}
.paragraph-heading {
display: block;
text-align: center;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-block-start: calc(var(--text-line-height) * 2em);
}
.paragraph-heading:first-of-type {
margin-block-start: 0;
}
.align-left {
display: block;
text-align: left;
}
.align-center {
display: block;
text-align: center;
}
.align-right {
display: block;
text-align: right;
}
/* Text sizes */
.heading-text {
display: block;
font-size: 1.2em;
}
.body-text {
display: block;
font-size: 1em;
}
.notes-text {
display: block;
font-size: 0.9em;
}